// 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 } }
// 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 } }
// 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 } }
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 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), } } }
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}, } }) It("produces the validation go code", func() { Ω(code).Should(Equal(enumValCode)) })