// Resource implements the resource definition dsl. There is one resource definition per resource // exposed by the API. The resource dsl allows setting the resource default media type. This media // type is used to render the response body of actions that return the OK response (unless the // action overrides the default). The default media type also sets the properties of the request // payload attributes with the same name. See DefaultMedia. // // The resource dsl also allows listing the supported resource collection and resource collection // item actions. Each action corresponds to a specific API endpoint. See Action. // // The resource dsl can also specify a parent resource. Parent resources have two effects. // First, they set the prefix of all resource action paths to the parent resource href. Note that // actions can override the path using an absolute path (that is a path starting with "//"). // Second, goa uses the parent resource href coupled with the resource BasePath if any to build // hrefs to the resource collection or resource collection items. By default goa uses the show // action if present to compute a resource href (basically concatenating the parent resource href // with the base path and show action path). The resource definition may specify a canonical action // via CanonicalActionName to override that default. Here is an example of a resource definition: // // Resource("bottle", func() { // Description("A wine bottle") // Resource description // DefaultMedia(BottleMedia) // Resource default media type // BasePath("/bottles") // Common resource action path prefix if not "" // Parent("account") // Name of parent resource if any // CanonicalActionName("get") // Name of action that returns canonical representation if not "show" // UseTrait("Authenticated") // Included trait if any, can appear more than once // APIVersion("v1") // API version exposing this resource, can appear more than once. // // Action("show", func() { // Action definition, can appear more than once // // ... Action dsl // }) // }) func Resource(name string, dsl func()) *design.ResourceDefinition { if design.Design.Resources == nil { design.Design.Resources = make(map[string]*design.ResourceDefinition) } var resource *design.ResourceDefinition if dslengine.TopLevelDefinition(true) { if _, ok := design.Design.Resources[name]; ok { dslengine.ReportError("resource %#v is defined twice", name) return nil } resource = design.NewResourceDefinition(name, dsl) design.Design.Resources[name] = resource } return resource }
// 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 }
// Type implements the type definition dsl. A type definition describes a data structure consisting // of attributes. Each attribute has a type which can also refer to a type definition (or use a // primitive type or nested attibutes). The dsl syntax for define a type definition is the // Attribute dsl, see Attribute. // // On top of specifying any attribute type, type definitions can also be used to describe the data // structure of a request payload. They can also be used by media type definitions as reference, see // Reference. Here is an example: // // Type("createPayload", func() { // Description("Type of create and upload action payloads") // APIVersion("1.0") // Attribute("name", String, "name of bottle") // Attribute("origin", Origin, "Details on wine origin") // See Origin definition below // Required("name") // }) // // var Origin = Type("origin", func() { // Description("Origin of bottle") // Attribute("Country") // }) // // This function returns the newly defined type so the value can be used throughout the dsl. func Type(name string, dsl func()) *design.UserTypeDefinition { if design.Design.Types == nil { design.Design.Types = make(map[string]*design.UserTypeDefinition) } else if _, ok := design.Design.Types[name]; ok { dslengine.ReportError("type %#v defined twice", name) return nil } var t *design.UserTypeDefinition if dslengine.TopLevelDefinition(true) { t = &design.UserTypeDefinition{ TypeName: name, AttributeDefinition: &design.AttributeDefinition{DSLFunc: dsl}, } if dsl == nil { t.Type = design.String } design.Design.Types[name] = t } return t }
// 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") // APIVersion("1.0") // 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.TopLevelDefinition(true) { // 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 = "plain/text" } 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 } return nil }