func okResp(a *design.ActionDefinition) map[string]interface{} { var ok *design.ResponseDefinition for _, resp := range a.Responses { if resp.Status == 200 { ok = resp break } } if ok == nil { return nil } var mt *design.MediaTypeDefinition var ok2 bool if mt, ok2 = design.Design.MediaTypes[design.CanonicalIdentifier(ok.MediaType)]; !ok2 { return nil } name := codegen.GoTypeRef(mt, mt.AllRequired(), 1, false) var pointer string if strings.HasPrefix(name, "*") { name = name[1:] pointer = "*" } typeref := fmt.Sprintf("%s%s.%s", pointer, TargetPackage, name) if strings.HasPrefix(typeref, "*") { typeref = "&" + typeref[1:] } return map[string]interface{}{ "Name": ok.Name, "GoType": codegen.GoNativeType(mt), "TypeRef": typeref, } }
// 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(true); ok { if m, ok := val.(*design.MediaTypeDefinition); ok { if m.UserTypeDefinition == nil { 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 { ReportError("media type must be a string or a *design.MediaTypeDefinition, got %#v", val) } } }
func (g *Generator) okResp(a *design.ActionDefinition) map[string]interface{} { var ok *design.ResponseDefinition for _, resp := range a.Responses { if resp.Status == 200 { ok = resp break } } if ok == nil { return nil } var mt *design.MediaTypeDefinition var ok2 bool if mt, ok2 = design.Design.MediaTypes[design.CanonicalIdentifier(ok.MediaType)]; !ok2 { return nil } view := ok.ViewName if view == "" { view = design.DefaultView } pmt, _, err := mt.Project(view) if err != nil { return nil } var typeref string if pmt.IsError() { typeref = `goa.ErrInternal("not implemented")` } else { name := codegen.GoTypeRef(pmt, pmt.AllRequired(), 1, false) var pointer string if strings.HasPrefix(name, "*") { name = name[1:] pointer = "*" } typeref = fmt.Sprintf("%s%s.%s", pointer, g.Target, name) if strings.HasPrefix(typeref, "*") { typeref = "&" + typeref[1:] } typeref += "{}" } var nameSuffix string if view != "default" { nameSuffix = codegen.Goify(view, true) } return map[string]interface{}{ "Name": ok.Name + nameSuffix, "GoType": codegen.GoNativeType(pmt), "TypeRef": typeref, } }
// 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 }
func okResp(a *design.ActionDefinition) map[string]interface{} { var ok *design.ResponseDefinition for _, resp := range a.Responses { if resp.Status == 200 { ok = resp break } } if ok == nil { return nil } var mt *design.MediaTypeDefinition var ok2 bool if mt, ok2 = design.Design.MediaTypes[design.CanonicalIdentifier(ok.MediaType)]; !ok2 { return nil } view := "default" if _, ok := mt.Views["default"]; !ok { for v := range mt.Views { view = v break } } pmt, _, err := mt.Project(view) if err != nil { return nil } name := codegen.GoTypeRef(pmt, pmt.AllRequired(), 1, false) var pointer string if strings.HasPrefix(name, "*") { name = name[1:] pointer = "*" } typeref := fmt.Sprintf("%s%s.%s", pointer, TargetPackage, name) if strings.HasPrefix(typeref, "*") { typeref = "&" + typeref[1:] } var nameSuffix string if view != "default" { nameSuffix = codegen.Goify(view, true) } return map[string]interface{}{ "Name": ok.Name + nameSuffix, "GoType": codegen.GoNativeType(pmt), "TypeRef": typeref, } }
func responseSpecFromDefinition(s *Swagger, api *design.APIDefinition, r *design.ResponseDefinition) (*Response, error) { var schema *genschema.JSONSchema if r.MediaType != "" { if mt, ok := api.MediaTypes[design.CanonicalIdentifier(r.MediaType)]; ok { schema = genschema.TypeSchema(api, mt) } } headers, err := headersFromDefinition(r.Headers) if err != nil { return nil, err } return &Response{ Description: r.Description, Schema: schema, Headers: headers, }, nil }
func responseSpecFromDefinition(s *Swagger, api *design.APIDefinition, r *design.ResponseDefinition) (*Response, error) { var schema *genschema.JSONSchema if r.MediaType != "" { if mt, ok := api.MediaTypes[design.CanonicalIdentifier(r.MediaType)]; ok { view := r.ViewName if view == "" { view = design.DefaultView } schema = genschema.NewJSONSchema() schema.Ref = genschema.MediaTypeRef(api, mt, view) } } headers, err := headersFromDefinition(r.Headers) if err != nil { return nil, err } return &Response{ Description: r.Description, Schema: schema, Headers: headers, Extensions: extensionsFromDefinition(r.Metadata), }, nil }
package design_test import ( "github.com/goadesign/goa/design" "github.com/goadesign/goa/dslengine" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) var _ = Describe("CanonicalIdentifier", func() { var id string var canonical string JustBeforeEach(func() { canonical = design.CanonicalIdentifier(id) }) Context("with a canonical identifier", func() { BeforeEach(func() { id = "application/json" }) It("returns it", func() { Ω(canonical).Should(Equal(id)) }) }) Context("with a non canonical identifier", func() { BeforeEach(func() { id = "application/json+xml; foo=bar" })
// MediaType implements the media type definition apidsl. A media type definition describes the // representation of a resource used in a response body. This includes listing all the *potential* // resource 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 apidsl. 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") // Optionally override the default generated name // 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 a link to the Account media type // Link("origin", "tiny") // Overrides the default view used to render links // }) // Required("id", "href") // }) // View("default", func() { // Attribute("id") // Attribute("href") // Attribute("links") // Default view renders links // }) // View("extended", func() { // Attribute("id") // Attribute("href") // Attribute("account") // Extended view renders account inline // Attribute("origin") // Extended view renders origin inline // Attribute("links") // Extended view also 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 is defined twice", identifier) return nil } parts := strings.Split(identifier, "+") // Make sure it has the `+json` suffix (TBD update when goa supports other encodings) if len(parts) > 1 { parts = parts[1:] found := false for _, part := range parts { if part == "json" { found = true break } } if !found { identifier += "+json" } } identifier = mime.FormatMediaType(identifier, params) // Concoct a Go type name from the identifier, should it be possible to set it in the apidsl? // pros: control the type name generated, cons: not needed in apidsl, adds one more thing to worry about 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 }
// CollectionOf creates a collection media type from its element media type. A collection media // type represents the content of responses that return a collection of resources such as "list" // actions. This function can be called from any place where a media type can be used. // The resulting media type identifier is built from the element media type by appending the media // type parameter "type" with value "collection". func CollectionOf(v interface{}, apidsl ...func()) *design.MediaTypeDefinition { var m *design.MediaTypeDefinition var ok bool m, ok = v.(*design.MediaTypeDefinition) if !ok { if id, ok := v.(string); ok { m = design.Design.MediaTypes[design.CanonicalIdentifier(id)] } } if m == nil { dslengine.ReportError("invalid CollectionOf argument: not a media type and not a known media type identifier") // don't return nil to avoid panics, the error will get reported at the end return design.NewMediaTypeDefinition("InvalidCollection", "text/plain", nil) } id := m.Identifier mediatype, params, err := mime.ParseMediaType(id) if err != nil { dslengine.ReportError("invalid media type identifier %#v: %s", id, err) // don't return nil to avoid panics, the error will get reported at the end return design.NewMediaTypeDefinition("InvalidCollection", "text/plain", nil) } hasType := false for param := range params { if param == "type" { hasType = true break } } if !hasType { params["type"] = "collection" } id = mime.FormatMediaType(mediatype, params) canonical := design.CanonicalIdentifier(id) if mt, ok := design.GeneratedMediaTypes[canonical]; ok { // Already have a type for this collection, reuse it. return mt } mt := design.NewMediaTypeDefinition("", id, func() { if mt, ok := mediaTypeDefinition(); ok { // Cannot compute collection type name before element media type DSL has executed // since the DSL may modify element type name via the TypeName function. mt.TypeName = m.TypeName + "Collection" mt.AttributeDefinition = &design.AttributeDefinition{Type: ArrayOf(m)} if len(apidsl) > 0 { dslengine.Execute(apidsl[0], mt) } if mt.Views == nil { // If the apidsl didn't create any views (or there is no apidsl at all) // then inherit the views from the collection element. mt.Views = make(map[string]*design.ViewDefinition) for n, v := range m.Views { mt.Views[n] = v } } } }) // Do not execute the apidsl right away, will be done last to make sure the element apidsl has run // first. design.GeneratedMediaTypes[canonical] = mt return mt }
// CollectionOf creates a collection media type from its element media type. A collection media // type represents the content of responses that return a collection of resources such as "list" // actions. This function can be called from any place where a media type can be used. // The resulting media type identifier is built from the element media type by appending the media // type parameter "type" with value "collection". func CollectionOf(v interface{}, apidsl ...func()) *design.MediaTypeDefinition { if design.GeneratedMediaTypes == nil { design.GeneratedMediaTypes = make(design.MediaTypeRoot) dslengine.Roots = append(dslengine.Roots, design.GeneratedMediaTypes) } var m *design.MediaTypeDefinition var ok bool m, ok = v.(*design.MediaTypeDefinition) if !ok { if id, ok := v.(string); ok { m = design.Design.MediaTypes[design.CanonicalIdentifier(id)] } } if m == nil { dslengine.ReportError("invalid CollectionOf argument: not a media type and not a known media type identifier") return nil } id := m.Identifier mediatype, params, err := mime.ParseMediaType(id) if err != nil { dslengine.ReportError("invalid media type identifier %#v: %s", id, err) return nil } hasType := false for param := range params { if param == "type" { hasType = true break } } if !hasType { params["type"] = "collection" } id = mime.FormatMediaType(mediatype, params) typeName := m.TypeName + "Collection" if mt, ok := design.GeneratedMediaTypes[typeName]; ok { // Already have a type for this collection, reuse it. return mt } mt := design.NewMediaTypeDefinition(typeName, id, func() { if mt, ok := mediaTypeDefinition(true); ok { mt.TypeName = typeName mt.AttributeDefinition = &design.AttributeDefinition{Type: ArrayOf(m)} mt.APIVersions = m.APIVersions if len(apidsl) > 0 { dslengine.Execute(apidsl[0], mt) } if mt.Views == nil { // If the apidsl didn't create any views (or there is no apidsl at all) // then inherit the views from the collection element. mt.Views = make(map[string]*design.ViewDefinition) for n, v := range m.Views { mt.Views[n] = v } } } }) // Do not execute the apidsl right away, will be done last to make sure the element apidsl has run // first. design.GeneratedMediaTypes[typeName] = mt return mt }
AttributeDefinition: &design.AttributeDefinition{ Type: design.Object{"foo": {Type: design.String}}, }, }, Identifier: "application/vnd.goa.test", ContentType: contentType, } defView := &design.ViewDefinition{ AttributeDefinition: mediaType.AttributeDefinition, Name: "default", Parent: mediaType, } mediaType.Views = map[string]*design.ViewDefinition{"default": defView} design.Design = new(design.APIDefinition) design.Design.MediaTypes = map[string]*design.MediaTypeDefinition{ design.CanonicalIdentifier(mediaType.Identifier): mediaType, } design.ProjectedMediaTypes = make(map[string]*design.MediaTypeDefinition) responses = map[string]*design.ResponseDefinition{"OK": { Name: "OK", Status: 200, MediaType: mediaType.Identifier, }} }) It("the generated code sets the Content-Type header", func() { err := writer.Execute(data) Ω(err).ShouldNot(HaveOccurred()) b, err := ioutil.ReadFile(filename) Ω(err).ShouldNot(HaveOccurred()) written := string(b)