Beispiel #1
0
// Params describe the action parameters, either path parameters identified via wildcards or query
// string parameters if there is no corresponding path parameter. Each parameter is described via
// the Param function which uses the same DSL as the Attribute DSL. Here is an example:
//
//	Params(func() {
//		Param("id", Integer)		// A path parameter defined using e.g. GET("/:id")
//		Param("sort", String, func() {	// A query string parameter
//			Enum("asc", "desc")
//		})
//	})
//
// Params can be used inside Action to define the action parameters, Resource to define common
// parameters to all the resource actions or API to define common parameters to all the API actions.
//
// If Params is used inside Resource or Action then the resource base media type attributes provide
// default values for all the properties of params with identical names. For example:
//
//     var BottleMedia = MediaType("application/vnd.bottle", func() {
//         Attributes(func() {
//             Attribute("name", String, "The name of the bottle", func() {
//                 MinLength(2) // BottleMedia has one attribute "name" which is a
//                              // string that must be at least 2 characters long.
//             })
//         })
//         View("default", func() {
//             Attribute("name")
//         })
//     })
//
//     var _ = Resource("Bottle", func() {
//         DefaultMedia(BottleMedia) // Resource "Bottle" uses "BottleMedia" as default
//         Action("show", func() {   // media type.
//             Routing(GET("/:name"))
//             Params(func() {
//                 Param("name") // inherits type, description and validation from
//                               // BottleMedia "name" attribute
//             })
//         })
//     })
//
func Params(dsl func()) {
	var params *design.AttributeDefinition
	switch def := dslengine.CurrentDefinition().(type) {
	case *design.ActionDefinition:
		params = newAttribute(def.Parent.MediaType)
	case *design.ResourceDefinition:
		params = newAttribute(def.MediaType)
	case *design.APIDefinition:
		params = new(design.AttributeDefinition)
	default:
		dslengine.IncompatibleDSL()
	}
	params.Type = make(design.Object)
	if !dslengine.Execute(dsl, params) {
		return
	}
	switch def := dslengine.CurrentDefinition().(type) {
	case *design.ActionDefinition:
		def.Params = def.Params.Merge(params) // Useful for traits
	case *design.ResourceDefinition:
		def.Params = def.Params.Merge(params) // Useful for traits
	case *design.APIDefinition:
		def.Params = def.Params.Merge(params) // Useful for traits
	}
}
Beispiel #2
0
// RecursiveFinalizer produces Go code that sets the default values for fields recursively for the
// given attribute.
func RecursiveFinalizer(att *design.AttributeDefinition, target string, depth int, vs ...map[string]bool) string {
	var assignments []string
	if o := att.Type.ToObject(); o != nil {
		if mt, ok := att.Type.(*design.MediaTypeDefinition); ok {
			if len(vs) == 0 {
				vs = []map[string]bool{make(map[string]bool)}
			} else if _, ok := vs[0][mt.TypeName]; ok {
				return ""
			}
			vs[0][mt.TypeName] = true
			att = mt.AttributeDefinition
		} else if ut, ok := att.Type.(*design.UserTypeDefinition); ok {
			if len(vs) == 0 {
				vs = []map[string]bool{make(map[string]bool)}
			} else if _, ok := vs[0][ut.TypeName]; ok {
				return ""
			}
			vs[0][ut.TypeName] = true
			att = ut.AttributeDefinition
		}
		o.IterateAttributes(func(n string, catt *design.AttributeDefinition) error {
			if att.HasDefaultValue(n) {
				data := map[string]interface{}{
					"target":     target,
					"field":      n,
					"catt":       catt,
					"depth":      depth,
					"isDatetime": catt.Type == design.DateTime,
					"defaultVal": printVal(catt.Type, catt.DefaultValue),
				}
				assignments = append(assignments, RunTemplate(assignmentT, data))
			}
			assignment := RecursiveFinalizer(
				catt,
				fmt.Sprintf("%s.%s", target, Goify(n, true)),
				depth+1,
				vs...,
			)
			if assignment != "" {
				if catt.Type.IsObject() {
					assignment = fmt.Sprintf("%sif %s.%s != nil {\n%s\n%s}",
						Tabs(depth), target, Goify(n, true), assignment, Tabs(depth))
				}
				assignments = append(assignments, assignment)
			}
			return nil
		})
	} else if a := att.Type.ToArray(); a != nil {
		data := map[string]interface{}{
			"elemType": a.ElemType,
			"target":   target,
			"depth":    1,
		}
		assignment := RunTemplate(arrayAssignmentT, data)
		if assignment != "" {
			assignments = append(assignments, assignment)
		}
	}
	return strings.Join(assignments, "\n")
}
Beispiel #3
0
// ArrayOf creates an array type from its element type. The result can be used anywhere a type can.
// Examples:
//
//	var Bottle = Type("bottle", func() {
//		Attribute("name")
//	})
//
//	var Bottles = ArrayOf(Bottle)
//
//	Action("update", func() {
//		Params(func() {
//			Param("ids", ArrayOf(Integer))
//		})
//		Payload(ArrayOf(Bottle))  // Equivalent to Payload(Bottles)
//	})
//
// If you are looking to return a collection of elements in a Response
// clause, refer to CollectionOf.  ArrayOf creates a type, where
// CollectionOf creates a media type.
func ArrayOf(t design.DataType) *design.Array {
	at := design.AttributeDefinition{Type: t}
	if ds, ok := t.(design.DataStructure); ok {
		at.APIVersions = ds.Definition().APIVersions
	}
	return &design.Array{ElemType: &at}
}
Beispiel #4
0
// attributeTags computes the struct field tags.
func attributeTags(parent, att *design.AttributeDefinition, name string, private bool) string {
	var elems []string
	keys := make([]string, len(att.Metadata))
	i := 0
	for k := range att.Metadata {
		keys[i] = k
		i++
	}
	sort.Strings(keys)
	for _, key := range keys {
		val := att.Metadata[key]
		if strings.HasPrefix(key, "struct:tag:") {
			name := key[11:]
			value := strings.Join(val, ",")
			elems = append(elems, fmt.Sprintf("%s:\"%s\"", name, value))
		}
	}
	if len(elems) > 0 {
		return " `" + strings.Join(elems, " ") + "`"
	}
	// Default algorithm
	var omit string
	if private || (!parent.IsRequired(name) && !parent.HasDefaultValue(name)) {
		omit = ",omitempty"
	}
	return fmt.Sprintf(" `form:\"%s%s\" json:\"%s%s\" xml:\"%s%s\"`", name, omit, name, omit, name, omit)
}
Beispiel #5
0
// goTypeDefObject returns the Go code that defines a Go struct.
func goTypeDefObject(obj design.Object, def *design.AttributeDefinition, tabs int, jsonTags, private bool) string {
	var buffer bytes.Buffer
	buffer.WriteString("struct {\n")
	keys := make([]string, len(obj))
	i := 0
	for n := range obj {
		keys[i] = n
		i++
	}
	sort.Strings(keys)
	for _, name := range keys {
		WriteTabs(&buffer, tabs+1)
		field := obj[name]
		typedef := GoTypeDef(field, tabs+1, jsonTags, private)
		if (field.Type.IsPrimitive() && private) || field.Type.IsObject() || def.IsPrimitivePointer(name) {
			typedef = "*" + typedef
		}
		fname := GoifyAtt(field, name, true)
		var tags string
		if jsonTags {
			tags = attributeTags(def, field, name, private)
		}
		desc := obj[name].Description
		if desc != "" {
			desc = strings.Replace(desc, "\n", "\n\t// ", -1)
			desc = fmt.Sprintf("// %s\n\t", desc)
		}
		buffer.WriteString(fmt.Sprintf("%s%s %s%s\n", desc, fname, typedef, tags))
	}
	WriteTabs(&buffer, tabs)
	buffer.WriteString("}")
	return buffer.String()
}
Beispiel #6
0
func paramsFromDefinition(params *design.AttributeDefinition, path string) ([]*Parameter, error) {
	if params == nil {
		return nil, nil
	}
	obj := params.Type.ToObject()
	if obj == nil {
		return nil, fmt.Errorf("invalid parameters definition, not an object")
	}
	res := make([]*Parameter, len(obj))
	i := 0
	wildcards := design.ExtractWildcards(path)
	obj.IterateAttributes(func(n string, at *design.AttributeDefinition) error {
		in := "query"
		required := params.IsRequired(n)
		for _, w := range wildcards {
			if n == w {
				in = "path"
				required = true
				break
			}
		}
		param := paramFor(at, n, in, required)
		res[i] = param
		i++
		return nil
	})
	return res, nil
}
Beispiel #7
0
// RecursivePublicizer produces code that copies fields from the private struct to the
// public struct
func RecursivePublicizer(att *design.AttributeDefinition, source, target string, depth int) string {
	var publications []string
	if o := att.Type.ToObject(); o != nil {
		if mt, ok := att.Type.(*design.MediaTypeDefinition); ok {
			// Hmm media types should never get here
			att = mt.AttributeDefinition
		} else if ut, ok := att.Type.(*design.UserTypeDefinition); ok {
			att = ut.AttributeDefinition
		}
		o.IterateAttributes(func(n string, catt *design.AttributeDefinition) error {
			publication := Publicizer(
				catt,
				fmt.Sprintf("%s.%s", source, Goify(n, true)),
				fmt.Sprintf("%s.%s", target, Goify(n, true)),
				catt.Type.IsPrimitive() && !att.IsPrimitivePointer(n),
				depth+1,
				false,
			)
			publication = fmt.Sprintf("%sif %s.%s != nil {\n%s\n%s}",
				Tabs(depth), source, Goify(n, true), publication, Tabs(depth))
			publications = append(publications, publication)
			return nil
		})
	}
	return strings.Join(publications, "\n")
}
Beispiel #8
0
func payload(isOptional bool, p interface{}, dsls ...func()) {
	if len(dsls) > 1 {
		dslengine.ReportError("too many arguments given to Payload")
		return
	}
	if a, ok := actionDefinition(); ok {
		var att *design.AttributeDefinition
		var dsl func()
		switch actual := p.(type) {
		case func():
			dsl = actual
			att = newAttribute(a.Parent.MediaType)
			att.Type = design.Object{}
		case *design.AttributeDefinition:
			att = design.DupAtt(actual)
		case *design.UserTypeDefinition:
			if len(dsls) == 0 {
				a.Payload = actual
				a.PayloadOptional = isOptional
				return
			}
			att = design.DupAtt(actual.Definition())
		case *design.MediaTypeDefinition:
			att = design.DupAtt(actual.AttributeDefinition)
		case string:
			ut, ok := design.Design.Types[actual]
			if !ok {
				dslengine.ReportError("unknown payload type %s", actual)
			}
			att = design.DupAtt(ut.AttributeDefinition)
		case *design.Array:
			att = &design.AttributeDefinition{Type: actual}
		case *design.Hash:
			att = &design.AttributeDefinition{Type: actual}
		case design.Primitive:
			att = &design.AttributeDefinition{Type: actual}
		default:
			dslengine.ReportError("invalid Payload argument, must be a type, a media type or a DSL building a type")
			return
		}
		if len(dsls) == 1 {
			if dsl != nil {
				dslengine.ReportError("invalid arguments in Payload call, must be (type), (dsl) or (type, dsl)")
			}
			dsl = dsls[0]
		}
		if dsl != nil {
			dslengine.Execute(dsl, att)
		}
		rn := camelize(a.Parent.Name)
		an := camelize(a.Name)
		a.Payload = &design.UserTypeDefinition{
			AttributeDefinition: att,
			TypeName:            fmt.Sprintf("%s%sPayload", an, rn),
		}
		a.PayloadOptional = isOptional
	}
}
Beispiel #9
0
func attToObject(name string, parent, att *design.AttributeDefinition) *ObjectType {
	obj := &ObjectType{}
	obj.Label = name
	obj.Name = codegen.Goify(name, false)
	obj.Type = codegen.GoTypeRef(att.Type, nil, 0, false)
	if att.Type.IsPrimitive() && parent.IsPrimitivePointer(name) {
		obj.Pointer = "*"
	}
	return obj
}
Beispiel #10
0
// Required adds a "required" validation to the attribute.
// See http://json-schema.org/latest/json-schema-validation.html#anchor61.
func Required(names ...string) {
	var at *design.AttributeDefinition
	if a, ok := attributeDefinition(false); ok {
		at = a
	} else if mt, ok := mediaTypeDefinition(true); ok {
		at = mt.AttributeDefinition
	} else {
		return
	}
	if at.Type != nil && at.Type.Kind() != design.ObjectKind {
		incompatibleAttributeType("required", at.Type.Name(), "an object")
	} else {
		at.Validations = append(at.Validations, &dslengine.RequiredValidationDefinition{Names: names})
	}
}
Beispiel #11
0
// RecursiveChecker produces Go code that runs the validation checks recursively over the given
// attribute.
func RecursiveChecker(att *design.AttributeDefinition, nonzero, required bool, target, context string, depth int) string {
	var checks []string
	validation := ValidationChecker(att, nonzero, required, target, context, depth)
	if validation != "" {
		checks = append(checks, validation)
	}
	if o := att.Type.ToObject(); o != nil {
		if mt, ok := att.Type.(*design.MediaTypeDefinition); ok {
			att = mt.AttributeDefinition
		} else if ut, ok := att.Type.(*design.UserTypeDefinition); ok {
			att = ut.AttributeDefinition
		}
		o.IterateAttributes(func(n string, catt *design.AttributeDefinition) error {
			actualDepth := depth
			if catt.Type.IsObject() {
				actualDepth = depth + 1
			}
			validation := RecursiveChecker(
				catt,
				att.IsNonZero(n),
				att.IsRequired(n),
				fmt.Sprintf("%s.%s", target, Goify(n, true)),
				fmt.Sprintf("%s.%s", context, n),
				actualDepth,
			)
			if validation != "" {
				if catt.Type.IsObject() {
					validation = fmt.Sprintf("%sif %s.%s != nil {\n%s\n%s}",
						Tabs(depth), target, Goify(n, true), validation, Tabs(depth))
				}
				checks = append(checks, validation)
			}
			return nil
		})
	} else if a := att.Type.ToArray(); a != nil {
		data := map[string]interface{}{
			"elemType": a.ElemType,
			"context":  context,
			"target":   target,
			"depth":    1,
		}
		validation := RunTemplate(arrayValT, data)
		if validation != "" {
			checks = append(checks, validation)
		}
	}
	return strings.Join(checks, "\n")
}
Beispiel #12
0
func addAttributeToModel(name string, att *design.AttributeDefinition, m *RelationalModelDefinition) {
	var parent *design.AttributeDefinition
	parent = m.AttributeDefinition
	if parent != nil {
		if parent.Type == nil {
			parent.Type = design.Object{}
		}
		if _, ok := parent.Type.(design.Object); !ok {
			dslengine.ReportError("can't define child attributes on attribute of type %s", parent.Type.Name())
			return
		}

		parent.Type.(design.Object)[name] = att
	}

}
Beispiel #13
0
// Payload implements the action payload DSL. An action payload describes the HTTP request body
// data structure. The function accepts either a type or a DSL that describes the payload members
// using the Member DSL which accepts the same syntax as the Attribute DSL. This function can be
// called passing in a type, a DSL or both. Examples:
//
//	Payload(BottlePayload)		// Request payload is described by the BottlePayload type
//
//	Payload(func() {		// Request payload is an object and is described inline
//		Member("Name")
//	})
//
//	Payload(BottlePayload, func() {	// Request payload is described by merging the inline
//		Required("Name")	// definition into the BottlePayload type.
//	})
//
func Payload(p interface{}, dsls ...func()) {
	if len(dsls) > 1 {
		ReportError("too many arguments given to Payload")
		return
	}
	if a, ok := actionDefinition(true); ok {
		var att *design.AttributeDefinition
		var dsl func()
		switch actual := p.(type) {
		case func():
			dsl = actual
			att = newAttribute(a.Parent.MediaType)
			att.Type = design.Object{}
		case *design.AttributeDefinition:
			att = actual.Dup()
		case design.DataStructure:
			att = actual.Definition().Dup()
		case string:
			ut, ok := design.Design.Types[actual]
			if !ok {
				ReportError("unknown payload type %s", actual)
			}
			att = ut.AttributeDefinition.Dup()
		case *design.Array:
			att = &design.AttributeDefinition{Type: actual}
		case *design.Hash:
			att = &design.AttributeDefinition{Type: actual}
		case design.Primitive:
			att = &design.AttributeDefinition{Type: actual}
		}
		if len(dsls) == 1 {
			if dsl != nil {
				ReportError("invalid arguments in Payload call, must be (type), (dsl) or (type, dsl)")
			}
			dsl = dsls[0]
		}
		if dsl != nil {
			ExecuteDSL(dsl, att)
		}
		rn := inflect.Camelize(a.Parent.Name)
		an := inflect.Camelize(a.Name)
		a.Payload = &design.UserTypeDefinition{
			AttributeDefinition: att,
			TypeName:            fmt.Sprintf("%s%sPayload", an, rn),
		}
	}
}
Beispiel #14
0
// BaseParams defines the API base path parameters. These parameters may correspond to wildcards in
// the BasePath or URL query string values.
// The DSL for describing each Param is the Attribute DSL.
func BaseParams(dsl func()) {
	params := new(design.AttributeDefinition)
	if !dslengine.Execute(dsl, params) {
		return
	}
	params.NonZeroAttributes = make(map[string]bool)
	for n := range params.Type.ToObject() {
		params.NonZeroAttributes[n] = true
	}
	if a, ok := apiDefinition(false); ok {
		a.BaseParams = params
	} else if v, ok := versionDefinition(false); ok {
		v.BaseParams = params
	} else if r, ok := resourceDefinition(true); ok {
		r.BaseParams = params
	}
}
Beispiel #15
0
// BaseParams defines the API base path parameters. These parameters may correspond to wildcards in
// the BasePath or URL query string values.
// The DSL for describing each Param is the Attribute DSL.
func BaseParams(dsl func()) {
	params := new(design.AttributeDefinition)
	if !dslengine.Execute(dsl, params) {
		return
	}
	params.NonZeroAttributes = make(map[string]bool)
	for n := range params.Type.ToObject() {
		params.NonZeroAttributes[n] = true
	}

	switch def := dslengine.CurrentDefinition().(type) {
	case *design.APIDefinition:
		def.BaseParams = params
	case *design.ResourceDefinition:
		def.BaseParams = params
	default:
		dslengine.IncompatibleDSL()
	}
}
Beispiel #16
0
func paramsFromDefinition(params *design.AttributeDefinition, path string) ([]*Parameter, error) {
	if params == nil {
		return nil, nil
	}
	obj := params.Type.ToObject()
	if obj == nil {
		return nil, fmt.Errorf("invalid parameters definition, not an object")
	}
	res := make([]*Parameter, len(obj))
	i := 0
	wildcards := design.ExtractWildcards(path)
	obj.IterateAttributes(func(n string, at *design.AttributeDefinition) error {
		in := "query"
		required := params.IsRequired(n)
		for _, w := range wildcards {
			if n == w {
				in = "path"
				required = true
				break
			}
		}
		param := &Parameter{
			Name:        n,
			Default:     at.DefaultValue,
			Description: at.Description,
			Required:    required,
			In:          in,
			Type:        at.Type.Name(),
		}
		var items *Items
		if at.Type.IsArray() {
			items = itemsFromDefinition(at)
		}
		param.Items = items
		initValidations(at, param)
		res[i] = param
		i++
		return nil
	})
	return res, nil
}
Beispiel #17
0
// Required adds a "required" validation to the attribute.
// See http://json-schema.org/latest/json-schema-validation.html#anchor61.
func Required(names ...string) {
	var at *design.AttributeDefinition

	switch def := dslengine.CurrentDefinition().(type) {
	case *design.AttributeDefinition:
		at = def
	case *design.MediaTypeDefinition:
		at = def.AttributeDefinition
	default:
		dslengine.IncompatibleDSL()
	}

	if at.Type != nil && at.Type.Kind() != design.ObjectKind {
		incompatibleAttributeType("required", at.Type.Name(), "an object")
	} else {
		if at.Validation == nil {
			at.Validation = &dslengine.ValidationDefinition{}
		}
		at.Validation.AddRequired(names)
	}
}
Beispiel #18
0
// Attribute implements the attribute definition DSL. An attribute describes a data structure
// recursively. Attributes are used for describing request headers, parameters and payloads -
// response bodies and headers - media types	 and types. An attribute definition is recursive:
// attributes may include other attributes. At the basic level an attribute has a name,
// a type and optionally a default value and validation rules. The type of an attribute can be one of:
//
// * The primitive types Boolean, Integer, Number or String.
//
// * A type defined via the Type function.
//
// * A media type defined via the MediaType function.
//
// * An object described recursively with child attributes.
//
// * An array defined using the ArrayOf function.
//
// * An hashmap defined using the HashOf function.
//
// Attributes can be defined using the Attribute, Param, Member or Header functions depending
// on where the definition appears. The syntax for all these DSL is the same.
// Here are some examples:
//
//	Attribute("name")					// Defines an attribute of type String
//
//	Attribute("name", func() {
//		Pattern("^foo")					// Adds a validation rule to the attribute
//	})
//
//	Attribute("name", Integer)				// Defines an attribute of type Integer
//
//	Attribute("name", Integer, func() {
//		Default(42)					// With a default value
//	})
//
//	Attribute("name", Integer, "description")		// Specifies a description
//
//	Attribute("name", Integer, "description", func() {
//		Enum(1, 2)					// And validation rules
//	})
//
// Nested attributes:
//
//	Attribute("nested", func() {
//		Description("description")
//		Attribute("child")
//		Attribute("child2", func() {
//			// ....
//		})
//		Required("child")
//	})
//
// Here are all the valid usage of the Attribute function:
//
//	Attribute(name string, dataType DataType, description string, dsl func())
//
//	Attribute(name string, dataType DataType, description string)
//
//	Attribute(name string, dataType DataType, dsl func())
//
//	Attribute(name string, dataType DataType)
//
//	Attribute(name string, dsl func())	// dataType is String or Object (if DSL defines child attributes)
//
//	Attribute(name string)			// dataType is String
func Attribute(name string, args ...interface{}) {
	var parent *design.AttributeDefinition

	switch def := dslengine.CurrentDefinition().(type) {
	case *design.AttributeDefinition:
		parent = def
	case *design.MediaTypeDefinition:
		parent = def.AttributeDefinition
	case design.ContainerDefinition:
		parent = def.Attribute()
	default:
		dslengine.IncompatibleDSL()
	}

	if parent != nil {
		if parent.Type == nil {
			parent.Type = design.Object{}
		}
		if _, ok := parent.Type.(design.Object); !ok {
			dslengine.ReportError("can't define child attributes on attribute of type %s", parent.Type.Name())
			return
		}

		var baseAttr *design.AttributeDefinition
		if parent.Reference != nil {
			if att, ok := parent.Reference.ToObject()[name]; ok {
				baseAttr = design.DupAtt(att)
			}
		}

		dataType, description, dsl := parseAttributeArgs(baseAttr, args...)
		if baseAttr != nil {
			if description != "" {
				baseAttr.Description = description
			}
			if dataType != nil {
				baseAttr.Type = dataType
			}
		} else {
			baseAttr = &design.AttributeDefinition{
				Type:        dataType,
				Description: description,
			}
		}
		baseAttr.Reference = parent.Reference
		if dsl != nil {
			dslengine.Execute(dsl, baseAttr)
		}
		if baseAttr.Type == nil {
			// DSL did not contain an "Attribute" declaration
			baseAttr.Type = design.String
		}
		parent.Type.(design.Object)[name] = baseAttr
	}
}
Beispiel #19
0
// goTypeDefObject returns the Go code that defines a Go struct.
func goTypeDefObject(actual design.Object, def *design.AttributeDefinition, tabs int, jsonTags, private bool) string {
	var buffer bytes.Buffer
	buffer.WriteString("struct {\n")
	keys := make([]string, len(actual))
	i := 0
	for n := range actual {
		keys[i] = n
		i++
	}
	sort.Strings(keys)
	for _, name := range keys {
		WriteTabs(&buffer, tabs+1)
		field := actual[name]
		typedef := GoTypeDef(field, tabs+1, jsonTags, private)
		if (field.Type.IsPrimitive() && private) || field.Type.IsObject() || def.IsPrimitivePointer(name) {
			typedef = "*" + typedef
		}
		fname := name
		if field.Metadata != nil {
			if tname, ok := field.Metadata["struct:field:name"]; ok {
				if len(tname) > 0 {
					fname = tname[0]
				}
			}
		}
		fname = Goify(fname, true)
		var tags string
		if jsonTags {
			tags = attributeTags(def, field, name, private)
		}
		desc := actual[name].Description
		if desc != "" {
			desc = fmt.Sprintf("// %s\n\t", desc)
		}
		buffer.WriteString(fmt.Sprintf("%s%s %s%s\n", desc, fname, typedef, tags))
	}
	WriteTabs(&buffer, tabs)
	buffer.WriteString("}")
	return buffer.String()
}
Beispiel #20
0
// RecursivePublicizer produces code that copies fields from the private struct to the
// public struct
func RecursivePublicizer(att *design.AttributeDefinition, source, target string, depth int) string {
	var publications []string
	if o := att.Type.ToObject(); o != nil {
		if ds, ok := att.Type.(design.DataStructure); ok {
			att = ds.Definition()
		}
		o.IterateAttributes(func(n string, catt *design.AttributeDefinition) error {
			publication := Publicizer(
				catt,
				fmt.Sprintf("%s.%s", source, Goify(n, true)),
				fmt.Sprintf("%s.%s", target, Goify(n, true)),
				catt.Type.IsPrimitive() && !att.IsPrimitivePointer(n),
				depth+1,
				false,
			)
			publication = fmt.Sprintf("%sif %s.%s != nil {\n%s\n%s}",
				Tabs(depth), source, Goify(n, true), publication, Tabs(depth))
			publications = append(publications, publication)
			return nil
		})
	}
	return strings.Join(publications, "\n")
}
Beispiel #21
0
// buildAttributeSchema initializes the given JSON schema that corresponds to the given attribute.
func buildAttributeSchema(api *design.APIDefinition, s *JSONSchema, at *design.AttributeDefinition) *JSONSchema {
	if at.View != "" {
		inner := NewJSONSchema()
		inner.Ref = MediaTypeRef(api, at.Type.(*design.MediaTypeDefinition), at.View)
		s.Merge(inner)
		return s
	}
	s.Merge(TypeSchema(api, at.Type))
	if s.Ref != "" {
		// Ref is exclusive with other fields
		return s
	}
	s.DefaultValue = toStringMap(at.DefaultValue)
	s.Description = at.Description
	s.Example = at.GenerateExample(api.RandomGenerator(), nil)
	val := at.Validation
	if val == nil {
		return s
	}
	s.Enum = val.Values
	s.Format = val.Format
	s.Pattern = val.Pattern
	if val.Minimum != nil {
		s.Minimum = val.Minimum
	}
	if val.Maximum != nil {
		s.Maximum = val.Maximum
	}
	if val.MinLength != nil {
		s.MinLength = val.MinLength
	}
	if val.MaxLength != nil {
		s.MaxLength = val.MaxLength
	}
	s.Required = val.Required
	return s
}
Beispiel #22
0
// Attribute implements the attribute definition DSL. An attribute describes a data structure
// recursively. Attributes are used for describing request headers, parameters and payloads -
// response bodies and headers - media types and types. An attribute definition is recursive:
// attributes may include other attributes. At the basic level an attribute has a name,
// a type and optionally a default value and validation rules. The type of an attribute can be one of:
//
// * The primitive types Boolean, Integer, Number or String.
//
// * A type defined via the Type function.
//
// * A media type defined via the MediaType function.
//
// * An object described recursively with child attributes.
//
// * An array defined using the ArrayOf function.
//
// * An hashmap defined using the HashOf function.
//
// Attributes can be defined using the Attribute, Param, Member or Header functions depending
// on where the definition appears. The syntax for all these DSL is the same.
// Here are some examples:
//
//	Attribute("name")					// Defines an attribute of type String
//
//	Attribute("name", func() {
//		Pattern("^foo")					// Adds a validation rule to the attribute
//	})
//
//	Attribute("name", Integer)				// Defines an attribute of type Integer
//
//	Attribute("name", Integer, func() {
//		Default(42)					// With a default value
//	})
//
//	Attribute("name", Integer, "description")		// Specifies a description
//
//	Attribute("name", Integer, "description", func() {
//		Enum(1, 2)					// And validation rules
//	})
//
// Nested attributes:
//
//	Attribute("nested", func() {
//		Description("description")
//		Attribute("child")
//		Attribute("child2", func() {
//			// ....
//		})
//		Required("child")
//	})
//
// Here are all the valid usage of the Attribute function:
//
//	Attribute(name string, dataType DataType, description string, dsl func())
//
//	Attribute(name string, dataType DataType, description string)
//
//	Attribute(name string, dataType DataType, dsl func())
//
//	Attribute(name string, dataType DataType)
//
//	Attribute(name string, dsl func())	// dataType is String or Object (if DSL defines child attributes)
//
//	Attribute(name string)			// dataType is String
func Attribute(name string, args ...interface{}) {
	var parent *design.AttributeDefinition
	if at, ok := attributeDefinition(false); ok {
		parent = at
	} else if mt, ok := mediaTypeDefinition(true); ok {
		parent = mt.AttributeDefinition
	}

	if parent != nil {
		if parent.Type == nil {
			parent.Type = design.Object{}
		}
		if _, ok := parent.Type.(design.Object); !ok {
			dslengine.ReportError("can't define child attributes on attribute of type %s", parent.Type.Name())
			return
		}

		var baseAttr *design.AttributeDefinition
		if parent.Reference != nil {
			if att, ok := parent.Reference.ToObject()[name]; ok {
				baseAttr = design.DupAtt(att)
			}
		}

		dataType, description, dsl := parseAttributeArgs(baseAttr, args...)
		if baseAttr != nil {
			if description != "" {
				baseAttr.Description = description
			}
			if dataType != nil {
				baseAttr.Type = dataType
			}
		} else {
			baseAttr = &design.AttributeDefinition{
				Type:        dataType,
				Description: description,
			}
		}
		baseAttr.Reference = parent.Reference
		if dsl != nil {
			dslengine.Execute(dsl, baseAttr)
		}
		if baseAttr.Type == nil {
			// DSL did not contain an "Attribute" declaration
			baseAttr.Type = design.String
		}
		parent.Type.(design.Object)[name] = baseAttr
	}
}
Beispiel #23
0
func (v *Validator) recurseAttribute(att, catt *design.AttributeDefinition, n, target, context string, depth int, private bool) string {
	var validation string
	if ds, ok := catt.Type.(design.DataStructure); ok {
		// We need to check empirically whether there are validations to be
		// generated, we can't just generate and check whether something was
		// generated to avoid infinite recursions.
		hasValidations := false
		done := errors.New("done")
		ds.Walk(func(a *design.AttributeDefinition) error {
			if a.Validation != nil {
				if private {
					hasValidations = true
					return done
				}
				// For public data structures there is a case where
				// there is validation but no actual validation
				// code: if the validation is a required validation
				// that applies to attributes that cannot be nil or
				// empty string i.e. primitive types other than
				// string.
				if !a.Validation.HasRequiredOnly() {
					hasValidations = true
					return done
				}
				for _, name := range a.Validation.Required {
					att := a.Type.ToObject()[name]
					if att != nil && (!att.Type.IsPrimitive() || att.Type.Kind() == design.StringKind) {
						hasValidations = true
						return done
					}
				}
			}
			return nil
		})
		if hasValidations {
			validation = RunTemplate(v.userValT, map[string]interface{}{
				"depth":  depth,
				"target": fmt.Sprintf("%s.%s", target, GoifyAtt(catt, n, true)),
			})
		}
	} else {
		dp := depth
		if catt.Type.IsObject() {
			dp++
		}
		validation = v.recurse(
			catt,
			att.IsNonZero(n),
			att.IsRequired(n),
			att.HasDefaultValue(n),
			fmt.Sprintf("%s.%s", target, GoifyAtt(catt, n, true)),
			fmt.Sprintf("%s.%s", context, n),
			dp,
			private,
		).String()
	}
	if validation != "" {
		if catt.Type.IsObject() {
			validation = fmt.Sprintf("%sif %s.%s != nil {\n%s\n%s}",
				Tabs(depth), target, GoifyAtt(catt, n, true), validation, Tabs(depth))
		}
	}
	return validation
}
Beispiel #24
0
package design_test

