Conditionally render your outputs

Conditionally render your outputs based on user information or external data

It's relatively common to return some values to users and not to others.

For example, if you have a common website you may want to return the email of a user only if it's the user is authenticated right?

I just found a library that does this with tags in your structure, it also supports versioning.

This library is called https://github.com/liip/sheriff

Example:

type GroupsExample struct {
    Username      string `json:"username" groups:"api"`
    Email         string `json:"email" groups:"personal"`
    SomethingElse string `json:"something_else" groups:"api,personal"`
}

Email and SomethingElse will only be visible when the group personal is set (so when the user is authenticated).

In this short example, you will see how to integrate this library with KCD.

How to pass sherrif options to KCD ?

The simplest way is to create an interface and a struct that is embedded in your output structure. Then in the render hook, you will test if the output implements that interface, you got it, then you can retrieve sherrif options. Let's do that!

First, define an interface:

type SherrifGetter interface {
	GetSherrifOptions() *sheriff.Options
}

Now, let's create the type that will be embedded in your output structures:

type SheriffOptions struct {
	SheriffOptions *sheriff.Options `json:"-"`
}

func (g SheriffOptions) GetSherrifOptions() *sheriff.Options {
	return g.SheriffOptions
}

Override the render hook to use sheriff

Just create a function that is a copy/paste of the default implementation.

Now adapt the code to use the sheriff getter interface previously created!

const (
	errJson     = "unable to render response in json format"
	errResponse = "unable to write response"
)

func Render(w http.ResponseWriter, _ *http.Request, response interface{}, statusCode int) error {
	if response != nil {
		var (
			marshal []byte
			err     error
		)

		viaSherrif, ok := response.(SheriffGetter)
		if ok {
		
		  // sheriff return a map[string]interface{} with keys taken from the json tag
			response, err = sheriff.Marshal(viaSherrif.GetSherrifOptions(), response)

			if err != nil {
				return errors.Wrap(err, errJson).WithKind(errors.KindInternal)
			}
		}

		marshal, err = json.Marshal(response)
		if err != nil {
			return errors.Wrap(err, errJson).WithKind(errors.KindInternal)
		}

		w.Header().Set("Content-type", "application/json")
		w.WriteHeader(statusCode)
		if _, err := w.Write(marshal); err != nil {
			return errors.Wrap(err, errResponse).WithKind(errors.KindInternal)
		}
	} else {
		w.WriteHeader(statusCode)
	}

	return nil
}

In your main, you just have to override the KCD render handler with the function above.

kcd.Config.RenderHook = Render // Render is the function defined above

All together

package main

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/alexisvisco/kcd/pkg/errors"
	"github.com/alexisvisco/kcd"
	"github.com/go-chi/chi"
	"github.com/liip/sheriff"
)

const (
	errJson     = "unable to render response in json format"
	errResponse = "unable to write response"
)

type SherrifGetter interface {
	GetSherrifOptions() *sheriff.Options
}

type SheriffOptions struct {
	SheriffOptions *sheriff.Options `json:"-"`
}

func (g SheriffOptions) GetSherrifOptions() *sheriff.Options {
	return g.SheriffOptions
}

func Render(w http.ResponseWriter, _ *http.Request, response interface{}, statusCode int) error {
	if response != nil {
		var (
			marshal []byte
			err     error
		)

		viaSheriff, ok := response.(SherrifGetter)
		if ok {

			// sheriff return a map[string]interface{} with keys taken from the json tag
			response, err = sheriff.Marshal(viaSheriff.GetSherrifOptions(), response)

			if err != nil {
				return errors.Wrap(err, errJson).WithKind(errors.KindInternal)
			}
		}

		marshal, err = json.Marshal(response)
		if err != nil {
			return errors.Wrap(err, errJson).WithKind(errors.KindInternal)
		}

		w.Header().Set("Content-type", "application/json")
		w.WriteHeader(statusCode)
		if _, err := w.Write(marshal); err != nil {
			return errors.Wrap(err, errResponse).WithKind(errors.KindInternal)
		}
	} else {
		w.WriteHeader(statusCode)
	}

	return nil
}


type Input struct {
	Groups []string `query:"groups" default:"api" exploder:","`
}

type Output struct {
	*SheriffOptions // pointer is required since we are using interface SherrifGetter

	Name string `groups:"api"`
	Email string `groups:"personal"`
	SomethingElse string `groups:"api,personal"`
}


func main() {
	kcd.Config.RenderHook = Render


	r := chi.NewRouter()
	r.Get("/", kcd.Handler(YourHttpHandler, http.StatusOK))
	_ = http.ListenAndServe(":3000", r)
}

func YourHttpHandler(in *Input) (*Output, error) {
	fmt.Println(len(in.Groups))

	return &Output{
		SheriffOptions: &SheriffOptions{SheriffOptions: &sheriff.Options{
			Groups:     in.Groups,
			ApiVersion: nil,
		}},
		Name:           "Alexis",
		Email:          "alexis.viscogliosi@outlook.fr",
		SomethingElse:  "haha !",
	}, nil
}

// Test it : curl 'localhost:3000?groups=api'
// Test it : curl 'localhost:3000?groups=personal'
// Test it : curl 'localhost:3000?groups=personal,api'
// Test it : curl 'localhost:3000?groups='

Last updated