func init() { DefaultDocTemplate = template.Must(template.New("DefaultDocTemplate").Funcs(map[string]interface{}{ "RenderEndpoint": func(r DocumentedRoute) (result string, err error) { return }, "JSON": func(i interface{}) (result string, err error) { b, err := json.MarshalIndent(i, "", " ") if err != nil { return } result = string(b) return }, "UUID": func() string { return "" }, "RenderSubType": func(t JSONType, stack []string) (result string, err error) { return }, "RenderType": func(t JSONType) (result string, err error) { return }, "Example": func(r JSONType) (result string, err error) { return }, "First": first, }).Parse(DefaultDocTemplateContent)) template.Must(DefaultDocTemplate.New("EndpointTemplate").Parse(DefaultEndpointTemplateContent)) template.Must(DefaultDocTemplate.New("TypeTemplate").Parse(DefaultTypeTemplateContent)) DefaultDocHandler = DocHandler(DefaultDocTemplate) }
/* MarshalJSON will recursively run any found `BeforeMarshal` functions on the content with arg and a stack of container instances, and then json marshal it. It will not recurse down further after a BeforeMarshal function has been found, but it will run all top level BeforeMarshal functions that it finds. */ func (self *DefaultJSONContext) MarshalJSON(c interface{}, body interface{}, arg interface{}) (result []byte, err error) { // declare a function that recursively will run itself var runRecursive func(reflect.Value, reflect.Value) error cVal := reflect.ValueOf(c) contextType := reflect.TypeOf((*JSONContextLogger)(nil)).Elem() // initialize an empty container stack stackType := reflect.TypeOf([]interface{}{}) // implement the function runRecursive = func(val reflect.Value, stack reflect.Value) error { // push this instance to the stack stack = reflect.Append(stack, val) // Try run BeforeMarshal fun := val.MethodByName("BeforeMarshal") if fun.IsValid() && !utils.IsNil(val.Interface()) { // make sure we don't run BeforeMarshal on any other things at the same time, at least in this context. return self.marshalSyncLock.Sync(val.Interface(), func() (err error) { // Validate BeforeMarshal takes something that implements JSONContextLogger if err = utils.ValidateFuncInput(fun.Interface(), []reflect.Type{contextType, stackType}); err != nil { if err = utils.ValidateFuncInput(fun.Interface(), []reflect.Type{contextType, stackType, reflect.TypeOf(arg)}); err != nil { return fmt.Errorf("BeforeMarshal needs to take an JSONContextLogger") } } // Validate BeforeMarshal returns an error if err = utils.ValidateFuncOutput(fun.Interface(), []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}); err != nil { return fmt.Errorf("BeforeMarshal needs to return an error") } args := []reflect.Value{cVal, stack} if fun.Type().NumIn() == 3 { args = append(args, reflect.ValueOf(arg)) } timer := time.Now() // run the actual BeforeMarshal res := fun.Call(args) if time.Now().Sub(timer) > (500 * time.Millisecond) { self.Infof("BeforeMarshal for %s is slow, took: %v", val.Type(), time.Now().Sub(timer)) } if !res[0].IsNil() { err = res[0].Interface().(error) } return }) } // Try do recursion on these types, if we didn't find a BeforeMarshal func on the val itself switch val.Kind() { case reflect.Ptr, reflect.Interface: if val.IsNil() { return nil } return runRecursive(val.Elem(), stack) case reflect.Slice: for i := 0; i < val.Len(); i++ { if err := runRecursive(val.Index(i).Addr(), stack); err != nil { return err } } break case reflect.Struct: for i := 0; i < val.NumField(); i++ { if val.Type().Field(i).PkgPath == "" { if err := runRecursive(val.Field(i), stack); err != nil { return err } } } break } return nil } // Run recursive reflection on self.Body that executes BeforeMarshal on every object possible. stack := []interface{}{} if err = runRecursive(reflect.ValueOf(body), reflect.ValueOf(stack)); err != nil { return } // This makes sure that replies that returns a slice that is empty returns a '[]' instad of 'null' bodyVal := reflect.ValueOf(body) if bodyVal.Kind() == reflect.Slice && bodyVal.IsNil() { reflect.ValueOf(&body).Elem().Set(reflect.MakeSlice(bodyVal.Type(), 0, 0)) } if result, err = json.MarshalIndent(body, "", " "); err != nil { return } return }
/* DocHandler will return a handler that renders the documentation for all routes registerd with DocHandle. The resulting func will do this by going through each route in DocumentedRoutes and render the endpoint using the provided template, providing it template functions to render separate endpoints, types, sub types and examples of types. */ func DocHandler(templ *template.Template) http.Handler { return httpcontext.HandlerFunc(func(c httpcontext.HTTPContextLogger) (err error) { c.Resp().Header().Set("Content-Type", "text/html; charset=UTF-8") // we define a func to render a type // it basically just executes the "TypeTemplate" with the provided // stack to avoid infinite recursion renderType := func(t JSONType, stack []string) (result string, err error) { // if the type is already mentioned in one of the parents we have already mentioned, // bail for _, parent := range stack { if parent != "" && parent == t.ReflectType.Name() { result = fmt.Sprintf("[loop protector enabled, render stack: %v]", stack) return } } stack = append(stack, t.ReflectType.Name()) buf := &bytes.Buffer{} // then execute the TypeTemplate with this type and this stack if err = templ.ExecuteTemplate(buf, "TypeTemplate", map[string]interface{}{ "Type": t, "Stack": stack, }); err != nil { return } result = buf.String() return } // routes are documented alphabetically sort.Sort(routes) // define all the functions that we left empty earlier err = templ.Funcs(map[string]interface{}{ "RenderEndpoint": func(r DocumentedRoute) (string, error) { return r.Render(templ.Lookup("EndpointTemplate")) }, "RenderSubType": func(t JSONType, stack []string) (result string, err error) { return renderType(t, stack) }, "RenderType": func(t JSONType) (result string, err error) { return renderType(t, nil) }, "First": first, "Example": func(r JSONType) (result string, err error) { // this will render an example of the provided JSONType defer func() { if e := recover(); e != nil { result = fmt.Sprintf("%v\n%s", e, utils.Stack()) } }() x := utils.Example(r.ReflectType) b, err := json.MarshalIndent(x, "", " ") if err != nil { return } if len(r.Fields) > 0 { var i interface{} if err = json.Unmarshal(b, &i); err != nil { return } if m, ok := i.(map[string]interface{}); ok { newMap := map[string]interface{}{} for k, v := range m { if _, found := r.Fields[k]; found { newMap[k] = v } } if b, err = json.MarshalIndent(newMap, "", " "); err != nil { return } } } result = string(b) return }, }).Execute(c.Resp(), map[string]interface{}{ "Endpoints": routes, }) return }) }