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 } }
// buildView builds a view definition given an attribute and a corresponding media type. func buildView(name string, mt *design.MediaTypeDefinition, at *design.AttributeDefinition) (*design.ViewDefinition, error) { if at.Type == nil || !at.Type.IsObject() { return nil, fmt.Errorf("invalid view DSL") } o := at.Type.ToObject() if o != nil { mto := mt.Type.ToObject() if mto == nil { mto = mt.Type.ToArray().ElemType.Type.ToObject() } for n, cat := range o { if existing, ok := mto[n]; ok { dup := design.DupAtt(existing) dup.View = cat.View o[n] = dup } else if n != "links" { return nil, fmt.Errorf("unknown attribute %#v", n) } } } return &design.ViewDefinition{ AttributeDefinition: at, Name: name, Parent: mt, }, 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 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 } }
// GenerateResourceDefinition produces the JSON schema corresponding to the given API resource. // It stores the results in cachedSchema. func GenerateResourceDefinition(api *design.APIDefinition, r *design.ResourceDefinition) { s := NewJSONSchema() s.Description = r.Description s.Type = JSONObject s.Title = r.Name Definitions[r.Name] = s if mt, ok := api.MediaTypes[r.MediaType]; ok { for _, v := range mt.Views { buildMediaTypeSchema(api, mt, v.Name, s) } } r.IterateActions(func(a *design.ActionDefinition) error { var requestSchema *JSONSchema if a.Payload != nil { requestSchema = TypeSchema(api, a.Payload) requestSchema.Description = a.Name + " payload" } if a.Params != nil { params := design.DupAtt(a.Params) // We don't want to keep the path params, these are defined inline in the href for _, r := range a.Routes { for _, p := range r.Params() { delete(params.Type.ToObject(), p) } } } var targetSchema *JSONSchema var identifier string for _, resp := range a.Responses { if mt, ok := api.MediaTypes[resp.MediaType]; ok { if identifier == "" { identifier = mt.Identifier } else { identifier = "" } if targetSchema == nil { targetSchema = TypeSchema(api, mt) } else if targetSchema.AnyOf == nil { firstSchema := targetSchema targetSchema = NewJSONSchema() targetSchema.AnyOf = []*JSONSchema{firstSchema, TypeSchema(api, mt)} } else { targetSchema.AnyOf = append(targetSchema.AnyOf, TypeSchema(api, mt)) } } } for i, r := range a.Routes { link := JSONLink{ Title: a.Name, Rel: a.Name, Href: toSchemaHref(api, r), Method: r.Verb, Schema: requestSchema, TargetSchema: targetSchema, MediaType: identifier, } if i == 0 { if ca := a.Parent.CanonicalAction(); ca != nil { if ca.Name == a.Name { link.Rel = "self" } } } s.Links = append(s.Links, &link) } return nil }) }
// View adds a new view to a media type. A view has a name and lists attributes that are // rendered when the view is used to produce a response. The attribute names must appear in the // media type definition. If an attribute is itself a media type then the view may specify which // view to use when rendering the attribute using the View function in the View apidsl. If not // specified then the view named "default" is used. Examples: // // View("default", func() { // Attribute("id") // "id" and "name" must be media type attributes // Attribute("name") // }) // // View("extended", func() { // Attribute("id") // Attribute("name") // Attribute("origin", func() { // View("extended") // Use view "extended" to render attribute "origin" // }) // }) func View(name string, apidsl ...func()) { if mt, ok := mediaTypeDefinition(false); ok { if !mt.Type.IsObject() && !mt.Type.IsArray() { dslengine.ReportError("cannot define view on non object and non collection media types") return } if mt.Views == nil { mt.Views = make(map[string]*design.ViewDefinition) } else { if _, ok = mt.Views[name]; ok { dslengine.ReportError("multiple definitions for view %#v in media type %#v", name, mt.TypeName) return } } at := &design.AttributeDefinition{} ok := false if len(apidsl) > 0 { ok = dslengine.Execute(apidsl[0], at) } else if mt.Type.IsArray() { // inherit view from collection element if present elem := mt.Type.ToArray().ElemType if elem != nil { if pa, ok2 := elem.Type.(*design.MediaTypeDefinition); ok2 { if v, ok2 := pa.Views[name]; ok2 { at = v.AttributeDefinition ok = true } else { dslengine.ReportError("unknown view %#v", name) return } } } } if ok { if at.Type == nil || !at.Type.IsObject() { dslengine.ReportError("invalid view DSL") return } o := at.Type.ToObject() if o != nil { mto := mt.Type.ToObject() if mto == nil { mto = mt.Type.ToArray().ElemType.Type.ToObject() } for n, cat := range o { if existing, ok := mto[n]; ok { dup := design.DupAtt(existing) dup.View = cat.View o[n] = dup } else if n != "links" { dslengine.ReportError("unknown attribute %#v", n) } } } mt.Views[name] = &design.ViewDefinition{ AttributeDefinition: at, Name: name, Parent: mt, } } } else if a, ok := attributeDefinition(true); ok { a.View = name } }