Example #1
0
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)
}
Example #2
0
/*
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
}
Example #3
0
/*
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
	})
}