import (
	"github.com/goadesign/goa/design"
	"github.com/goadesign/goa/dslengine"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("IsRequired", func() {
	var required string
	var attName string

	var attribute *design.AttributeDefinition
	var res bool

	JustBeforeEach(func() {
		integer := &design.AttributeDefinition{Type: design.Integer}
		attribute = &design.AttributeDefinition{
			Type: design.Object{required: integer},
			Validations: []dslengine.ValidationDefinition{
				&dslengine.RequiredValidationDefinition{Names: []string{required}},
			},
		}
		res = attribute.IsRequired(attName)
	})

	Context("called on a required field", func() {
		BeforeEach(func() {
			attName = "required"
			required = "required"
Beispiel #25
0
	"github.com/goadesign/goa/goagen/codegen"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("validation code generation", func() {
	BeforeEach(func() {
		codegen.TempCount = 0
	})

	Describe("ValidationChecker", func() {
		Context("given an attribute definition and validations", func() {
			var attType design.DataType
			var validation *dslengine.ValidationDefinition

			att := new(design.AttributeDefinition)
			target := "val"
			context := "context"
			var code string // generated code

			JustBeforeEach(func() {
				att.Type = attType
				att.Validation = validation
				code = codegen.RecursiveChecker(att, false, false, target, context, 1)
			})

			Context("of enum", func() {
				BeforeEach(func() {
					attType = design.Integer
					validation = &dslengine.ValidationDefinition{
						Values: []interface{}{1, 2, 3},
Beispiel #26
0
	"github.com/goadesign/goa/goagen/codegen"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("validation code generation", func() {
	BeforeEach(func() {
		codegen.TempCount = 0
	})

	Describe("ValidationChecker", func() {
		Context("given an attribute definition and validations", func() {
			var attType design.DataType
			var validation *dslengine.ValidationDefinition

			att := new(design.AttributeDefinition)
			target := "val"
			context := "context"
			var code string // generated code

			JustBeforeEach(func() {
				att.Type = attType
				att.Validation = validation
				code = codegen.NewValidator().Code(att, false, false, false, target, context, 1, false)
			})

			Context("of enum", func() {
				BeforeEach(func() {
					attType = design.Integer
					validation = &dslengine.ValidationDefinition{
						Values: []interface{}{1, 2, 3},
Beispiel #27
0
func (f *Finalizer) recurse(att *design.AttributeDefinition, target string, depth int) *bytes.Buffer {
	var (
		buf   = new(bytes.Buffer)
		first = true
	)

	// Break infinite recursions
	switch dt := att.Type.(type) {
	case *design.MediaTypeDefinition:
		if buf, ok := f.seen[dt.TypeName]; ok {
			return buf
		}
		f.seen[dt.TypeName] = buf
		att = dt.AttributeDefinition
	case *design.UserTypeDefinition:
		if buf, ok := f.seen[dt.TypeName]; ok {
			return buf
		}
		f.seen[dt.TypeName] = buf
		att = dt.AttributeDefinition
	}

	if o := att.Type.ToObject(); o != nil {
		o.IterateAttributes(func(n string, catt *design.AttributeDefinition) error {
			if att.HasDefaultValue(n) {
				data := map[string]interface{}{
					"target":     target,
					"field":      n,
					"catt":       catt,
					"depth":      depth,
					"isDatetime": catt.Type == design.DateTime,
					"defaultVal": printVal(catt.Type, catt.DefaultValue),
				}
				if !first {
					buf.WriteByte('\n')
				} else {
					first = false
				}
				buf.WriteString(RunTemplate(f.assignmentT, data))
			}
			a := f.recurse(catt, fmt.Sprintf("%s.%s", target, Goify(n, true)), depth+1).String()
			if a != "" {
				if catt.Type.IsObject() {
					a = fmt.Sprintf("%sif %s.%s != nil {\n%s\n%s}",
						Tabs(depth), target, Goify(n, true), a, Tabs(depth))
				}
				if !first {
					buf.WriteByte('\n')
				} else {
					first = false
				}
				buf.WriteString(a)
			}
			return nil
		})
	} else if a := att.Type.ToArray(); a != nil {
		data := map[string]interface{}{
			"elemType": a.ElemType,
			"target":   target,
			"depth":    1,
		}
		if as := RunTemplate(f.arrayAssignmentT, data); as != "" {
			buf.WriteString(as)
		}
	}
	return buf
}
Beispiel #28
0
// RecursiveChecker produces Go code that runs the validation checks recursively over the given
// attribute.
func RecursiveChecker(att *design.AttributeDefinition, nonzero, required, hasDefault bool, target, context string, depth int, private bool) string {
	var checks []string
	if o := att.Type.ToObject(); o != nil {
		if ds, ok := att.Type.(design.DataStructure); ok {
			att = ds.Definition()
		}
		validation := ValidationChecker(att, nonzero, required, hasDefault, target, context, depth, private)
		if validation != "" {
			checks = append(checks, validation)
		}
		o.IterateAttributes(func(n string, catt *design.AttributeDefinition) error {
			var validation string
			if ds, ok := catt.Type.(design.DataStructure); ok {
				// We need to check empirically whether there are validations to be
				// generated, we can't just generate and check whether something was
				// generated to avoid infinite recursions.
				hasValidations := false
				done := errors.New("done")
				ds.Walk(func(a *design.AttributeDefinition) error {
					if a.Validation != nil {
						if private {
							hasValidations = true
							return done
						}
						// For public data structures there is a case where
						// there is validation but no actual validation
						// code: if the validation is a required validation
						// that applies to attributes that cannot be nil or
						// empty string i.e. primitive types other than
						// string.
						if !a.Validation.HasRequiredOnly() {
							hasValidations = true
							return done
						}
						for _, name := range a.Validation.Required {
							att := a.Type.ToObject()[name]
							if att != nil && (!att.Type.IsPrimitive() || att.Type.Kind() == design.StringKind) {
								hasValidations = true
								return done
							}
						}
					}
					return nil
				})
				if hasValidations {
					validation = RunTemplate(
						userValT,
						map[string]interface{}{
							"depth":  depth,
							"target": fmt.Sprintf("%s.%s", target, GoifyAtt(catt, n, true)),
						},
					)
				}
			} else {
				dp := depth
				if catt.Type.IsObject() {
					dp++
				}
				validation = RecursiveChecker(
					catt,
					att.IsNonZero(n),
					att.IsRequired(n),
					att.HasDefaultValue(n),
					fmt.Sprintf("%s.%s", target, GoifyAtt(catt, n, true)),
					fmt.Sprintf("%s.%s", context, n),
					dp,
					private,
				)
			}
			if validation != "" {
				if catt.Type.IsObject() {
					validation = fmt.Sprintf("%sif %s.%s != nil {\n%s\n%s}",
						Tabs(depth), target, GoifyAtt(catt, n, true), validation, Tabs(depth))
				}
				checks = append(checks, validation)
			}
			return nil
		})
	} else if a := att.Type.ToArray(); a != nil {
		// Perform any validation on the array type such as MinLength, MaxLength, etc.
		validation := ValidationChecker(att, nonzero, required, hasDefault, target, context, depth, private)
		if validation != "" {
			checks = append(checks, validation)
		}
		data := map[string]interface{}{
			"elemType": a.ElemType,
			"context":  context,
			"target":   target,
			"depth":    1,
			"private":  private,
		}
		validation = RunTemplate(arrayValT, data)
		if validation != "" {
			checks = append(checks, validation)
		}
	} else {
		validation := ValidationChecker(att, nonzero, required, hasDefault, target, context, depth, private)
		if validation != "" {
			checks = append(checks, validation)
		}
	}
	return strings.Join(checks, "\n")
}