intro

In ft’s codebase, there is heavy use of interfaces otherwise known as abstract data structures in other programming languages. The reason is simple: mock implementation can be used to test out functionality without much complication.

Let’s go in depth of ft’s codebase to further illustrate this idea and go more in depth of the coding style of ft.

controllers

type ControllerError struct {
    ID     string `json:"id"`
    Reason string `json:"reason"`
}

type Controller interface {
    // Error is a function that tells the client that their request
    // encountered an error.
    Error(err ControllerError)
    // Value is a function that tells the client that their request
    // was successful with an extra value.
    Value(val interface{}) error
}

In simple terms, a controller is essentially a request that can be written to. In case of an error it writes the generic ControllerError, otherwise it writes a dynamic value that can be of any data type; even ControllerError.

The use of Controller allows for easier implementation by protocols; as I do not want ft to be bound by any protocol whatsoever. There aren’t plans to implement ft in say NFS or ftp, but if ther happens to be a better protocol than http then ft might consider adopting it.

Do note that Value does not specify an encoding protocol but instead leaves it on the implementation.

testing controllers

To test a controller; one can use DummyController as it provides ways to read the underlying error or value.

DummyController can be used via creating an empty pointer to it, like so:

func main() {
    dc := &DummyController{}
    // ...
    MethodThatUsesController(dc)
    // read the value
    var str string
    if err := dc.Read(str); err != nil {
        panic(err)
    }
    
    // or read error
    if err := dc.GetError(); err != nil {
        panic(err)
    }
}

Always use DummyController as it is a lot easier than starting an http server and parsing through the request to get the body response.

Do note that methods that take in a controller and writes an important value should always return it. Always write the value to the controller and return the value you have written.

// bad
func WriteStringToController(str string, c model.Controller)
// good
func WriteStringToController(str string, c model.Controller) (string, error)

controller functions should be uniform

In ft, most functions that take in a controller take two parameters and return two values. The two parameters should always be io.Reader and model.Controller.

The encoding can depend on the function but encoding/json is, most of the times, used. Generally, it can be inferred that io.Reader should have a json encoded value.

The value data structure should have a similar name to the function. This also applies to the returned value aswell.

// parameter
type ReadDirData struct {
    Name string `json:"name"`
}
// return value
type ReadDirValue struct {
    Files []model.OsFileInfo `json:"files"`
}
// therefore
func ReadDir(rd io.Reader, ctrl model.Controller) (ReadDirValue, error) {
    // unmarshal rd to ReadDirData
}

If two data structures have the same structure, you could shorten this to a generic structure. For example, most operation functions(pause, resume, start) have the exact same data structure, so they take in a OperationGenericData and return that same value when successful.

structures to replace extra values

I mentioned before that most functions should always stick to having two parameters io.Reader and model.Controller, but if a function needs access to a filesystem, how should the function get it? Through a data structure.

A good example of functions that take multiple parameters in ft is controller.FsController. Basically, a FsController is a data structure that provides methods for controllers to interact with the file system, deleting, moving, creating directories, and reading directories.

FsController takes in a Channel, which is a sort of subscriber model that provides live updates to the clients about the file system, and a file system. Underlying functions such as MkdirAll use the FsController’s internal properties.