// attributeMarshalerR is the recursive implementation of AttributeMarshaler. func attributeMarshalerR(att *design.AttributeDefinition, context, source, target string, depth int) string { var marshaler string switch actual := att.Type.(type) { case *design.MediaTypeDefinition: marshaler = mediaTypeMarshalerR(actual, source, target, att.View, depth) case design.Object: marshaler = objectMarshalerR(actual, att.AllRequired(), context, source, target, depth) default: marshaler = typeMarshalerR(att.Type, context, source, target, depth) } validation := ValidationChecker(att, false, source, context, 1) if validation != "" { if !strings.HasPrefix(strings.TrimLeft(" \t\n", marshaler), "if err == nil {") { return fmt.Sprintf( "%s\n%sif err == nil {\n%s\n%s}", validation, Tabs(depth), marshaler, Tabs(depth), ) } return validation + marshaler } return marshaler }
// RecursiveChecker produces Go code that runs the validation checks recursively over the given // attribute. func RecursiveChecker(att *design.AttributeDefinition, required bool, target, context string, depth int) string { var checks []string validation := ValidationChecker(att, required, target, context, depth) if validation != "" { checks = append(checks, validation) } if o := att.Type.ToObject(); o != nil { o.IterateAttributes(func(n string, catt *design.AttributeDefinition) error { validation := RecursiveChecker( catt, att.IsRequired(n), fmt.Sprintf("%s.%s", target, Goify(n, true)), fmt.Sprintf("%s.%s", context, n), depth+1, ) if validation != "" { checks = append(checks, validation) } return nil }) } else if a := att.Type.ToArray(); a != nil { data := map[string]interface{}{ "attribute": att, "context": context, "target": target, "depth": 1, } validation := runTemplate(arrayValT, data) if validation != "" { checks = append(checks, validation) } } return strings.Join(checks, "\n") }
// 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) // }) 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} }
// 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, &design.RequiredValidationDefinition{Names: names}) } }
// 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), } } }
// 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 !ExecuteDSL(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 } }
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 }
// 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 { 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 = att.Dup() } } 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 { executeDSL(dsl, baseAttr) } if baseAttr.Type == nil { // DSL did not contain an "Attribute" declaration baseAttr.Type = design.String } parent.Type.(design.Object)[name] = baseAttr } }
. "github.com/onsi/gomega" "github.com/raphael/goa/design" "github.com/raphael/goa/goagen/codegen" ) 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 validations []design.ValidationDefinition att := new(design.AttributeDefinition) target := "val" context := "context" var code string // generated code JustBeforeEach(func() { att.Type = attType att.Validations = validations code = codegen.ValidationChecker(att, false, false, target, context, 1) }) Context("of enum", func() { BeforeEach(func() { attType = design.Integer enumVal := &design.EnumValidationDefinition{ Values: []interface{}{1, 2, 3},
package design_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/raphael/goa/design" ) 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: []design.ValidationDefinition{ &design.RequiredValidationDefinition{Names: []string{required}}, }, } res = attribute.IsRequired(attName) }) Context("called on a required field", func() { BeforeEach(func() { attName = "required" required = "required" })
package dsl import ( "regexp" "strconv" "strings" "github.com/raphael/goa/design" ) // 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{} } else if _, ok := parent.Type.(design.Object); !ok { ReportError("can't define child attributes on attribute of type %s", parent.Type.Name()) return } var baseAttr *design.AttributeDefinition if parent.Reference != nil { for n, att := range parent.Reference.ToObject() { if n == name { baseAttr = att.Dup() break } } } var dataType design.DataType var description string var dsl func() var ok bool if len(args) == 0 { if baseAttr != nil { dataType = baseAttr.Type } else { dataType = design.String } } else if len(args) == 1 { if dsl, ok = args[0].(func()); !ok { if dataType, ok = args[0].(design.DataType); !ok { invalidArgError("DataType or func()", args[0]) } } else if baseAttr != nil { dataType = baseAttr.Type } } else if len(args) == 2 { if dataType, ok = args[0].(design.DataType); !ok { invalidArgError("DataType", args[0]) } if dsl, ok = args[1].(func()); !ok { if description, ok = args[1].(string); !ok { invalidArgError("string or func()", args[1]) } } } else if len(args) == 3 { if dataType, ok = args[0].(design.DataType); !ok { invalidArgError("DataType", args[0]) } if description, ok = args[1].(string); !ok { invalidArgError("string", args[1]) } if dsl, ok = args[2].(func()); !ok { invalidArgError("func()", args[2]) } } else { ReportError("too many arguments in call to Attribute") } var att *design.AttributeDefinition if baseAttr != nil { att = baseAttr if description != "" { att.Description = description } if dataType != nil { att.Type = dataType } } else { att = &design.AttributeDefinition{ Type: dataType, Description: description, } } att.Reference = parent.Reference if dsl != nil { executeDSL(dsl, att) } if att.Type == nil { // DSL did not contain an "Attribute" declaration att.Type = design.String } parent.Type.(design.Object)[name] = att } }