Example #1
0
// marshalEndpoint marshals a service endpoint into a json schema
func marshalEndpoint(ep *Endpoint) (*jsonschema.JsonSchema, error) {
	reqT, resT := ep.ProtoTypes()
	if reqT == nil && resT == nil {
		return nil, nil
	}

	// Generate the root schema for this endpoint
	rootSchema := jsonschema.New()
	rootSchema.Id = fmt.Sprintf("http://directory.hailoweb.com/service/%s-%v.json#%s", Name, Version, ep.GetName())
	rootSchema.Title = fmt.Sprintf("%s-%v.%s", Name, Version, ep.GetName())
	rootSchema.Description = fmt.Sprintf("%s endpoint schema for %s-%v", ep.GetName(), Name, Version)
	rootSchema.Type = jsonschema.TYPE_OBJECT
	rootSchema.Schema = "http://json-schema.org/draft-04/schema#"

	if reqT != nil {
		// Generate the request skeleton
		reqSchema := jsonschema.New()
		reqSchema.Property = "Request"
		reqSchema.Parent = rootSchema

		if err := reflectEndpoint(reqT, reqSchema); err != nil {
			return nil, err
		}

		if len(reqSchema.Properties) > 0 || len(reqSchema.Definitions) > 0 {
			reqSchema.Type = jsonschema.TYPE_OBJECT
		}

		rootSchema.AddProperty(reqSchema)

	}

	if resT != nil {
		// Generate the response skeleton
		resSchema := jsonschema.New()
		resSchema.Property = "Response"
		resSchema.Parent = rootSchema

		if err := reflectEndpoint(resT, resSchema); err != nil {
			return nil, err
		}
		if len(resSchema.Properties) > 0 || len(resSchema.Definitions) > 0 {
			resSchema.Type = jsonschema.TYPE_OBJECT
		}

		rootSchema.AddProperty(resSchema)
	}

	// All definitions go on the root level
	definitions := make(map[string]*jsonschema.JsonSchema, 20)
	for _, child := range rootSchema.DefinitionsChildren {
		definitions[child.Property] = child
	}
	rootSchema.Definitions = definitions

	return rootSchema, nil
}
Example #2
0
// reflectEndpoint traverses a given endpoint and returns a predefined json schema
// based on its data
func reflectEndpoint(typ reflect.Type, schema *jsonschema.JsonSchema) error {
	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
	}
	// Iterate over not built-in types
	if typ.PkgPath() != "" && typ.Kind() == reflect.Struct {
		for i := 0; i < typ.NumField(); i++ {
			f := typ.Field(i)
			tag := f.Tag.Get("protobuf")

			// If there are no proto tags, we assume the field is unused
			if tag == "" {
				continue
			}

			// Parse the proto tags
			priority, propertyName, _, enum := parseProtoTag(tag)

			// Create a property wrapper
			prop := jsonschema.New()
			prop.Property = propertyName
			prop.Parent = schema

			var (
				items   *jsonschema.JsonSchema
				isSlice bool
			)

			typ := f.Type
			// Check if we have a slice
			if typ.Kind() == reflect.Slice {
				typ = typ.Elem()
				isSlice = true
				items = jsonschema.New()
				items.Parent = prop
			}

			// Check if we have a ptr and get the underlying type
			if typ.Kind() == reflect.Ptr {
				typ = typ.Elem()
			}

			// Add this to the required fields on this level if necessary
			if priority == "req" {
				if err := schema.AddRequired(propertyName); err != nil {
					return err
				}
			}

			// Deal with enums
			if enum != "" {
				enumVals := getProtoEnumValues(typ)

				if isSlice {
					items.Type = jsonschema.TYPE_STRING
					items.Enum = enumVals
					prop.Items = items
					prop.Type = jsonschema.TYPE_ARRAY
				} else {
					prop.Type = jsonschema.TYPE_STRING
					prop.Enum = enumVals
				}
				if err := schema.AddProperty(prop); err != nil {
					return err
				}
				continue
			}

			// Deal with custom types
			if typ.PkgPath() != "" {
				// If this was a slice, create a new property and add the ref as a child
				// Otherwise, add the reference as a normal property
				if isSlice {
					items.Ref = fmt.Sprintf("#/definitions/%s", typ.String())
					prop.Items = items
					prop.Type = jsonschema.TYPE_ARRAY
					if err := schema.AddProperty(prop); err != nil {
						return err
					}

				} else {
					ref := jsonschema.New()
					ref.Ref = fmt.Sprintf("#/definitions/%s", typ.String())
					ref.Parent = schema
					ref.Property = propertyName
					if err := schema.AddProperty(ref); err != nil {
						return err
					}
				}
				// Create a new definition for this object and iterate it
				// NOTE: We always put definitions at root level
				root := findRoot(schema)
				if root == nil {
					root = schema
				}

				def := jsonschema.New()
				def.Property = typ.String()

				def.Parent = root
				def.Type = jsonschema.TYPE_OBJECT
				root.AddDefinitionChild(def)
				reflectEndpoint(typ, def)
				continue
			}

			// We are dealing with a builtin type
			switch typ.Kind() {
			case reflect.String:
				prop.Type = jsonschema.TYPE_STRING

			case reflect.Bool:
				prop.Type = jsonschema.TYPE_BOOLEAN

			case reflect.Int, reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64,
				reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint8:
				prop.Type = jsonschema.TYPE_INTEGER

			case reflect.Float32, reflect.Float64, reflect.Complex128, reflect.Complex64:
				prop.Type = jsonschema.TYPE_NUMBER
			default:
				// We should never get here
				log.Errorf("Unhandled type: %v", typ.Kind())
				continue
			}
			// If we had a slice on this iteration, use the prop type for the nested items scheme
			if isSlice {
				items.Type = prop.Type
				prop.Type = jsonschema.TYPE_ARRAY
				prop.Items = items
			}
			if err := schema.AddProperty(prop); err != nil {
				return err
			}

		}
	}

	return nil
}