Skip to content

Commit

Permalink
doc: add lot of documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
alexisvisco committed Sep 3, 2020
1 parent 7675ae0 commit f2ec960
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 30 deletions.
22 changes: 17 additions & 5 deletions examples/simple/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"context"
"net/http"

"github.com/go-chi/chi"
Expand All @@ -13,26 +14,37 @@ import (

func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.RequestID)

// kcd.Configuration.BindHook = ...
// kcd.Config.ErrorHook ...

r.Post("/{name}", kcd.Handler(SuperShinyHandler, http.StatusOK))
r.Use(func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, "id", 12345)
handler.ServeHTTP(w, r)
})
})

r.Get("/{name}", kcd.Handler(SuperShinyHandler, http.StatusOK))
_ = http.ListenAndServe(":3000", r)
}

// CreateCustomerInput is an example of input for an http request.
type CreateCustomerInput struct {
Name string `json:"name" path:"name"`
Emails []string `json:"emails"`
Name string `path:"name"`
Emails []string `query:"emails" exploder:","`
ContextualID *struct {
ID int `ctx:"id"`
}
}

// Validate is the function that will be called before calling your shiny handler.
func (c CreateCustomerInput) Validate() error {
return validation.ValidateStruct(&c,
validation.Field(&c.Name, validation.Required, validation.Length(5, 20)),
validation.Field(&c.Emails, validation.Each(is.Email)),
validation.Field(&c.ContextualID, validation.Required),
)
}

Expand Down
24 changes: 15 additions & 9 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,23 @@ import (
"github.com/expectedsh/kcd/internal/types"
)

// StructAnalyzer is the cache struct analyzer
type StructAnalyzer struct {
tags []string
valueTag []string
mainStructType reflect.Type
}

// NewStructAnalyzer instantiate a new StructAnalyzer
func NewStructAnalyzer(stringsTags, valueTags []string, mainStructType reflect.Type) *StructAnalyzer {
return &StructAnalyzer{
tags: append(stringsTags, valueTags...),
valueTag: valueTags,
mainStructType: mainStructType,
}
}

