Tips For Creating Reusable Commands

Reusable commands sit at the heart of cookoo. In this tutorial we will cover a number of tips for creating commands.

Getting Parameters Passed Into A Command

For this example we can take a look at a simple set of commands for a route. The route is:

registry.Route("GET /", "The Homepage").
    Does(MyMessage, "msg").
    Does(ActOnMessage, "out").
    Using("type").WithDefault("test").
    Using("content").From("cxt:msg")

And the first command, MyMessage is:

func MyMessage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    msg := "This is a test"

    return msg, nil
}

In this case the name msg as the second argument on the Does function that includes MyMessage is the name associated with the returned value msg from the command containing the string "This is a test".

In the second command, ActOnMessage the Using() function sets the name of the param to be passed in. The From function that follows tells cookoo where to get the value from. The string "cxt:msg" represents the msg option on the context. WithDefault() sets a default value to use instead of specifying where a value comes from.

func ActOnMessage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    type, ok := p.Has("type")
    content := p.Get("content", "")

    return true, nil
}

Here we look at two ways to retrieve a parameter that had been passed in. First, there is the p.Has() function. It returns a value, if one exist, and if the value was present. The second function, p.Get() returns a value if one exists. If none exists the second argument contains a default value to use.

Requiring Parameters

There are cases where data is required to be passed in. There are two useful function to use inside a command.

func ActOnMessage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    ok, missing := p.Requires("type", "content")
    ...
}

p.Requires() takes in a comma separated list of names to check. If one is missing ok will be false and missing will contain a list of the missing parameters.

func ActOnMessage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    ok, missing := p.Requires("type", "content")
    ...
}

Requires() checks if parameter is declared via Using(). RequiresValue() takes this a step further and makes sure a value exists as well. For example,

func ActOnMessage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    ok, missing := p.RequiresValue("type", "content")
    ...
}

Get Values From The Context

Sometimes there is a case to retrieve values from the context that were not passed in as parameters. There are two functions to help that along.

func Foo(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    writer, ok = cxt.Has("http.ResponseWriter")
    req := cxt.Get("http.Request", nil).(*http.Request)

    return true, nil
}

The functions c.Has() and c.Get() work the same their counterparts on the Params. The difference is they have access to anything on the context.

Note, the preferred method of passing information into commands is Params as they allow for the control of the data as it comes and goes from each command.

Return An Error

Sometimes there is an error and a command needs to let cookoo know about it. The two types of errors a command can return are cookoo.FatalError and cookoo.RecoverableError. These are returned as the second value, the cookoo.Interrupt. For example,

func Foo(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    return false, &cookoo.RecoverableError{"Not enough arguments."}
}

If you need to stop a chain of commands without there being an error there is a cookoo.Stop option as well.

Reroute A Chain of Commands

If you are executing one chain of commands and want to reroute to a different set of commands on a different route, you can use cookoo.Reroute. This is a return option for the second return value. For example,

func Foo(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    return false, &cookoo.Reroute{"GET /hello"}
}

The string passed into Reroute is the other route to execute.