// Store represents a database. Gorma lets you specify // a database type, but it's currently not used for any generation // logic. func Store(name string, storeType gorma.RelationalStorageType, dsl func()) { if name == "" || len(name) == 0 { dslengine.ReportError("Relational Store requires a name.") return } if len(storeType) == 0 { dslengine.ReportError("Relational Store requires a RelationalStoreType.") return } if dsl == nil { dslengine.ReportError("Relational Store requires a dsl.") return } if s, ok := storageGroupDefinition(true); ok { if s.RelationalStores == nil { s.RelationalStores = make(map[string]*gorma.RelationalStoreDefinition) } store, ok := s.RelationalStores[name] if !ok { store = &gorma.RelationalStoreDefinition{ Name: name, DefinitionDSL: dsl, Parent: s, Type: storeType, RelationalModels: make(map[string]*gorma.RelationalModelDefinition), } } else { dslengine.ReportError("Relational Store %s can only be declared once.", name) } s.RelationalStores[name] = store } }
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 }
// Trait defines an API trait. A trait encapsulates arbitrary DSL that gets executed wherever the // trait is called via the UseTrait function. func Trait(name string, val ...func()) { var ver *design.APIVersionDefinition if a, ok := apiDefinition(false); ok { ver = a.APIVersionDefinition } else if v, ok := versionDefinition(true); ok { ver = v } if len(val) < 1 { dslengine.ReportError("missing trait DSL for %s", name) return } else if len(val) > 1 { dslengine.ReportError("too many arguments given to Trait") return } if ver == nil { return } if _, ok := ver.Traits[name]; ok { dslengine.ReportError("multiple definitions for trait %s%s", name, ver.Context()) return } trait := &dslengine.TraitDefinition{Name: name, DSLFunc: val[0]} if ver.Traits == nil { ver.Traits = make(map[string]*dslengine.TraitDefinition) } ver.Traits[name] = trait }
// Response implements the response definition DSL. Response takes the name of the response as // first parameter. goa defines all the standard HTTP status name as global variables so they can be // readily used as response names. Response also accepts optional arguments that correspond to the // arguments defined by the corresponding response template (the response template with the same // name) if there is one, see ResponseTemplate. // // A response may also optionally use an anonymous function as last argument to specify the response // status code, media type and headers overriding what the default response or response template // specifies: // // Response(OK, "vnd.goa.bottle", func() { // OK response template accepts one argument: the media type identifier // Headers(func() { // Headers list the response HTTP headers, see Headers // Header("X-Request-Id") // }) // }) // // Response(NotFound, func() { // Status(404) // Not necessary as defined by default NotFound response. // Media("application/json") // Override NotFound response default of "text/plain" // }) // // Response(Created, func() { // Media(BottleMedia) // }) // // goa defines a default response for all the HTTP status code. The default response simply sets // the status code. So if an action can return NotFound for example all it has to do is specify // Response(NotFound) - there is no need to specify the status code as the default response already // does it, in other words: // // Response(NotFound) // // is equivalent to: // // Response(NotFound, func() { // Status(404) // }) // // goa also defines a default response template for the OK response which takes a single argument: // the identifier of the media type used to render the response. The API DSL can define additional // response templates or override the default OK response template using ResponseTemplate. // // The media type identifier specified in a response definition via the Media function can be // "generic" such as "text/plain" or "application/json" or can correspond to the identifier of a // media type defined in the API DSL. In this latter case goa uses the media type definition to // generate helper response methods. These methods know how to render the views defined on the media // type and run the validations defined in the media type during rendering. func Response(name string, paramsAndDSL ...interface{}) { if a, ok := actionDefinition(false); ok { if a.Responses == nil { a.Responses = make(map[string]*design.ResponseDefinition) } if _, ok := a.Responses[name]; ok { dslengine.ReportError("response %s is defined twice", name) return } if resp := executeResponseDSL(name, paramsAndDSL...); resp != nil { if resp.Status == 200 && resp.MediaType == "" { resp.MediaType = a.Parent.MediaType } resp.Parent = a a.Responses[name] = resp } } else if r, ok := resourceDefinition(true); ok { if r.Responses == nil { r.Responses = make(map[string]*design.ResponseDefinition) } if _, ok := r.Responses[name]; ok { dslengine.ReportError("response %s is defined twice", name) return } if resp := executeResponseDSL(name, paramsAndDSL...); resp != nil { if resp.Status == 200 && resp.MediaType == "" { resp.MediaType = r.MediaType } resp.Parent = r r.Responses[name] = resp } } }
// 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 }
// ResponseTemplate defines a response template that action definitions can use to describe their // responses. The template may specify the HTTP response status, header specification and body media // type. The template consists of a name and an anonymous function. The function is called when an // action uses the template to define a response. Response template functions accept string // parameters they can use to define the response fields. Here is an example of a response template // definition that uses a function with one argument corresponding to the name of the response body // media type: // // ResponseTemplate(OK, func(mt string) { // Status(200) // OK response uses status code 200 // Media(mt) // Media type name set by action definition // Headers(func() { // Header("X-Request-Id", func() { // X-Request-Id header contains a string // Pattern("[0-9A-F]+") // Regexp used to validate the response header content // }) // Required("X-Request-Id") // Header is mandatory // }) // }) // // This template can the be used by actions to define the OK response as follows: // // Response(OK, "vnd.goa.example") // // goa comes with a set of predefined response templates (one per standard HTTP status code). The // OK template is the only one that accepts an argument. It is used as shown in the example above to // set the response media type. Other predefined templates do not use arguments. ResponseTemplate // makes it possible to define additional response templates specific to the API. func ResponseTemplate(name string, p interface{}) { var v *design.APIVersionDefinition if a, ok := apiDefinition(false); ok { v = a.APIVersionDefinition } else if ver, ok := versionDefinition(true); ok { v = ver } if v == nil { return } if v.Responses == nil { v.Responses = make(map[string]*design.ResponseDefinition) } if v.ResponseTemplates == nil { v.ResponseTemplates = make(map[string]*design.ResponseTemplateDefinition) } if _, ok := v.Responses[name]; ok { dslengine.ReportError("multiple definitions for response template %s", name) return } if _, ok := v.ResponseTemplates[name]; ok { dslengine.ReportError("multiple definitions for response template %s", name) return } setupResponseTemplate(v, name, p) }
// 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 } } }
// 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 } }
// 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} }
// Scope defines an authorization scope. Used within SecurityScheme, a description may be provided // explaining what the scope means. Within a Security block, only a scope is needed. func Scope(name string, desc ...string) { switch current := dslengine.CurrentDefinition().(type) { case *design.SecurityDefinition: if len(desc) >= 1 { dslengine.ReportError("too many arguments") return } current.Scopes = append(current.Scopes, name) case *design.SecuritySchemeDefinition: if len(desc) > 1 { dslengine.ReportError("too many arguments") return } if current.Scopes == nil { current.Scopes = make(map[string]string) } d := "no description" if len(desc) == 1 { d = desc[0] } current.Scopes[name] = d default: dslengine.IncompatibleDSL() } }
// Maximum adds a "maximum" validation to the attribute. // See http://json-schema.org/latest/json-schema-validation.html#anchor17. func Maximum(val interface{}) { if a, ok := attributeDefinition(); ok { if a.Type != nil && a.Type.Kind() != design.IntegerKind && a.Type.Kind() != design.NumberKind { incompatibleAttributeType("maximum", a.Type.Name(), "an integer or a number") } else { var f float64 switch v := val.(type) { case float32, float64, int, int8, int16, int32, int64, uint8, uint16, uint32, uint64: f = reflect.ValueOf(v).Convert(reflect.TypeOf(float64(0.0))).Float() case string: var err error f, err = strconv.ParseFloat(v, 64) if err != nil { dslengine.ReportError("invalid number value %#v", v) return } default: dslengine.ReportError("invalid number value %#v", v) return } if a.Validation == nil { a.Validation = &dslengine.ValidationDefinition{} } a.Validation.Maximum = &f } } }
// 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 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 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, } } }
// 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() } }
// MediaType implements the media type definition DSL. A media type definition describes the // representation of a resource used in a response body. // // Media types are defined with a unique identifier as defined by RFC6838. The identifier also // defines the default value for the Content-Type header of responses. The ContentType DSL allows // overridding the default as shown in the example below. // // The media type definition includes a listing of all the potential attributes that can appear in // the body. Views specify which of the attributes are actually rendered so that the same media type // definition may represent multiple rendering of a given resource representation. // // All media types must define a view named "default". This view is used to render the media type in // response bodies when no other view is specified. // // A media type definition may also define links to other media types. This is done by first // defining an attribute for the linked-to media type and then referring to that attribute in the // Links DSL. Views may then elect to render one or the other or both. Links are rendered using the // special "link" view. Media types that are linked to must define that view. Here is an example // showing all the possible media type sub-definitions: // // MediaType("application/vnd.goa.example.bottle", func() { // Description("A bottle of wine") // TypeName("BottleMedia") // Override default generated name // ContentType("application/json") // Override default Content-Type header value // Attributes(func() { // Attribute("id", Integer, "ID of bottle") // Attribute("href", String, "API href of bottle") // Attribute("account", Account, "Owner account") // Attribute("origin", Origin, "Details on wine origin") // Links(func() { // Link("account") // Defines link to Account media type // Link("origin", "tiny") // Set view used to render link if not "link" // }) // Required("id", "href") // }) // View("default", func() { // Attribute("id") // Attribute("href") // Attribute("links") // Renders links // }) // View("extended", func() { // Attribute("id") // Attribute("href") // Attribute("account") // Renders account inline // Attribute("origin") // Renders origin inline // Attribute("links") // Renders links // }) // }) // // This function returns the media type definition so it can be referred to throughout the apidsl. func MediaType(identifier string, apidsl func()) *design.MediaTypeDefinition { if design.Design.MediaTypes == nil { design.Design.MediaTypes = make(map[string]*design.MediaTypeDefinition) } if !dslengine.IsTopLevelDefinition() { dslengine.IncompatibleDSL() return nil } // Validate Media Type identifier, params, err := mime.ParseMediaType(identifier) if err != nil { dslengine.ReportError("invalid media type identifier %#v: %s", identifier, err) // We don't return so that other errors may be // captured in this one run. identifier = "text/plain" } canonicalID := design.CanonicalIdentifier(identifier) // Validate that media type identifier doesn't clash if _, ok := design.Design.MediaTypes[canonicalID]; ok { dslengine.ReportError("media type %#v with canonical identifier %#v is defined twice", identifier, canonicalID) return nil } identifier = mime.FormatMediaType(identifier, params) lastPart := identifier lastPartIndex := strings.LastIndex(identifier, "/") if lastPartIndex > -1 { lastPart = identifier[lastPartIndex+1:] } plusIndex := strings.Index(lastPart, "+") if plusIndex > 0 { lastPart = lastPart[:plusIndex] } lastPart = strings.TrimPrefix(lastPart, "vnd.") elems := strings.Split(lastPart, ".") for i, e := range elems { elems[i] = strings.Title(e) } typeName := strings.Join(elems, "") if typeName == "" { mediaTypeCount++ typeName = fmt.Sprintf("MediaType%d", mediaTypeCount) } // Now save the type in the API media types map mt := design.NewMediaTypeDefinition(typeName, identifier, apidsl) design.Design.MediaTypes[canonicalID] = mt return mt }
// Version is the top level design language function which defines the API global property values // for a given version. The DSL used to define the property values is identical to the one used by // the API function. func Version(ver string, dsl func()) *design.APIVersionDefinition { verdef := &design.APIVersionDefinition{Version: ver, DSLFunc: dsl} if _, ok := design.Design.APIVersions[ver]; ok { dslengine.ReportError("API Version %s defined twice", ver) return verdef } if design.Design.APIVersions == nil { design.Design.APIVersions = make(map[string]*design.APIVersionDefinition) } if ver == "" { dslengine.ReportError("version cannot be an empty string") } design.Design.APIVersions[ver] = verdef return verdef }
// API implements the top level API DSL. It defines the API name, default description and other // default global property values for all API versions. Here is an example showing all the possible // API sub-definitions: // // API("API name", func() { // Title("title") // API title used in documentation // Description("description") // API description used in documentation // TermsOfService("terms") // Contact(func() { // API Contact information // Name("contact name") // Email("contact email") // URL("contact URL") // }) // License(func() { // API Licensing information // Name("license name") // URL("license URL") // }) // Docs(func() { // Description("doc description") // URL("doc URL") // }) // Host("goa.design") // API hostname // Scheme("http") // BasePath("/base/:param") // Common base path to all API actions // BaseParams(func() { // Common parameters to all API actions // Param("param") // }) // Consumes("application/xml", "text/xml") // Built-in encoders and decoders // Consumes("application/json") // Produces("application/gob") // Produces("application/json", func() { // Custom encoder // Package("github.com/goadesign/encoding/json") // }) // ResponseTemplate("static", func() { // Response template for use by actions // Description("description") // Status(404) // MediaType("application/json") // }) // ResponseTemplate("dynamic", func(arg1, arg2 string) { // Description(arg1) // Status(200) // MediaType(arg2) // }) // Trait("Authenticated", func() { // Traits define DSL that can be run anywhere // Headers(func() { // Header("header") // Required("header") // }) // }) // } // func API(name string, dsl func()) *design.APIDefinition { if design.Design.Name != "" { dslengine.ReportError("multiple API definitions, only one is allowed") return nil } if !dslengine.TopLevelDefinition(true) { return nil } if name == "" { dslengine.ReportError("API name cannot be empty") } design.Design.Name = name design.Design.DSLFunc = dsl return design.Design }
// PopulateFromModeledType creates fields for the model // based on the goa UserTypeDefinition it models. // This happens before fields are processed, so it's // ok to just assign without testing. func (f *RelationalModelDefinition) PopulateFromModeledType() { if f.BuiltFrom == nil { return } for _, mt := range f.BuiltFrom { obj := mt.ToObject() obj.IterateAttributes(func(name string, att *design.AttributeDefinition) error { rf := &RelationalFieldDefinition{} rf.Parent = f rf.Name = codegen.Goify(name, true) if strings.HasSuffix(rf.Name, "Id") { rf.Name = strings.TrimSuffix(rf.Name, "Id") rf.Name = rf.Name + "ID" } switch att.Type.Kind() { case design.BooleanKind: rf.Datatype = Boolean case design.IntegerKind: rf.Datatype = Integer case design.NumberKind: rf.Datatype = Decimal case design.StringKind: rf.Datatype = String case design.DateTimeKind: rf.Datatype = Timestamp default: dslengine.ReportError("Unsupported type: %#v ", att.Type.Kind()) } f.RelationalFields[rf.Name] = rf return nil }) } return }
// Enum adds a "enum" validation to the attribute. // See http://json-schema.org/latest/json-schema-validation.html#anchor76. func Enum(val ...interface{}) { if a, ok := attributeDefinition(); ok { ok := true for i, v := range val { // When can a.Type be nil? glad you asked // There are two ways to write an Attribute declaration with the DSL that // don't set the type: with one argument - just the name - in which case the type // is set to String or with two arguments - the name and DSL. In this latter form // the type can end up being either String - if the DSL does not define any // attribute - or object if it does. // Why allowing this? because it's not always possible to specify the type of an // object - an object may just be declared inline to represent a substructure. // OK then why not assuming object and not allowing for string? because the DSL // where there's only one argument and the type is string implicitly is very // useful and common, for example to list attributes that refer to other attributes // such as responses that refer to responses defined at the API level or links that // refer to the media type attributes. So if the form that takes a DSL always ended // up defining an object we'd have a weird situation where one arg is string and // two args is object. Breaks the least surprise principle. Soooo long story // short the lesser evil seems to be to allow the ambiguity. Also tests like the // one below are really a convenience to the user and not a fundamental feature // - not checking in the case the type is not known yet is OK. if a.Type != nil && !a.Type.IsCompatible(v) { dslengine.ReportError("value %#v at index #d is incompatible with attribute of type %s", v, i, a.Type.Name()) ok = false } } if ok { a.AddValues(val) } } }
// DefaultMedia sets a resource default media type by identifier or by reference using a value // returned by MediaType: // // var _ = Resource("bottle", func() { // DefaultMedia(BottleMedia) // // ... // }) // // var _ = Resource("region", func() { // DefaultMedia("vnd.goa.region") // // ... // }) // // The default media type is used to build OK response definitions when no specific media type is // given in the Response function call. The default media type is also used to set the default // properties of attributes listed in action payloads. So if a media type defines an attribute // "name" with associated validations then simply calling Attribute("name") inside a request // Payload defines the payload attribute with the same type and validations. func DefaultMedia(val interface{}) { if r, ok := resourceDefinition(); ok { if m, ok := val.(*design.MediaTypeDefinition); ok { if m.UserTypeDefinition == nil { dslengine.ReportError("invalid media type specification, media type is not initialized") } else { r.MediaType = design.CanonicalIdentifier(m.Identifier) m.Resource = r } } else if identifier, ok := val.(string); ok { r.MediaType = design.CanonicalIdentifier(identifier) } else { dslengine.ReportError("media type must be a string or a *design.MediaTypeDefinition, got %#v", val) } } }
// 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) } } } }
// Default sets the default value for an attribute. // See http://json-schema.org/latest/json-schema-validation.html#anchor10. func Default(def interface{}) { if a, ok := attributeDefinition(); ok { if a.Type != nil { if !a.Type.CanHaveDefault() { dslengine.ReportError("%s type cannot have a default value", qualifiedTypeName(a.Type)) } else if !a.Type.IsCompatible(def) { dslengine.ReportError("default value %#v is incompatible with attribute of type %s", def, qualifiedTypeName(a.Type)) } else { a.SetDefault(def) } } else { a.SetDefault(def) } } }
// 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), } } }
// Example sets the example of an attribute to be used for the documentation: // // Attributes(func() { // Attribute("ID", Integer, func() { // Example(1) // }) // Attribute("name", String, func() { // Example("Cabernet Sauvignon") // }) // Attribute("price", String) //If no Example() is provided, goa generates one that fits your specification // }) // // If you do not want an auto-generated example for an attribute, add NoExample() to it. func Example(exp interface{}) { if a, ok := attributeDefinition(); ok { if pass := a.SetExample(exp); !pass { dslengine.ReportError("example value %#v is incompatible with attribute of type %s", exp, a.Type.Name()) } } }
// Media sets a response media type by name or by reference using a value returned by MediaType: // // Response("NotFound", func() { // Status(404) // Media("application/json") // }) // // If Media uses a media type defined in the design then it may optionally specify a view name: // // Response("OK", func() { // Status(200) // Media(BottleMedia, "tiny") // }) // // Specifying a media type is useful for responses that always return the same view. // // Media can be used inside Response or ResponseTemplate. func Media(val interface{}, viewName ...string) { if r, ok := responseDefinition(); ok { if m, ok := val.(*design.MediaTypeDefinition); ok { if m != nil { r.MediaType = m.Identifier } } else if identifier, ok := val.(string); ok { r.MediaType = identifier } else { dslengine.ReportError("media type must be a string or a pointer to MediaTypeDefinition, got %#v", val) } if len(viewName) == 1 { r.ViewName = viewName[0] } else if len(viewName) > 1 { dslengine.ReportError("too many arguments given to DefaultMedia") } } }
func securitySchemeRedefined(name string) bool { for _, previousScheme := range design.Design.SecuritySchemes { if previousScheme.SchemeName == name { dslengine.ReportError("cannot redefine SecurityScheme with name %q", name) return true } } return false }
// Default sets the default value for an attribute. func Default(def interface{}) { if a, ok := attributeDefinition(true); ok { if a.Type != nil && !a.Type.IsCompatible(def) { dslengine.ReportError("default value %#v is incompatible with attribute of type %s", def, a.Type.Name()) } else { a.DefaultValue = def } } }
// Host sets the API hostname. func Host(host string) { if !hostnameRegex.MatchString(host) { dslengine.ReportError(`invalid hostname value "%s"`, host) return } if a, ok := apiDefinition(); ok { a.Host = host } }
// ResponseTemplate defines a response template that action definitions can use to describe their // responses. The template may specify the HTTP response status, header specification and body media // type. The template consists of a name and an anonymous function. The function is called when an // action uses the template to define a response. Response template functions accept string // parameters they can use to define the response fields. Here is an example of a response template // definition that uses a function with one argument corresponding to the name of the response body // media type: // // ResponseTemplate(OK, func(mt string) { // Status(200) // OK response uses status code 200 // Media(mt) // Media type name set by action definition // Headers(func() { // Header("X-Request-Id", func() { // X-Request-Id header contains a string // Pattern("[0-9A-F]+") // Regexp used to validate the response header content // }) // Required("X-Request-Id") // Header is mandatory // }) // }) // // This template can the be used by actions to define the OK response as follows: // // Response(OK, "vnd.goa.example") // // goa comes with a set of predefined response templates (one per standard HTTP status code). The // OK template is the only one that accepts an argument. It is used as shown in the example above to // set the response media type. Other predefined templates do not use arguments. ResponseTemplate // makes it possible to define additional response templates specific to the API. func ResponseTemplate(name string, p interface{}) { if a, ok := apiDefinition(); ok { if a.Responses == nil { a.Responses = make(map[string]*design.ResponseDefinition) } if a.ResponseTemplates == nil { a.ResponseTemplates = make(map[string]*design.ResponseTemplateDefinition) } if _, ok := a.Responses[name]; ok { dslengine.ReportError("multiple definitions for response template %s", name) return } if _, ok := a.ResponseTemplates[name]; ok { dslengine.ReportError("multiple definitions for response template %s", name) return } setupResponseTemplate(a, name, p) } }