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:
Copy 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:
Copy type SherrifGetter interface {
GetSherrifOptions () * sheriff . Options
}
Now, let's create the type that will be embedded in your output structures:
Copy 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!
Copy 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.
Copy kcd.Config.RenderHook = Render // Render is the function defined above
All together
Copy 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='