// 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 } }
// 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") }
// 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} }
// 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) }
// 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() }
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 }
// 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") }
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 } }
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 }
// 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}) } }
// 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") }
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 } }
// 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 !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 } }
// 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() } }
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 }
// 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) } }
// 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 } }
// 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() }
// 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") }
// 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 }
// 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 } }
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 }
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"
"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},
"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},
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 }
// 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") }