// Headers implements the DSL for describing HTTP headers. The DSL syntax is identical to the one // of Attribute. Here is an example defining a couple of headers with validations: // // Headers(func() { // Header("Authorization") // Header("X-Account", Integer, func() { // Minimum(1) // }) // Required("Authorization") // }) // // Headers can be used inside Action to define the action request headers, Response to define the // response headers or Resource to define common request headers to all the resource actions. func Headers(dsl func()) { if a, ok := actionDefinition(false); ok { headers := newAttribute(a.Parent.MediaType) if dslengine.Execute(dsl, headers) { a.Headers = headers } } else if r, ok := resourceDefinition(false); ok { headers := newAttribute(r.MediaType) if dslengine.Execute(dsl, headers) { r.Headers = headers } } else if r, ok := responseDefinition(true); ok { if r.Headers != nil { dslengine.ReportError("headers already defined") return } var h *design.AttributeDefinition switch actual := r.Parent.(type) { case *design.ResourceDefinition: h = newAttribute(actual.MediaType) case *design.ActionDefinition: h = newAttribute(actual.Parent.MediaType) case nil: // API ResponseTemplate h = &design.AttributeDefinition{} default: dslengine.ReportError("invalid use of Response or ResponseTemplate") } if dslengine.Execute(dsl, h) { r.Headers = h } } }
// Headers implements the DSL for describing HTTP headers. The DSL syntax is identical to the one // of Attribute. Here is an example defining a couple of headers with validations: // // Headers(func() { // Header("Authorization") // Header("X-Account", Integer, func() { // Minimum(1) // }) // Required("Authorization") // }) // // Headers can be used inside Action to define the action request headers, Response to define the // response headers or Resource to define common request headers to all the resource actions. func Headers(params ...interface{}) { if len(params) == 0 { dslengine.ReportError("missing parameter") return } dsl, ok := params[0].(func()) if ok { switch def := dslengine.CurrentDefinition().(type) { case *design.ActionDefinition: headers := newAttribute(def.Parent.MediaType) if dslengine.Execute(dsl, headers) { def.Headers = headers } case *design.ResourceDefinition: headers := newAttribute(def.MediaType) if dslengine.Execute(dsl, headers) { def.Headers = headers } case *design.ResponseDefinition: if def.Headers != nil { dslengine.ReportError("headers already defined") return } var h *design.AttributeDefinition switch actual := def.Parent.(type) { case *design.ResourceDefinition: h = newAttribute(actual.MediaType) case *design.ActionDefinition: h = newAttribute(actual.Parent.MediaType) case nil: // API ResponseTemplate h = &design.AttributeDefinition{} default: dslengine.ReportError("invalid use of Response or ResponseTemplate") } if dslengine.Execute(dsl, h) { def.Headers = h } default: dslengine.IncompatibleDSL() } } else if cors, ok := corsDefinition(); ok { vals := make([]string, len(params)) for i, p := range params { if v, ok := p.(string); ok { vals[i] = v } else { dslengine.ReportError("invalid parameter at position %d: must be a string", i) return } } cors.Headers = vals } else { dslengine.IncompatibleDSL() } }
func setupResponseTemplate(a *design.APIDefinition, name string, p interface{}) { if f, ok := p.(func()); ok { r := &design.ResponseDefinition{Name: name} if dslengine.Execute(f, r) { a.Responses[name] = r } } else if tmpl, ok := p.(func(...string)); ok { t := func(params ...string) *design.ResponseDefinition { r := &design.ResponseDefinition{Name: name} dslengine.Execute(func() { tmpl(params...) }, r) return r } a.ResponseTemplates[name] = &design.ResponseTemplateDefinition{ Name: name, Template: t, } } else { typ := reflect.TypeOf(p) if kind := typ.Kind(); kind != reflect.Func { dslengine.ReportError("dsl must be a function but got %s", kind) return } num := typ.NumIn() val := reflect.ValueOf(p) t := func(params ...string) *design.ResponseDefinition { if len(params) < num { args := "1 argument" if num > 0 { args = fmt.Sprintf("%d arguments", num) } dslengine.ReportError("expected at least %s when invoking response template %s", args, name) return nil } r := &design.ResponseDefinition{Name: name} in := make([]reflect.Value, num) for i := 0; i < num; i++ { // type checking if t := typ.In(i); t.Kind() != reflect.String { dslengine.ReportError("ResponseTemplate parameters must be strings but type of parameter at position %d is %s", i, t) return nil } // append input arguments in[i] = reflect.ValueOf(params[i]) } dslengine.Execute(func() { val.Call(in) }, r) return r } a.ResponseTemplates[name] = &design.ResponseTemplateDefinition{ Name: name, Template: t, } } }
// Params describe the action parameters, either path parameters identified via wildcards or query // string parameters. 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 or Resource to define common // parameters to all the resource actions. func Params(dsl func()) { if a, ok := actionDefinition(false); ok { params := newAttribute(a.Parent.MediaType) if dslengine.Execute(dsl, params) { a.Params = params } } else if r, ok := resourceDefinition(true); ok { params := newAttribute(r.MediaType) if dslengine.Execute(dsl, params) { r.Params = params } } }
// 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 dsl. 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, dsl ...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(dsl) > 0 { ok = dslengine.Execute(dsl[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 { 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 := existing.Dup() 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 } }
func executeResponseDSL(name string, paramsAndDSL ...interface{}) *design.ResponseDefinition { var params []string var dsl func() var ok bool var dt design.DataType if len(paramsAndDSL) > 0 { d := paramsAndDSL[len(paramsAndDSL)-1] if dsl, ok = d.(func()); ok { paramsAndDSL = paramsAndDSL[:len(paramsAndDSL)-1] } if len(paramsAndDSL) > 0 { t := paramsAndDSL[0] if dt, ok = t.(design.DataType); ok { paramsAndDSL = paramsAndDSL[1:] } } params = make([]string, len(paramsAndDSL)) for i, p := range paramsAndDSL { params[i], ok = p.(string) if !ok { dslengine.ReportError("invalid response template parameter %#v, must be a string", p) return nil } } } var resp *design.ResponseDefinition if len(params) > 0 { if tmpl, ok := design.Design.ResponseTemplates[name]; ok { resp = tmpl.Template(params...) } else if tmpl, ok := design.Design.DefaultResponseTemplates[name]; ok { resp = tmpl.Template(params...) } else { dslengine.ReportError("no response template named %#v", name) return nil } } else { if ar, ok := design.Design.Responses[name]; ok { resp = ar.Dup() } else if ar, ok := design.Design.DefaultResponses[name]; ok { resp = ar.Dup() resp.Standard = true } else { resp = &design.ResponseDefinition{Name: name} } } if dsl != nil { if !dslengine.Execute(dsl, resp) { return nil } resp.Standard = false } if dt != nil { if mt, ok := dt.(*design.MediaTypeDefinition); ok { resp.MediaType = mt.Identifier } resp.Type = dt resp.Standard = false } return resp }
func (m *MediaTypeDefinition) projectCollection(view string) (p *MediaTypeDefinition, links *UserTypeDefinition, err error) { e := m.ToArray().ElemType.Type.(*MediaTypeDefinition) // validation checked this cast would work pe, le, err2 := e.Project(view) if err2 != nil { return nil, nil, fmt.Errorf("collection element: %s", err2) } p = &MediaTypeDefinition{ Identifier: m.Identifier, UserTypeDefinition: &UserTypeDefinition{ AttributeDefinition: &AttributeDefinition{ Type: &Array{ElemType: &AttributeDefinition{Type: pe}}, }, TypeName: pe.TypeName + "Collection", }, } if !dslengine.Execute(p.DSL(), p) { return nil, nil, dslengine.Errors } if le != nil { lTypeName := le.TypeName + "Array" links = &UserTypeDefinition{ AttributeDefinition: &AttributeDefinition{ Type: &Array{ElemType: &AttributeDefinition{Type: le}}, Description: fmt.Sprintf("%s contains links to related resources of %s.", lTypeName, m.TypeName), }, TypeName: lTypeName, } } return }
// buildEncodingDefinition builds up an encoding definition. func buildEncodingDefinition(encoding bool, args ...interface{}) *design.EncodingDefinition { var dsl func() var ok bool funcName := "Consumes" if encoding { funcName = "Produces" } if len(args) == 0 { dslengine.ReportError("missing argument in call to %s", funcName) return nil } if _, ok = args[0].(string); !ok { dslengine.ReportError("first argument to %s must be a string (MIME type)", funcName) return nil } last := len(args) if dsl, ok = args[len(args)-1].(func()); ok { last = len(args) - 1 } mimeTypes := make([]string, last) for i := 0; i < last; i++ { var mimeType string if mimeType, ok = args[i].(string); !ok { dslengine.ReportError("argument #%d of %s must be a string (MIME type)", i, funcName) return nil } mimeTypes[i] = mimeType } d := &design.EncodingDefinition{MIMETypes: mimeTypes, Encoder: encoding} if dsl != nil { dslengine.Execute(dsl, d) } return d }
// 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) // }) // // ArrayOf accepts an optional DSL as second argument which allows providing validations for the // elements of the array: // // var Names = ArrayOf(String, func() { // Pattern("[a-zA-Z]+") // }) // // 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(v interface{}, dsl ...func()) *design.Array { var t design.DataType var ok bool t, ok = v.(design.DataType) if !ok { if name, ok := v.(string); ok { t = design.Design.Types[name] } } // never return nil to avoid panics, errors are reported after DSL execution res := &design.Array{ElemType: &design.AttributeDefinition{Type: design.String}} if t == nil { dslengine.ReportError("invalid ArrayOf argument: not a type and not a known user type name") return res } if len(dsl) > 1 { dslengine.ReportError("ArrayOf: too many arguments") return res } at := design.AttributeDefinition{Type: t} if len(dsl) == 1 { dslengine.Execute(dsl[0], &at) } return &design.Array{ElemType: &at} }
// Origin defines the CORS policy for a given origin. The origin can use a wildcard prefix // such as "https://*.mydomain.com". The special value "*" defines the policy for all origins // (in which case there should be only one Origin DSL in the parent resource). // The origin can also be a regular expression wrapped into "/". // Example: // // Origin("http://swagger.goa.design", func() { // Define CORS policy, may be prefixed with "*" wildcard // Headers("X-Shared-Secret") // One or more authorized headers, use "*" to authorize all // Methods("GET", "POST") // One or more authorized HTTP methods // Expose("X-Time") // One or more headers exposed to clients // MaxAge(600) // How long to cache a preflight request response // Credentials() // Sets Access-Control-Allow-Credentials header // }) // // Origin("/[api|swagger].goa.design/", func() {}) // Define CORS policy with a regular expression func Origin(origin string, dsl func()) { cors := &design.CORSDefinition{Origin: origin} if strings.HasPrefix(origin, "/") && strings.HasSuffix(origin, "/") { cors.Regexp = true cors.Origin = strings.Trim(origin, "/") } if !dslengine.Execute(dsl, cors) { return } var parent dslengine.Definition switch def := dslengine.CurrentDefinition().(type) { case *design.APIDefinition: parent = def if def.Origins == nil { def.Origins = make(map[string]*design.CORSDefinition) } def.Origins[origin] = cors case *design.ResourceDefinition: parent = def if def.Origins == nil { def.Origins = make(map[string]*design.CORSDefinition) } def.Origins[origin] = cors default: dslengine.IncompatibleDSL() return } cors.Parent = parent }
// UseTrait executes the API trait with the given name. UseTrait can be used inside a Resource, // Action, Type, MediaType or Attribute DSL. UseTrait takes a variable number // of trait names. func UseTrait(names ...string) { var def dslengine.Definition switch typedDef := dslengine.CurrentDefinition().(type) { case *design.ResourceDefinition: def = typedDef case *design.ActionDefinition: def = typedDef case *design.AttributeDefinition: def = typedDef case *design.MediaTypeDefinition: def = typedDef default: dslengine.IncompatibleDSL() } if def != nil { for _, name := range names { if trait, ok := design.Design.Traits[name]; ok { dslengine.Execute(trait.DSLFunc, def) } else { dslengine.ReportError("unknown trait %s", name) } } } }
// 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 } }
// Contact sets the API contact information. func Contact(dsl func()) { contact := new(design.ContactDefinition) if !dslengine.Execute(dsl, contact) { return } if a, ok := apiDefinition(); ok { a.Contact = contact } }
// License sets the API license information. func License(dsl func()) { license := new(design.LicenseDefinition) if !dslengine.Execute(dsl, license) { return } if a, ok := apiDefinition(); ok { a.License = license } }
// PATCH creates a route using the PATCH HTTP method. func PATCH(path string, dsl ...func()) *design.RouteDefinition { route := &design.RouteDefinition{Verb: "PATCH", Path: path} if len(dsl) != 0 { if !dslengine.Execute(dsl[0], route) { return nil } } return route }
// 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 } }
// Contact sets the API contact information. func Contact(dsl func()) { contact := new(design.ContactDefinition) if !dslengine.Execute(dsl, contact) { return } if a, ok := apiDefinition(false); ok { a.Contact = contact } else if v, ok := versionDefinition(true); ok { v.Contact = contact } }
// License sets the API license information. func License(dsl func()) { license := new(design.LicenseDefinition) if !dslengine.Execute(dsl, license) { return } if a, ok := apiDefinition(false); ok { a.License = license } else if v, ok := versionDefinition(true); ok { v.License = license } }
// Params describe the action parameters, either path parameters identified via wildcards or query // string parameters. 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 or Resource to define common // parameters to all the resource actions. func Params(dsl func()) { switch def := dslengine.CurrentDefinition().(type) { case *design.ActionDefinition: params := newAttribute(def.Parent.MediaType) params.Type = make(design.Object) if dslengine.Execute(dsl, params) { def.Params = params } case *design.ResourceDefinition: params := newAttribute(def.MediaType) params.Type = make(design.Object) if dslengine.Execute(dsl, params) { def.Params = params } default: dslengine.IncompatibleDSL() } }
// 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()) { switch def := dslengine.CurrentDefinition().(type) { case *design.MediaTypeDefinition: mt := def 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 { view, err := buildView(name, mt, at) if err != nil { dslengine.ReportError(err.Error()) return } mt.Views[name] = view } case *design.AttributeDefinition: def.View = name default: dslengine.IncompatibleDSL() } }
// Docs provides external documentation pointers. func Docs(dsl func()) { docs := new(design.DocsDefinition) if !dslengine.Execute(dsl, docs) { return } if a, ok := apiDefinition(false); ok { a.Docs = docs } else if v, ok := versionDefinition(false); ok { v.Docs = docs } else if a, ok := actionDefinition(true); ok { a.Docs = docs } }
// 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 } }
// Docs provides external documentation pointers. func Docs(dsl func()) { docs := new(design.DocsDefinition) if !dslengine.Execute(dsl, docs) { return } switch def := dslengine.CurrentDefinition().(type) { case *design.APIDefinition: def.Docs = docs case *design.ActionDefinition: def.Docs = docs default: dslengine.IncompatibleDSL() } }
// Files defines an API endpoint that serves static assets. The logic for what to do when the // filename points to a file vs. a directory is the same as the standard http package ServeFile // function. The path may end with a wildcard that matches the rest of the URL (e.g. *filepath). If // it does the matching path is appended to filename to form the full file path, so: // // Files("/index.html", "/www/data/index.html") // // Returns the content of the file "/www/data/index.html" when requests are sent to "/index.html" // and: // // Files("/assets/*filepath", "/www/data/assets") // // returns the content of the file "/www/data/assets/x/y/z" when requests are sent to // "/assets/x/y/z". // The file path may be specified as a relative path to the current path of the process. // Files support setting a description, security scheme and doc links via additional DSL: // // Files("/index.html", "/www/data/index.html", func() { // Description("Serve home page") // Docs(func() { // Description("Download docs") // URL("http//cellarapi.com/docs/actions/download") // }) // Security("oauth2", func() { // Scope("api:read") // }) // }) func Files(path, filename string, dsls ...func()) { if r, ok := resourceDefinition(); ok { server := &design.FileServerDefinition{ Parent: r, RequestPath: path, FilePath: filename, } if len(dsls) > 0 { if !dslengine.Execute(dsls[0], server) { return } } r.FileServers = append(r.FileServers, server) } }
// 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 { dslengine.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 { dslengine.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 { 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 := inflect.Camelize(a.Parent.Name) an := inflect.Camelize(a.Name) a.Payload = &design.UserTypeDefinition{ AttributeDefinition: att, TypeName: fmt.Sprintf("%s%sPayload", an, rn), } } }
func (m *MediaTypeDefinition) projectCollection(view string) (*MediaTypeDefinition, *UserTypeDefinition, error) { // Project the collection element media type e := m.ToArray().ElemType.Type.(*MediaTypeDefinition) // validation checked this cast would work pe, le, err2 := e.Project(view) if err2 != nil { return nil, nil, fmt.Errorf("collection element: %s", err2) } // Build the projected collection with the results desc := m.TypeName + " is the media type for an array of " + e.TypeName + " (" + view + " view)" p := &MediaTypeDefinition{ Identifier: m.projectIdentifier(view), UserTypeDefinition: &UserTypeDefinition{ AttributeDefinition: &AttributeDefinition{ Description: desc, Type: &Array{ElemType: &AttributeDefinition{Type: pe}}, Example: m.Example, }, TypeName: pe.TypeName + "Collection", }, } p.Views = map[string]*ViewDefinition{"default": &ViewDefinition{ AttributeDefinition: DupAtt(pe.Views["default"].AttributeDefinition), Name: "default", Parent: p, }} // Run the DSL that was created by the CollectionOf function if !dslengine.Execute(p.DSL(), p) { return nil, nil, dslengine.Errors } // Build the links user type var links *UserTypeDefinition if le != nil { lTypeName := le.TypeName + "Array" links = &UserTypeDefinition{ AttributeDefinition: &AttributeDefinition{ Type: &Array{ElemType: &AttributeDefinition{Type: le}}, Description: fmt.Sprintf("%s contains links to related resources of %s.", lTypeName, m.TypeName), }, TypeName: lTypeName, } } return p, links, nil }
// UseTrait executes the API trait with the given name. UseTrait can be used inside a Resource, // Action or Attribute DSL. func UseTrait(name string) { var def dslengine.Definition if r, ok := resourceDefinition(false); ok { def = r } else if a, ok := actionDefinition(false); ok { def = a } else if a, ok := attributeDefinition(true); ok { def = a } if def != nil { if trait, ok := design.Design.Traits[name]; ok { dslengine.Execute(trait.DSLFunc, def) } else { dslengine.ReportError("unknown trait %s", name) } } }
// 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 } }
// Action implements the action definition DSL. Action definitions describe specific API endpoints // including the URL, HTTP method and request parameters (via path wildcards or query strings) and // payload (data structure describing the request HTTP body). An action belongs to a resource and // "inherits" default values from the resource definition including the URL path prefix, default // response media type and default payload attribute properties (inherited from the attribute with // identical name in the resource default media type). Action definitions also describe all the // possible responses including the HTTP status, headers and body. Here is an example showing all // the possible sub-definitions: // Action("Update", func() { // Description("Update account") // Docs(func() { // Description("Update docs") // URL("http//cellarapi.com/docs/actions/update") // }) // Scheme("http") // Routing( // PUT("/:id"), // Action path is relative to parent resource base path // PUT("//orgs/:org/accounts/:id"), // The // prefix indicates an absolute path // ) // Params(func() { // Params describe the action parameters // Param("org", String) // Parameters may correspond to path wildcards // Param("id", Integer) // Param("sort", func() { // or URL query string values. // Enum("asc", "desc") // }) // }) // Security("oauth2", func() { // Security sets the security scheme used to secure requests // Scope("api:read") // Scope("api:write") // }) // Headers(func() { // Headers describe relevant action headers // Header("Authorization", String) // Header("X-Account", Integer) // Required("Authorization", "X-Account") // }) // Payload(UpdatePayload) // Payload describes the HTTP request body // // OptionalPayload(UpdatePayload) // OptionalPayload defines an HTTP request body which may be omitted // Response(NoContent) // Each possible HTTP response is described via Response // Response(NotFound) // }) func Action(name string, dsl func()) { if r, ok := resourceDefinition(); ok { if r.Actions == nil { r.Actions = make(map[string]*design.ActionDefinition) } action, ok := r.Actions[name] if !ok { action = &design.ActionDefinition{ Parent: r, Name: name, } } if !dslengine.Execute(dsl, action) { return } r.Actions[name] = action } }