// StructCache
type StructCache struct {
IsRoot bool
Index []int
Expand All @@ -21,11 +32,13 @@ type StructCache struct {
Child []StructCache
}

// String is a way to debug StructCache
func (s StructCache) String() string {
marshal, _ := json.MarshalIndent(s, "", " ")
return string(marshal)
}

// FieldMetadata contains all the necessary field to decode
type FieldMetadata struct {
Index []int
Paths TagsPath
Expand All @@ -36,14 +49,7 @@ type FieldMetadata struct {
Exploder string
}

func NewStructAnalyzer(stringsTags, valueTags []string, mainStructType reflect.Type) *StructAnalyzer {
return &StructAnalyzer{
tags: append(stringsTags, valueTags...),
valueTag: valueTags,
mainStructType: mainStructType,
}
}

// Cache will take all fields which contain a tag to be lookup.
func (s StructAnalyzer) Cache() StructCache {
sc := newStructCache()

Expand All @@ -67,7 +73,7 @@ func (s StructAnalyzer) cache(cache *StructCache, paths TagsPath, t reflect.Type
metadata = FieldMetadata{Index: structField.Index, Type: structField.Type}
)

if types.IsImplementingUnmarshaller(metadata.Type) {
if types.IsImplementingUnmarshaler(metadata.Type) {
metadata.ImplementUnmarshaller = true
}

Expand Down
5 changes: 5 additions & 0 deletions internal/cache/tags_path.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package cache

// TagsPath is a simple map that register from a tag the path of the field.
type TagsPath map[string]string

// Add will add or create the path for a given tag.
// If the tag exist it will add it with the dot notation.
func (t TagsPath) Add(tag string, key string) {
k, ok := t[tag]
if !ok {
Expand All @@ -18,6 +21,7 @@ func (t TagsPath) Add(tag string, key string) {
t[tag] = k
}

// clone will duplicate this map.
func (t TagsPath) clone() TagsPath {
n := make(TagsPath, len(t))
for k, v := range t {
Expand All @@ -27,6 +31,7 @@ func (t TagsPath) clone() TagsPath {
return n
}

// hasValueTag check if there is a key from the set list.
func (t TagsPath) hasValueTag(set []string) bool {
for _, v := range set {
_, ok := t[v]
Expand Down
4 changes: 4 additions & 0 deletions internal/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/expectedsh/kcd/pkg/extractor"
)

// Decoder is the http decoder system for KCD.
type Decoder struct {
req *http.Request
res http.ResponseWriter
Expand All @@ -17,6 +18,7 @@ type Decoder struct {
valueExtractors []extractor.Value
}

// NewDecoder create a new Decoder.
func NewDecoder(
req *http.Request,
res http.ResponseWriter,
Expand Down Expand Up @@ -57,6 +59,8 @@ func (d previousFields) getCurrentReflectValue() reflect.Value {
return field
}

// Decode will from cache struct and a root value decode the http request/response
// and set all extracted values inside the root parameter.
func (d Decoder) Decode(c cache.StructCache, root reflect.Value) error {
return d.decode(c, root.Type(), previousFields{root: root})
}
Expand Down
10 changes: 5 additions & 5 deletions internal/decoder/field_setter.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (f fieldSetter) setForArrayOrSlice(ptr bool, list []string) error {
}

addToElem(i, native)
case types.IsImplementingUnmarshaller(f.metadata.Type):
case types.IsImplementingUnmarshaler(f.metadata.Type):
withUnmarshaller, err := f.makeWithUnmarshaller(val)
if err != nil {
return err.WithField("value-index", i)
Expand Down Expand Up @@ -169,7 +169,7 @@ func (f fieldSetter) setForNormalType(str string, ptr bool) error {
}

f.field.Set(native)
case types.IsImplementingUnmarshaller(f.metadata.Type):
case types.IsImplementingUnmarshaler(f.metadata.Type):
withUnmarshaller, err := f.makeWithUnmarshaller(str)
if err != nil {
return err
Expand Down Expand Up @@ -265,7 +265,7 @@ func (f fieldSetter) makeWithUnmarshaller(str string) (reflect.Value, *errors.Er
el = reflect.New(f.metadata.Type)
}

if el.Type().Implements(types.UnmarshallerText) {
if el.Type().Implements(types.UnmarshalerText) {
t := el.Interface().(encoding.TextUnmarshaler)

err := t.UnmarshalText([]byte(str))
Expand All @@ -278,7 +278,7 @@ func (f fieldSetter) makeWithUnmarshaller(str string) (reflect.Value, *errors.Er
return el, nil
}

if el.Type().Implements(types.JSONUnmarshaller) {
if el.Type().Implements(types.JSONUnmarshaler) {
t := el.Interface().(json.Unmarshaler)
err := t.UnmarshalJSON([]byte(str))
if err != nil {
Expand All @@ -290,7 +290,7 @@ func (f fieldSetter) makeWithUnmarshaller(str string) (reflect.Value, *errors.Er
return el, nil
}

if el.Type().Implements(types.BinaryUnmarshaller) {
if el.Type().Implements(types.BinaryUnmarshaler) {
t := el.Interface().(encoding.BinaryUnmarshaler)
err := t.UnmarshalBinary([]byte(str))
if err != nil {
Expand Down
9 changes: 7 additions & 2 deletions internal/kcderr/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import (
)

var (
Input = errors.Kind("input")
InputCritical = errors.Kind("input_critical")
// Input errors are related to bad request and didn't need to be logged
Input = errors.Kind("input")

// InputCritical should never happen, those kind of error are logged
InputCritical = errors.Kind("input_critical")

// OutputCritical should never happen, those kind of error are logged
OutputCritical = errors.Kind("output_critical")
)
26 changes: 19 additions & 7 deletions internal/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,30 @@ import (
"time"
)

var Unmarshallers = []reflect.Type{
// Unmarshalers is the list of possible unmarshaler kcd support.
var Unmarshalers = []reflect.Type{
reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem(),
reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem(),
reflect.TypeOf((*json.Unmarshaler)(nil)).Elem(),
}

var (
UnmarshallerText = Unmarshallers[0]
BinaryUnmarshaller = Unmarshallers[1]
JSONUnmarshaller = Unmarshallers[2]
// UnmarshalerText is the type of TextUnmarshaler
UnmarshalerText = Unmarshalers[0]

// BinaryUnmarshaler is the type of BinaryUnmarshaler
BinaryUnmarshaler = Unmarshalers[1]

// JSONUnmarshaler is the type of json.Unmarshaler
JSONUnmarshaler = Unmarshalers[2]
)

func IsImplementingUnmarshaller(t reflect.Type) bool {
// IsImplementingUnmarshaler check if the type t implement one of the possible unmarshalers.
func IsImplementingUnmarshaler(t reflect.Type) bool {
if t.Kind() != reflect.Ptr {
t = reflect.New(t).Type()
}
for _, u := range Unmarshallers {
for _, u := range Unmarshalers {
if t.Implements(u) {
return true
}
Expand All @@ -34,11 +41,13 @@ func IsImplementingUnmarshaller(t reflect.Type) bool {

var d = time.Duration(1)

// Custom is the list of custom types supported.
var Custom = []reflect.Type{
reflect.TypeOf(time.Duration(1)),
reflect.TypeOf(&d),
}

// IsCustomType check the type is a custom type.
func IsCustomType(t reflect.Type) bool {
for _, c := range Custom {
if t.AssignableTo(c) {
Expand All @@ -48,6 +57,7 @@ func IsCustomType(t reflect.Type) bool {
return false
}

// Native is the list of supported native types.
var Native = map[reflect.Kind]bool{
reflect.String: true,
reflect.Bool: true,
Expand All @@ -65,11 +75,13 @@ var Native = map[reflect.Kind]bool{
reflect.Float64: true,
}

// IsNative check if the type t is a native type.
func IsNative(t reflect.Type) bool {
_, ok := Native[t.Kind()]
return ok
}

// IsUnmarshallable check if the type t is either a native, custom type or implement an unmarshaler.
func IsUnmarshallable(t reflect.Type) bool {
return IsNative(t) || IsCustomType(t) || IsImplementingUnmarshaller(t)
return IsNative(t) || IsCustomType(t) || IsImplementingUnmarshaler(t)
}
2 changes: 1 addition & 1 deletion kcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"github.com/expectedsh/kcd/pkg/hook"
)

// Configuration is the main configuration type of kcd
// Configuration is the main configuration type of kcd.
type Configuration struct {
StringsExtractors []extractor.Strings
ValueExtractors []extractor.Value
Expand Down
2 changes: 2 additions & 0 deletions pkg/extractor/extractors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"net/http"
)

// Strings extract multiples strings values from request/response.
type Strings interface {
Extract(req *http.Request, res http.ResponseWriter, valueOfTag string) ([]string, error)
Tag() string
}

// Value extract one value (a type) from http request/response.
type Value interface {
Extract(req *http.Request, res http.ResponseWriter, valueOfTag string) (interface{}, error)
Tag() string
Expand Down
1 change: 1 addition & 0 deletions pkg/hook/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/sirupsen/logrus"
)

// Log will log the error.
func Log(_ http.ResponseWriter, r *http.Request, err error) {
var logger *logrus.Entry

Expand Down
2 changes: 1 addition & 1 deletion pkg/hook/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// Validate is the default validation hook.
// It use 'ozzo-validation' to validate structure.
// A structure must implement 'ValidatableWithContext' or 'Validatable'
// A structure must implement 'ValidatableWithContext' or 'Validatable'.
func Validate(ctx context.Context, input interface{}) error {
switch v := input.(type) {
case validation.ValidatableWithContext:
Expand Down

0 comments on commit f2ec960

Please sign in to comment.