11 Star 11 Fork 0

Gitee 极速下载 / goa

Create your Gitee Account
Explore and code with more than 12 million developers,Free private repositories !:)
Sign up
此仓库是为了提升国内下载速度的镜像仓库,每日同步一次。 原始仓库: https://github.com/goadesign/goa
Clone or Download
definitions.go 53.54 KB
Copy Edit Raw Blame History
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904
package design
import (
"errors"
"fmt"
"net/http"
"path"
"sort"
"strings"
"github.com/dimfeld/httppath"
"github.com/goadesign/goa/dslengine"
)
type (
// APIDefinition defines the global properties of the API.
APIDefinition struct {
// Name of API
Name string
// Title of API
Title string
// Description of API
Description string
// Version is the version of the API described by this design.
Version string
// Host is the default API hostname
Host string
// Schemes is the supported API URL schemes
Schemes []string
// BasePath is the common base path to all API endpoints
BasePath string
// Params define the common path parameters to all API endpoints
Params *AttributeDefinition
// Consumes lists the mime types supported by the API controllers
Consumes []*EncodingDefinition
// Produces lists the mime types generated by the API controllers
Produces []*EncodingDefinition
// Origins defines the CORS policies that apply to this API.
Origins map[string]*CORSDefinition
// TermsOfService describes or links to the API terms of service
TermsOfService string
// Contact provides the API users with contact information
Contact *ContactDefinition
// License describes the API license
License *LicenseDefinition
// Docs points to the API external documentation
Docs *DocsDefinition
// Resources is the set of exposed resources indexed by name
Resources map[string]*ResourceDefinition
// Types indexes the user defined types by name
Types map[string]*UserTypeDefinition
// MediaTypes indexes the API media types by canonical identifier
MediaTypes map[string]*MediaTypeDefinition
// Traits available to all API resources and actions indexed by name
Traits map[string]*dslengine.TraitDefinition
// Responses available to all API actions indexed by name
Responses map[string]*ResponseDefinition
// Response template factories available to all API actions indexed by name
ResponseTemplates map[string]*ResponseTemplateDefinition
// Built-in responses
DefaultResponses map[string]*ResponseDefinition
// Built-in response templates
DefaultResponseTemplates map[string]*ResponseTemplateDefinition
// DSLFunc contains the DSL used to create this definition if any
DSLFunc func()
// Metadata is a list of key/value pairs
Metadata dslengine.MetadataDefinition
// SecuritySchemes lists the available security schemes available
// to the API.
SecuritySchemes []*SecuritySchemeDefinition
// Security defines security requirements for all the
// resources and actions, unless overridden by Resource or
// Action-level Security() calls.
Security *SecurityDefinition
// NoExamples indicates whether to bypass automatic example generation.
NoExamples bool
// rand is the random generator used to generate examples.
rand *RandomGenerator
}
// ContactDefinition contains the API contact information.
ContactDefinition struct {
// Name of the contact person/organization
Name string `json:"name,omitempty"`
// Email address of the contact person/organization
Email string `json:"email,omitempty"`
// URL pointing to the contact information
URL string `json:"url,omitempty"`
}
// LicenseDefinition contains the license information for the API.
LicenseDefinition struct {
// Name of license used for the API
Name string `json:"name,omitempty"`
// URL to the license used for the API
URL string `json:"url,omitempty"`
}
// DocsDefinition points to external documentation.
DocsDefinition struct {
// Description of documentation.
Description string `json:"description,omitempty"`
// URL to documentation.
URL string `json:"url,omitempty"`
}
// ResourceDefinition describes a REST resource.
// It defines both a media type and a set of actions that can be executed through HTTP
// requests.
ResourceDefinition struct {
// Resource name
Name string
// Schemes is the supported API URL schemes
Schemes []string
// Common URL prefix to all resource action HTTP requests
BasePath string
// Path and query string parameters that apply to all actions.
Params *AttributeDefinition
// Name of parent resource if any
ParentName string
// Optional description
Description string
// Default media type, describes the resource attributes
MediaType string
// Default view name if default media type is MediaTypeDefinition
DefaultViewName string
// Exposed resource actions indexed by name
Actions map[string]*ActionDefinition
// FileServers is the list of static asset serving endpoints
FileServers []*FileServerDefinition
// Action with canonical resource path
CanonicalActionName string
// Map of response definitions that apply to all actions indexed by name.
Responses map[string]*ResponseDefinition
// Request headers that apply to all actions.
Headers *AttributeDefinition
// Origins defines the CORS policies that apply to this resource.
Origins map[string]*CORSDefinition
// DSLFunc contains the DSL used to create this definition if any.
DSLFunc func()
// metadata is a list of key/value pairs
Metadata dslengine.MetadataDefinition
// Security defines security requirements for the Resource,
// for actions that don't define one themselves.
Security *SecurityDefinition
}
// CORSDefinition contains the definition for a specific origin CORS policy.
CORSDefinition struct {
// Parent API or resource
Parent dslengine.Definition
// Origin
Origin string
// List of authorized headers, "*" authorizes all
Headers []string
// List of authorized HTTP methods
Methods []string
// List of headers exposed to clients
Exposed []string
// How long to cache a prefligh request response
MaxAge uint
// Sets Access-Control-Allow-Credentials header
Credentials bool
// Sets Whether the Origin string is a regular expression
Regexp bool
}
// EncodingDefinition defines an encoder supported by the API.
EncodingDefinition struct {
// MIMETypes is the set of possible MIME types for the content being encoded or decoded.
MIMETypes []string
// PackagePath is the path to the Go package that implements the encoder/decoder.
// The package must expose a `EncoderFactory` or `DecoderFactory` function
// that the generated code calls. The methods must return objects that implement
// the goa.EncoderFactory or goa.DecoderFactory interface respectively.
PackagePath string
// Function is the name of the Go function used to instantiate the encoder/decoder.
// Defaults to NewEncoder and NewDecoder respecitively.
Function string
// Encoder is true if the definition is for a encoder, false if it's for a decoder.
Encoder bool
}
// ResponseDefinition defines a HTTP response status and optional validation rules.
ResponseDefinition struct {
// Response name
Name string
// HTTP status
Status int
// Response description
Description string
// Response body type if any
Type DataType
// Response body media type if any
MediaType string
// Response view name if MediaType is MediaTypeDefinition
ViewName string
// Response header definitions
Headers *AttributeDefinition
// Parent action or resource
Parent dslengine.Definition
// Metadata is a list of key/value pairs
Metadata dslengine.MetadataDefinition
// Standard is true if the response definition comes from the goa default responses
Standard bool
}
// ResponseTemplateDefinition defines a response template.
// A response template is a function that takes an arbitrary number
// of strings and returns a response definition.
ResponseTemplateDefinition struct {
// Response template name
Name string
// Response template function
Template func(params ...string) *ResponseDefinition
}
// ActionDefinition defines a resource action.
// It defines both an HTTP endpoint and the shape of HTTP requests and responses made to
// that endpoint.
// The shape of requests is defined via "parameters", there are path parameters
// parameters and a payload parameter (request body).
// (i.e. portions of the URL that define parameter values), query string
ActionDefinition struct {
// Action name, e.g. "create"
Name string
// Action description, e.g. "Creates a task"
Description string
// Docs points to the API external documentation
Docs *DocsDefinition
// Parent resource
Parent *ResourceDefinition
// Specific action URL schemes
Schemes []string
// Action routes
Routes []*RouteDefinition
// Map of possible response definitions indexed by name
Responses map[string]*ResponseDefinition
// Path and query string parameters
Params *AttributeDefinition
// Query string parameters only
QueryParams *AttributeDefinition
// Payload blueprint (request body) if any
Payload *UserTypeDefinition
// PayloadOptional is true if the request payload is optional, false otherwise.
PayloadOptional bool
// PayloadOptional is true if the request payload is multipart, false otherwise.
PayloadMultipart bool
// Request headers that need to be made available to action
Headers *AttributeDefinition
// Metadata is a list of key/value pairs
Metadata dslengine.MetadataDefinition
// Security defines security requirements for the action
Security *SecurityDefinition
}
// FileServerDefinition defines an endpoint that servers static assets.
FileServerDefinition struct {
// Parent resource
Parent *ResourceDefinition
// Description for docs
Description string
// Docs points to the API external documentation
Docs *DocsDefinition
// FilePath is the file path to the static asset(s)
FilePath string
// RequestPath is the HTTP path that servers the assets.
RequestPath string
// Metadata is a list of key/value pairs
Metadata dslengine.MetadataDefinition
// Security defines security requirements for the file server.
Security *SecurityDefinition
}
// LinkDefinition defines a media type link, it specifies a URL to a related resource.
LinkDefinition struct {
// Link name
Name string
// View used to render link if not "link"
View string
// URITemplate is the RFC6570 URI template of the link Href.
URITemplate string
// Parent media Type
Parent *MediaTypeDefinition
}
// ViewDefinition defines which members and links to render when building a response.
// The view is a JSON object whose property names must match the names of the parent media
// type members.
// The members fields are inherited from the parent media type but may be overridden.
ViewDefinition struct {
// Set of properties included in view
*AttributeDefinition
// Name of view
Name string
// Parent media Type
Parent *MediaTypeDefinition
}
// RouteDefinition represents an action route.
RouteDefinition struct {
// Verb is the HTTP method, e.g. "GET", "POST", etc.
Verb string
// Path is the URL path e.g. "/tasks/:id"
Path string
// Parent is the action this route applies to.
Parent *ActionDefinition
// Metadata is a list of key/value pairs
Metadata dslengine.MetadataDefinition
}
// AttributeDefinition defines a JSON object member with optional description, default
// value and validations.
AttributeDefinition struct {
// Attribute type
Type DataType
// Attribute reference type if any
Reference DataType
// Optional description
Description string
// Optional validations
Validation *dslengine.ValidationDefinition
// Metadata is a list of key/value pairs
Metadata dslengine.MetadataDefinition
// Optional member default value
DefaultValue interface{}
// Optional member example value
Example interface{}
// Optional view used to render Attribute (only applies to media type attributes).
View string
// NonZeroAttributes lists the names of the child attributes that cannot have a
// zero value (and thus whose presence does not need to be validated).
NonZeroAttributes map[string]bool
// DSLFunc contains the initialization DSL. This is used for user types.
DSLFunc func()
}
// ContainerDefinition defines a generic container definition that contains attributes.
// This makes it possible for plugins to use attributes in their own data structures.
ContainerDefinition interface {
// Attribute returns the container definition embedded attribute.
Attribute() *AttributeDefinition
}
// ResourceIterator is the type of functions given to IterateResources.
ResourceIterator func(r *ResourceDefinition) error
// MediaTypeIterator is the type of functions given to IterateMediaTypes.
MediaTypeIterator func(m *MediaTypeDefinition) error
// UserTypeIterator is the type of functions given to IterateUserTypes.
UserTypeIterator func(m *UserTypeDefinition) error
// ActionIterator is the type of functions given to IterateActions.
ActionIterator func(a *ActionDefinition) error
// FileServerIterator is the type of functions given to IterateFileServers.
FileServerIterator func(f *FileServerDefinition) error
// HeaderIterator is the type of functions given to IterateHeaders.
HeaderIterator func(name string, isRequired bool, h *AttributeDefinition) error
// ResponseIterator is the type of functions given to IterateResponses.
ResponseIterator func(r *ResponseDefinition) error
)
// NewAPIDefinition returns a new design with built-in response templates.
func NewAPIDefinition() *APIDefinition {
api := &APIDefinition{
DefaultResponseTemplates: make(map[string]*ResponseTemplateDefinition),
DefaultResponses: make(map[string]*ResponseDefinition),
}
t := func(params ...string) *ResponseDefinition {
if len(params) < 1 {
dslengine.ReportError("expected media type as argument when invoking response template OK")
return nil
}
return &ResponseDefinition{
Name: OK,
Status: 200,
MediaType: params[0],
}
}
api.DefaultResponseTemplates[OK] = &ResponseTemplateDefinition{
Name: OK,
Template: t,
}
for _, p := range []struct {
status int
name string
}{
{100, Continue},
{101, SwitchingProtocols},
{200, OK},
{201, Created},
{202, Accepted},
{203, NonAuthoritativeInfo},
{204, NoContent},
{205, ResetContent},
{206, PartialContent},
{300, MultipleChoices},
{301, MovedPermanently},
{302, Found},
{303, SeeOther},
{304, NotModified},
{305, UseProxy},
{307, TemporaryRedirect},
{400, BadRequest},
{401, Unauthorized},
{402, PaymentRequired},
{403, Forbidden},
{404, NotFound},
{405, MethodNotAllowed},
{406, NotAcceptable},
{407, ProxyAuthRequired},
{408, RequestTimeout},
{409, Conflict},
{410, Gone},
{411, LengthRequired},
{412, PreconditionFailed},
{413, RequestEntityTooLarge},
{414, RequestURITooLong},
{415, UnsupportedMediaType},
{416, RequestedRangeNotSatisfiable},
{417, ExpectationFailed},
{418, Teapot},
{422, UnprocessableEntity},
{500, InternalServerError},
{501, NotImplemented},
{502, BadGateway},
{503, ServiceUnavailable},
{504, GatewayTimeout},
{505, HTTPVersionNotSupported},
} {
api.DefaultResponses[p.name] = &ResponseDefinition{
Name: p.name,
Description: http.StatusText(p.status),
Status: p.status,
}
}
return api
}
// DSLName is the name of the DSL as displayed to the user during execution.
func (a *APIDefinition) DSLName() string {
return "goa API"
}
// DependsOn returns the other roots this root depends on, nothing for APIDefinition.
func (a *APIDefinition) DependsOn() []dslengine.Root {
return nil
}
// IterateSets calls the given iterator possing in the API definition, user types, media types and
// finally resources.
func (a *APIDefinition) IterateSets(iterator dslengine.SetIterator) {
// First run the top level API DSL to initialize responses and
// response templates needed by resources.
iterator([]dslengine.Definition{a})
// Then run the user type DSLs
typeAttributes := make([]dslengine.Definition, len(a.Types))
i := 0
a.IterateUserTypes(func(u *UserTypeDefinition) error {
u.AttributeDefinition.DSLFunc = u.DSLFunc
typeAttributes[i] = u.AttributeDefinition
i++
return nil
})
iterator(typeAttributes)
// Then the media type DSLs
mediaTypes := make([]dslengine.Definition, len(a.MediaTypes))
i = 0
a.IterateMediaTypes(func(mt *MediaTypeDefinition) error {
mediaTypes[i] = mt
i++
return nil
})
iterator(mediaTypes)
// Then, the Security schemes definitions
var securitySchemes []dslengine.Definition
for _, scheme := range a.SecuritySchemes {
securitySchemes = append(securitySchemes, dslengine.Definition(scheme))
}
iterator(securitySchemes)
// And now that we have everything - the resources. The resource
// lifecycle handlers dispatch to their children elements, like Actions,
// etc.. We must process parent resources first to ensure that query
// string and path parameters are initialized by the time a child
// resource action parameters are categorized.
resources := make([]*ResourceDefinition, len(a.Resources))
i = 0
a.IterateResources(func(res *ResourceDefinition) error {
resources[i] = res
i++
return nil
})
sort.Sort(byParent(resources))
defs := make([]dslengine.Definition, len(resources))
for i, r := range resources {
defs[i] = r
}
iterator(defs)
}
// Reset sets all the API definition fields to their zero value except the default responses and
// default response templates.
func (a *APIDefinition) Reset() {
n := NewAPIDefinition()
*a = *n
}
// Context returns the generic definition name used in error messages.
func (a *APIDefinition) Context() string {
if a.Name != "" {
return fmt.Sprintf("API %#v", a.Name)
}
return "unnamed API"
}
// PathParams returns the base path parameters of a.
func (a *APIDefinition) PathParams() *AttributeDefinition {
names := ExtractWildcards(a.BasePath)
obj := make(Object)
for _, n := range names {
obj[n] = a.Params.Type.ToObject()[n]
}
return &AttributeDefinition{Type: obj}
}
// IterateMediaTypes calls the given iterator passing in each media type sorted in alphabetical order.
// Iteration stops if an iterator returns an error and in this case IterateMediaTypes returns that
// error.
func (a *APIDefinition) IterateMediaTypes(it MediaTypeIterator) error {
names := make([]string, len(a.MediaTypes))
i := 0
for n := range a.MediaTypes {
names[i] = n
i++
}
sort.Strings(names)
for _, n := range names {
if err := it(a.MediaTypes[n]); err != nil {
return err
}
}
return nil
}
// IterateUserTypes calls the given iterator passing in each user type sorted in alphabetical order.
// Iteration stops if an iterator returns an error and in this case IterateUserTypes returns that
// error.
func (a *APIDefinition) IterateUserTypes(it UserTypeIterator) error {
names := make([]string, len(a.Types))
i := 0
for n := range a.Types {
names[i] = n
i++
}
sort.Strings(names)
for _, n := range names {
if err := it(a.Types[n]); err != nil {
return err
}
}
return nil
}
// IterateResponses calls the given iterator passing in each response sorted in alphabetical order.
// Iteration stops if an iterator returns an error and in this case IterateResponses returns that
// error.
func (a *APIDefinition) IterateResponses(it ResponseIterator) error {
names := make([]string, len(a.Responses))
i := 0
for n := range a.Responses {
names[i] = n
i++
}
sort.Strings(names)
for _, n := range names {
if err := it(a.Responses[n]); err != nil {
return err
}
}
return nil
}
// RandomGenerator is seeded after the API name. It's used to generate examples.
func (a *APIDefinition) RandomGenerator() *RandomGenerator {
if a.rand == nil {
a.rand = NewRandomGenerator(a.Name)
}
return a.rand
}
// MediaTypeWithIdentifier returns the media type with a matching
// media type identifier. Two media type identifiers match if their
// values sans suffix match. So for example "application/vnd.foo+xml",
// "application/vnd.foo+json" and "application/vnd.foo" all match.
func (a *APIDefinition) MediaTypeWithIdentifier(id string) *MediaTypeDefinition {
canonicalID := CanonicalIdentifier(id)
for _, mt := range a.MediaTypes {
if canonicalID == CanonicalIdentifier(mt.Identifier) {
return mt
}
}
return nil
}
// IterateResources calls the given iterator passing in each resource sorted in alphabetical order.
// Iteration stops if an iterator returns an error and in this case IterateResources returns that
// error.
func (a *APIDefinition) IterateResources(it ResourceIterator) error {
res := make([]*ResourceDefinition, len(a.Resources))
i := 0
for _, r := range a.Resources {
res[i] = r
i++
}
// Iterate parent resources first so that action parameters are
// finalized prior to child actions needing them.
isParent := func(p, c *ResourceDefinition) bool {
par := c.Parent()
for par != nil {
if par == p {
return true
}
par = par.Parent()
}
return false
}
sort.Slice(res, func(i, j int) bool {
if isParent(res[i], res[j]) {
return true
}
if isParent(res[j], res[i]) {
return false
}
return res[i].Name < res[j].Name
})
for _, r := range res {
if err := it(r); err != nil {
return err
}
}
return nil
}
// DSL returns the initialization DSL.
func (a *APIDefinition) DSL() func() {
return a.DSLFunc
}
// Finalize sets the Consumes and Produces fields to the defaults if empty.
// Also it records built-in media types that are used by the user design.
func (a *APIDefinition) Finalize() {
if len(a.Consumes) == 0 {
a.Consumes = DefaultDecoders
}
if len(a.Produces) == 0 {
a.Produces = DefaultEncoders
}
a.IterateResources(func(r *ResourceDefinition) error {
returnsError := func(resp *ResponseDefinition) bool {
if resp.MediaType == ErrorMediaIdentifier {
if a.MediaTypes == nil {
a.MediaTypes = make(map[string]*MediaTypeDefinition)
}
a.MediaTypes[CanonicalIdentifier(ErrorMediaIdentifier)] = ErrorMedia
return true
}
return false
}
for _, resp := range a.Responses {
if returnsError(resp) {
return errors.New("done")
}
}
for _, resp := range r.Responses {
if returnsError(resp) {
return errors.New("done")
}
}
return r.IterateActions(func(action *ActionDefinition) error {
for _, resp := range action.Responses {
if returnsError(resp) {
return errors.New("done")
}
}
return nil
})
})
}
// NewResourceDefinition creates a resource definition but does not
// execute the DSL.
func NewResourceDefinition(name string, dsl func()) *ResourceDefinition {
return &ResourceDefinition{
Name: name,
MediaType: "text/plain",
DSLFunc: dsl,
}
}
// Context returns the generic definition name used in error messages.
func (r *ResourceDefinition) Context() string {
if r.Name != "" {
return fmt.Sprintf("resource %#v", r.Name)
}
return "unnamed resource"
}
// PathParams returns the base path parameters of r.
func (r *ResourceDefinition) PathParams() *AttributeDefinition {
names := ExtractWildcards(r.BasePath)
obj := make(Object)
if r.Params != nil {
for _, n := range names {
if p, ok := r.Params.Type.ToObject()[n]; ok {
obj[n] = p
}
}
}
return &AttributeDefinition{Type: obj}
}
// IterateActions calls the given iterator passing in each resource action sorted in alphabetical order.
// Iteration stops if an iterator returns an error and in this case IterateActions returns that
// error.
func (r *ResourceDefinition) IterateActions(it ActionIterator) error {
names := make([]string, len(r.Actions))
i := 0
for n := range r.Actions {
names[i] = n
i++
}
sort.Strings(names)
for _, n := range names {
if err := it(r.Actions[n]); err != nil {
return err
}
}
return nil
}
// IterateFileServers calls the given iterator passing each resource file server sorted by file
// path. Iteration stops if an iterator returns an error and in this case IterateFileServers returns
// that error.
func (r *ResourceDefinition) IterateFileServers(it FileServerIterator) error {
sort.Sort(ByFilePath(r.FileServers))
for _, f := range r.FileServers {
if err := it(f); err != nil {
return err
}
}
return nil
}
// IterateHeaders calls the given iterator passing in each response sorted in alphabetical order.
// Iteration stops if an iterator returns an error and in this case IterateHeaders returns that
// error.
func (r *ResourceDefinition) IterateHeaders(it HeaderIterator) error {
return iterateHeaders(r.Headers, r.Headers.IsRequired, it)
}
// CanonicalAction returns the canonical action of the resource if any.
// The canonical action is used to compute hrefs to resources.
func (r *ResourceDefinition) CanonicalAction() *ActionDefinition {
name := r.CanonicalActionName
if name == "" {
name = "show"
}
ca, _ := r.Actions[name]
return ca
}
// URITemplate returns a URI template to this resource.
// The result is the empty string if the resource does not have a "show" action
// and does not define a different canonical action.
func (r *ResourceDefinition) URITemplate() string {
ca := r.CanonicalAction()
if ca == nil || len(ca.Routes) == 0 {
return ""
}
return ca.Routes[0].FullPath()
}
// FullPath computes the base path to the resource actions concatenating the API and parent resource
// base paths as needed.
func (r *ResourceDefinition) FullPath() string {
if strings.HasPrefix(r.BasePath, "//") {
return httppath.Clean(r.BasePath)
}
var basePath string
if p := r.Parent(); p != nil {
if ca := p.CanonicalAction(); ca != nil {
if routes := ca.Routes; len(routes) > 0 {
// Note: all these tests should be true at code generation time
// as DSL validation makes sure that parent resources have a
// canonical path.
basePath = path.Join(routes[0].FullPath())
}
}
} else {
basePath = Design.BasePath
}
return httppath.Clean(path.Join(basePath, r.BasePath))
}
// Parent returns the parent resource if any, nil otherwise.
func (r *ResourceDefinition) Parent() *ResourceDefinition {
if r.ParentName != "" {
if parent, ok := Design.Resources[r.ParentName]; ok {
return parent
}
}
return nil
}
// AllOrigins compute all CORS policies for the resource taking into account any API policy.
// The result is sorted alphabetically by policy origin.
func (r *ResourceDefinition) AllOrigins() []*CORSDefinition {
all := make(map[string]*CORSDefinition)
for n, o := range Design.Origins {
all[n] = o
}
for n, o := range r.Origins {
all[n] = o
}
names := make([]string, len(all))
i := 0
for n := range all {
names[i] = n
i++
}
sort.Strings(names)
cors := make([]*CORSDefinition, len(names))
for i, n := range names {
cors[i] = all[n]
}
return cors
}
// PreflightPaths returns the paths that should handle OPTIONS requests.
func (r *ResourceDefinition) PreflightPaths() []string {
var paths []string
r.IterateActions(func(a *ActionDefinition) error {
for _, r := range a.Routes {
if r.Verb == "OPTIONS" {
continue
}
found := false
fp := r.FullPath()
for _, p := range paths {
if fp == p {
found = true
break
}
}
if !found {
paths = append(paths, fp)
}
}
return nil
})
r.IterateFileServers(func(fs *FileServerDefinition) error {
found := false
fp := fs.RequestPath
for _, p := range paths {
if fp == p {
found = true
break
}
}
if !found {
paths = append(paths, fp)
}
return nil
})
return paths
}
// DSL returns the initialization DSL.
func (r *ResourceDefinition) DSL() func() {
return r.DSLFunc
}
// Finalize is run post DSL execution. It merges response definitions, creates implicit action
// parameters, initializes querystring parameters, sets path parameters as non zero attributes
// and sets the fallbacks for security schemes.
func (r *ResourceDefinition) Finalize() {
meta := r.Metadata["swagger:generate"]
r.IterateFileServers(func(f *FileServerDefinition) error {
if meta != nil {
if _, ok := f.Metadata["swagger:generate"]; !ok {
f.Metadata["swagger:generate"] = meta
}
}
f.Finalize()
return nil
})
r.IterateActions(func(a *ActionDefinition) error {
if meta != nil {
if _, ok := a.Metadata["swagger:generate"]; !ok {
a.Metadata["swagger:generate"] = meta
}
}
a.Finalize()
return nil
})
}
// UserTypes returns all the user types used by the resource action payloads and parameters.
func (r *ResourceDefinition) UserTypes() map[string]*UserTypeDefinition {
types := make(map[string]*UserTypeDefinition)
for _, a := range r.Actions {
for n, ut := range a.UserTypes() {
types[n] = ut
}
}
if len(types) == 0 {
return nil
}
return types
}
// byParent makes it possible to sort resources - parents first the children.
type byParent []*ResourceDefinition
func (p byParent) Len() int { return len(p) }
func (p byParent) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p byParent) Less(i, j int) bool {
for k := 0; k < i; k++ {
// We need to inspect _all_ previous fields to see if they are a parent. Sort doesn't do this.
if p[i].Name == p[k].ParentName {
return true
}
}
return false
}
// Context returns the generic definition name used in error messages.
func (cors *CORSDefinition) Context() string {
return fmt.Sprintf("CORS policy for resource %s origin %s", cors.Parent.Context(), cors.Origin)
}
// Context returns the generic definition name used in error messages.
func (enc *EncodingDefinition) Context() string {
return fmt.Sprintf("encoding for %s", strings.Join(enc.MIMETypes, ", "))
}
// Context returns the generic definition name used in error messages.
func (a *AttributeDefinition) Context() string {
return ""
}
// AllRequired returns the list of all required fields from the underlying object.
// An attribute type can be itself an attribute (e.g. a MediaTypeDefinition or a UserTypeDefinition)
// This happens when the DSL uses references for example. So traverse the hierarchy and collect
// all the required validations.
func (a *AttributeDefinition) AllRequired() (required []string) {
if a == nil || a.Validation == nil {
return
}
required = a.Validation.Required
if ds, ok := a.Type.(DataStructure); ok {
required = append(required, ds.Definition().AllRequired()...)
}
return
}
// IsRequired returns true if the given string matches the name of a required
// attribute, false otherwise.
func (a *AttributeDefinition) IsRequired(attName string) bool {
for _, name := range a.AllRequired() {
if name == attName {
return true
}
}
return false
}
// HasDefaultValue returns true if the given attribute has a default value.
func (a *AttributeDefinition) HasDefaultValue(attName string) bool {
if a.Type.IsObject() {
att := a.Type.ToObject()[attName]
return att.DefaultValue != nil
}
return false
}
// SetDefault sets the default for the attribute. It also converts HashVal
// and ArrayVal to map and slice respectively.
func (a *AttributeDefinition) SetDefault(def interface{}) {
switch actual := def.(type) {
case HashVal:
a.DefaultValue = actual.ToMap()
case ArrayVal:
a.DefaultValue = actual.ToSlice()
default:
a.DefaultValue = actual
}
}
// AddValues adds the Enum values to the attribute's validation definition.
// It also performs any conversion needed for HashVal and ArrayVal types.
func (a *AttributeDefinition) AddValues(values []interface{}) {
if a.Validation == nil {
a.Validation = &dslengine.ValidationDefinition{}
}
a.Validation.Values = make([]interface{}, len(values))
for i, v := range values {
switch actual := v.(type) {
case HashVal:
a.Validation.Values[i] = actual.ToMap()
case ArrayVal:
a.Validation.Values[i] = actual.ToSlice()
default:
a.Validation.Values[i] = actual
}
}
}
// AllNonZero returns the complete list of all non-zero attribute name.
func (a *AttributeDefinition) AllNonZero() []string {
nzs := make([]string, len(a.NonZeroAttributes))
i := 0
for n := range a.NonZeroAttributes {
nzs[i] = n
i++
}
return nzs
}
// IsNonZero returns true if the given string matches the name of a non-zero
// attribute, false otherwise.
func (a *AttributeDefinition) IsNonZero(attName string) bool {
return a.NonZeroAttributes[attName]
}
// IsPrimitivePointer returns true if the field generated for the given attribute should be a
// pointer to a primitive type. The target attribute must be an object.
func (a *AttributeDefinition) IsPrimitivePointer(attName string) bool {
if !a.Type.IsObject() {
panic("checking pointer field on non-object") // bug
}
att := a.Type.ToObject()[attName]
if att == nil {
return false
}
if att.Type.IsPrimitive() {
return (!a.IsRequired(attName) && !a.HasDefaultValue(attName) && !a.IsNonZero(attName) && !a.IsInterface(attName)) || a.IsFile(attName)
}
return false
}
// IsInterface returns true if the field generated for the given attribute has
// an interface type that should not be referenced as a "*interface{}" pointer.
// The target attribute must be an object.
func (a *AttributeDefinition) IsInterface(attName string) bool {
if !a.Type.IsObject() {
panic("checking pointer field on non-object") // bug
}
att := a.Type.ToObject()[attName]
if att == nil {
return false
}
return att.Type.Kind() == AnyKind
}
// IsFile returns true if the attribute is of type File or if any its children attributes (if any) is.
func (a *AttributeDefinition) IsFile(attName string) bool {
if !a.Type.IsObject() {
panic("checking pointer field on non-object") // bug
}
att := a.Type.ToObject()[attName]
if att == nil {
return false
}
return att.Type.Kind() == FileKind
}
// SetExample sets the custom example. SetExample also handles the case when the user doesn't
// want any example or any auto-generated example.
func (a *AttributeDefinition) SetExample(example interface{}) bool {
if example == nil {
a.Example = "-" // set it to something else than nil so we know not to generate one
return true
}
if a.Type == nil || a.Type.IsCompatible(example) {
a.Example = example
return true
}
return false
}
// GenerateExample returns the value of the Example field if not nil. Otherwise it traverses the
// attribute type and recursively generates an example. The result is saved in the Example field.
func (a *AttributeDefinition) GenerateExample(rand *RandomGenerator, seen []string) interface{} {
if a.Example != nil {
return a.Example
}
if Design.NoExamples {
return nil
}
// Avoid infinite loops
var key string
if mt, ok := a.Type.(*MediaTypeDefinition); ok {
key = mt.Identifier
} else if ut, ok := a.Type.(*UserTypeDefinition); ok {
key = ut.TypeName
}
if key != "" {
count := 0
for _, k := range seen {
if k == key {
count++
}
}
if count > 1 {
// Only go a couple of levels deep
return nil
}
seen = append(seen, key)
}
switch {
case a.Type.IsArray():
a.Example = a.arrayExample(rand, seen)
case a.Type.IsHash():
a.Example = a.hashExample(rand, seen)
case a.Type.IsObject():
a.Example = a.objectExample(rand, seen)
default:
a.Example = newExampleGenerator(a, rand).Generate(seen)
}
return a.Example
}
// SetReadOnly sets the attribute's ReadOnly field as true.
func (a *AttributeDefinition) SetReadOnly() {
if a.Metadata == nil {
a.Metadata = map[string][]string{}
}
a.Metadata["swagger:read-only"] = nil
}
// IsReadOnly returns true if attribute is read-only (set using SetReadOnly() method)
func (a *AttributeDefinition) IsReadOnly() bool {
if _, readOnlyMetadataIsPresent := a.Metadata["swagger:read-only"]; readOnlyMetadataIsPresent {
return true
}
return false
}
func (a *AttributeDefinition) arrayExample(rand *RandomGenerator, seen []string) interface{} {
ary := a.Type.ToArray()
ln := newExampleGenerator(a, rand).ExampleLength()
var res []interface{}
for i := 0; i < ln; i++ {
ex := ary.ElemType.GenerateExample(rand, seen)
if ex != nil {
res = append(res, ex)
}
}
if len(res) == 0 {
return nil
}
return ary.MakeSlice(res)
}
func (a *AttributeDefinition) hashExample(rand *RandomGenerator, seen []string) interface{} {
h := a.Type.ToHash()
ln := newExampleGenerator(a, rand).ExampleLength()
res := make(map[interface{}]interface{})
for i := 0; i < ln; i++ {
k := h.KeyType.GenerateExample(rand, seen)
v := h.ElemType.GenerateExample(rand, seen)
if k != nil && v != nil {
res[k] = v
}
}
if len(res) == 0 {
return nil
}
return h.MakeMap(res)
}
func (a *AttributeDefinition) objectExample(rand *RandomGenerator, seen []string) interface{} {
// project media types
actual := a
if mt, ok := a.Type.(*MediaTypeDefinition); ok {
v := a.View
if v == "" {
v = DefaultView
}
projected, _, err := mt.Project(v)
if err != nil {
panic(err) // bug
}
actual = projected.AttributeDefinition
}
// ensure fixed ordering so random values are computed with consistent seeds
aObj := actual.Type.ToObject()
keys := make([]string, len(aObj))
i := 0
for n := range aObj {
keys[i] = n
i++
}
sort.Strings(keys)
res := make(map[string]interface{})
for _, n := range keys {
att := aObj[n]
if ex := att.GenerateExample(rand, seen); ex != nil {
res[n] = ex
}
}
if len(res) > 0 {
a.Example = res
}
return a.Example
}
// Merge merges the argument attributes into the target and returns the target overriding existing
// attributes with identical names.
// This only applies to attributes of type Object and Merge panics if the
// argument or the target is not of type Object.
func (a *AttributeDefinition) Merge(other *AttributeDefinition) *AttributeDefinition {
if other == nil {
return a
}
if a == nil {
return other
}
left := a.Type.(Object)
right := other.Type.(Object)
if left == nil || right == nil {
panic("cannot merge non object attributes") // bug
}
for n, v := range right {
left[n] = v
}
if other.Validation != nil && len(other.Validation.Required) > 0 {
if a.Validation == nil {
a.Validation = &dslengine.ValidationDefinition{}
}
for _, r := range other.Validation.Required {
a.Validation.Required = append(a.Validation.Required, r)
}
}
return a
}
// Inherit merges the properties of existing target type attributes with the argument's.
// The algorithm is recursive so that child attributes are also merged.
func (a *AttributeDefinition) Inherit(parent *AttributeDefinition, seen ...map[*AttributeDefinition]struct{}) {
if !a.shouldInherit(parent) {
return
}
a.inheritValidations(parent)
a.inheritRecursive(parent, seen...)
}
// DSL returns the initialization DSL.
func (a *AttributeDefinition) DSL() func() {
return a.DSLFunc
}
func (a *AttributeDefinition) inheritRecursive(parent *AttributeDefinition, seen ...map[*AttributeDefinition]struct{}) {
// prevent infinite recursions
var s map[*AttributeDefinition]struct{}
if len(seen) > 0 {
s = seen[0]
if _, ok := s[parent]; ok {
return
}
} else {
s = make(map[*AttributeDefinition]struct{})
}
s[parent] = struct{}{}
if !a.shouldInherit(parent) {
return
}
for n, att := range a.Type.ToObject() {
if patt, ok := parent.Type.ToObject()[n]; ok {
if att.Description == "" {
att.Description = patt.Description
}
att.inheritValidations(patt)
if att.DefaultValue == nil {
att.DefaultValue = patt.DefaultValue
}
if att.View == "" {
att.View = patt.View
}
if att.Type == nil {
att.Type = patt.Type
} else if att.shouldInherit(patt) {
for _, att := range att.Type.ToObject() {
att.Inherit(patt.Type.ToObject()[n], s)
}
}
if att.Example == nil {
att.Example = patt.Example
}
if patt.Metadata != nil {
if att.Metadata == nil {
att.Metadata = patt.Metadata
} else {
// Copy all key/value pairs from parent to child that DO NOT exist in child; existing ones will remain with the same value
for k, v := range patt.Metadata {
if _, keyMetadataIsPresent := att.Metadata[k]; !keyMetadataIsPresent {
att.Metadata[k] = v
}
}
}
}
}
}
}
func (a *AttributeDefinition) inheritValidations(parent *AttributeDefinition) {
if parent.Validation == nil {
return
}
if a.Validation == nil {
a.Validation = &dslengine.ValidationDefinition{}
}
a.Validation.AddRequired(parent.Validation.Required)
}
func (a *AttributeDefinition) shouldInherit(parent *AttributeDefinition) bool {
return a != nil && a.Type.ToObject() != nil &&
parent != nil && parent.Type.ToObject() != nil
}
// Context returns the generic definition name used in error messages.
func (c *ContactDefinition) Context() string {
if c.Name != "" {
return fmt.Sprintf("contact %s", c.Name)
}
return "unnamed contact"
}
// Context returns the generic definition name used in error messages.
func (l *LicenseDefinition) Context() string {
if l.Name != "" {
return fmt.Sprintf("license %s", l.Name)
}
return "unnamed license"
}
// Context returns the generic definition name used in error messages.
func (d *DocsDefinition) Context() string {
return fmt.Sprintf("documentation for %s", Design.Name)
}
// Context returns the generic definition name used in error messages.
func (t *UserTypeDefinition) Context() string {
if t.TypeName != "" {
return fmt.Sprintf("type %#v", t.TypeName)
}
return "unnamed type"
}
// DSL returns the initialization DSL.
func (t *UserTypeDefinition) DSL() func() {
return t.DSLFunc
}
// Context returns the generic definition name used in error messages.
func (r *ResponseDefinition) Context() string {
var prefix, suffix string
if r.Name != "" {
prefix = fmt.Sprintf("response %#v", r.Name)
} else {
prefix = "unnamed response"
}
if r.Parent != nil {
suffix = fmt.Sprintf(" of %s", r.Parent.Context())
}
return prefix + suffix
}
// Finalize sets the response media type from its type if the type is a media type and no media
// type is already specified.
func (r *ResponseDefinition) Finalize() {
if r.Type == nil {
return
}
if r.MediaType != "" && r.MediaType != "text/plain" {
return
}
mt, ok := r.Type.(*MediaTypeDefinition)
if !ok {
return
}
r.MediaType = mt.Identifier
}
// Dup returns a copy of the response definition.
func (r *ResponseDefinition) Dup() *ResponseDefinition {
res := ResponseDefinition{
Name: r.Name,
Status: r.Status,
Description: r.Description,
MediaType: r.MediaType,
ViewName: r.ViewName,
}
if r.Headers != nil {
res.Headers = DupAtt(r.Headers)
}
return &res
}
// Merge merges other into target. Only the fields of target that are not already set are merged.
func (r *ResponseDefinition) Merge(other *ResponseDefinition) {
if other == nil {
return
}
if r.Name == "" {
r.Name = other.Name
}
if r.Status == 0 {
r.Status = other.Status
}
if r.Description == "" {
r.Description = other.Description
}
if r.MediaType == "" {
r.MediaType = other.MediaType
r.ViewName = other.ViewName
}
if other.Headers != nil {
otherHeaders := other.Headers.Type.ToObject()
if len(otherHeaders) > 0 {
if r.Headers == nil {
r.Headers = &AttributeDefinition{Type: Object{}}
}
headers := r.Headers.Type.ToObject()
for n, h := range otherHeaders {
if _, ok := headers[n]; !ok {
headers[n] = h
}
}
}
}
}
// Context returns the generic definition name used in error messages.
func (r *ResponseTemplateDefinition) Context() string {
if r.Name != "" {
return fmt.Sprintf("response template %#v", r.Name)
}
return "unnamed response template"
}
// Context returns the generic definition name used in error messages.
func (a *ActionDefinition) Context() string {
var prefix, suffix string
if a.Name != "" {
suffix = fmt.Sprintf("action %#v", a.Name)
} else {
suffix = "unnamed action"
}
if a.Parent != nil {
prefix = a.Parent.Context() + " "
}
return prefix + suffix
}
// PathParams returns the path parameters of the action across all its routes.
func (a *ActionDefinition) PathParams() *AttributeDefinition {
obj := make(Object)
allParams := a.AllParams().Type.ToObject()
for _, r := range a.Routes {
for _, p := range r.Params() {
if _, ok := obj[p]; !ok {
obj[p] = allParams[p]
}
}
}
return &AttributeDefinition{Type: obj}
}
// AllParams returns the path and query string parameters of the action across all its routes.
func (a *ActionDefinition) AllParams() *AttributeDefinition {
var res *AttributeDefinition
if a.Params != nil {
res = DupAtt(a.Params)
} else {
res = &AttributeDefinition{Type: Object{}}
}
if a.HasAbsoluteRoutes() {
return res
}
res = res.Merge(a.Parent.Params)
if p := a.Parent.Parent(); p != nil {
res = res.Merge(p.CanonicalAction().PathParams())
} else {
res = res.Merge(a.Parent.PathParams())
}
return res.Merge(Design.Params)
}
// HasAbsoluteRoutes returns true if all the action routes are absolute.
func (a *ActionDefinition) HasAbsoluteRoutes() bool {
for _, r := range a.Routes {
if !r.IsAbsolute() {
return false
}
}
return true
}
// CanonicalScheme returns the preferred scheme for making requests. Favor secure schemes.
func (a *ActionDefinition) CanonicalScheme() string {
if a.WebSocket() {
for _, s := range a.EffectiveSchemes() {
if s == "wss" {
return s
}
}
return "ws"
}
for _, s := range a.EffectiveSchemes() {
if s == "https" {
return s
}
}
return "http"
}
// EffectiveSchemes return the URL schemes that apply to the action. Looks recursively into action
// resource, parent resources and API.
func (a *ActionDefinition) EffectiveSchemes() []string {
// Compute the schemes
schemes := a.Schemes
if len(schemes) == 0 {
res := a.Parent
schemes = res.Schemes
parent := res.Parent()
for len(schemes) == 0 && parent != nil {
schemes = parent.Schemes
parent = parent.Parent()
}
if len(schemes) == 0 {
schemes = Design.Schemes
}
}
return schemes
}
// WebSocket returns true if the action scheme is "ws" or "wss" or both (directly or inherited
// from the resource or API)
func (a *ActionDefinition) WebSocket() bool {
schemes := a.EffectiveSchemes()
if len(schemes) == 0 {
return false
}
for _, s := range schemes {
if s != "ws" && s != "wss" {
return false
}
}
return true
}
// Finalize inherits security scheme and action responses from parent and top level design.
func (a *ActionDefinition) Finalize() {
// Inherit security scheme
if a.Security == nil {
a.Security = a.Parent.Security // ResourceDefinition
if a.Security == nil {
a.Security = Design.Security
}
}
if a.Security != nil && a.Security.Scheme.Kind == NoSecurityKind {
a.Security = nil
}
if a.Payload != nil {
a.Payload.Finalize()
}
a.mergeResponses()
a.initImplicitParams()
a.initQueryParams()
}
// UserTypes returns all the user types used by the action payload and parameters.
func (a *ActionDefinition) UserTypes() map[string]*UserTypeDefinition {
types := make(map[string]*UserTypeDefinition)
allp := a.AllParams().Type.ToObject()
if a.Payload != nil {
allp["__payload__"] = &AttributeDefinition{Type: a.Payload}
}
for n, ut := range UserTypes(allp) {
types[n] = ut
}
for _, r := range a.Responses {
if mt := Design.MediaTypeWithIdentifier(r.MediaType); mt != nil {
types[mt.TypeName] = mt.UserTypeDefinition
for n, ut := range UserTypes(mt.UserTypeDefinition) {
types[n] = ut
}
}
}
if len(types) == 0 {
return nil
}
return types
}
// IterateHeaders iterates over the resource-level and action-level headers,
// calling the given iterator passing in each response sorted in alphabetical order.
// Iteration stops if an iterator returns an error and in this case IterateHeaders returns that
// error.
func (a *ActionDefinition) IterateHeaders(it HeaderIterator) error {
mergedHeaders := a.Parent.Headers.Merge(a.Headers)
isRequired := func(name string) bool {
// header required in either the Resource or Action scope?
return a.Parent.Headers.IsRequired(name) || a.Headers.IsRequired(name)
}
return iterateHeaders(mergedHeaders, isRequired, it)
}
// IterateResponses calls the given iterator passing in each response sorted in alphabetical order.
// Iteration stops if an iterator returns an error and in this case IterateResponses returns that
// error.
func (a *ActionDefinition) IterateResponses(it ResponseIterator) error {
names := make([]string, len(a.Responses))
i := 0
for n := range a.Responses {
names[i] = n
i++
}
sort.Strings(names)
for _, n := range names {
if err := it(a.Responses[n]); err != nil {
return err
}
}
return nil
}
// mergeResponses merges the parent resource and design responses.
func (a *ActionDefinition) mergeResponses() {
for name, resp := range a.Parent.Responses {
if _, ok := a.Responses[name]; !ok {
if a.Responses == nil {
a.Responses = make(map[string]*ResponseDefinition)
}
a.Responses[name] = resp.Dup()
}
}
for name, resp := range a.Responses {
resp.Finalize()
if pr, ok := a.Parent.Responses[name]; ok {
resp.Merge(pr)
}
if ar, ok := Design.Responses[name]; ok {
resp.Merge(ar)
}
if dr, ok := Design.DefaultResponses[name]; ok {
resp.Merge(dr)
}
}
}
// initImplicitParams creates params for path segments that don't have one.
func (a *ActionDefinition) initImplicitParams() {
for _, ro := range a.Routes {
for _, wc := range ro.Params() {
found := false
search := func(params *AttributeDefinition) {
if params == nil {
return
}
att, ok := params.Type.ToObject()[wc]
if ok {
if a.Params == nil {
a.Params = &AttributeDefinition{Type: Object{}}
}
a.Params.Type.ToObject()[wc] = att
found = true
}
}
search(a.Params)
parent := a.Parent
for !found && parent != nil {
bp := parent.Params
parent = parent.Parent()
search(bp)
}
if found {
continue
}
search(Design.Params)
if found {
continue
}
if a.Params == nil {
a.Params = &AttributeDefinition{Type: Object{}}
}
a.Params.Type.ToObject()[wc] = &AttributeDefinition{Type: String}
}
}
}
// initQueryParams extract the query parameters from the action params.
func (a *ActionDefinition) initQueryParams() {
// 3. Compute QueryParams from Params and set all path params as non zero attributes
if params := a.AllParams(); params != nil {
queryParams := DupAtt(params)
queryParams.Type = Dup(queryParams.Type)
if a.Params == nil {
a.Params = &AttributeDefinition{Type: Object{}}
}
a.Params.NonZeroAttributes = make(map[string]bool)
for _, route := range a.Routes {
pnames := route.Params()
for _, pname := range pnames {
a.Params.NonZeroAttributes[pname] = true
delete(queryParams.Type.ToObject(), pname)
if queryParams.Validation != nil {
req := queryParams.Validation.Required
for i, n := range req {
if n == pname {
queryParams.Validation.Required = append(req[:i], req[i+1:]...)
break
}
}
}
}
}
a.QueryParams = queryParams
}
}
// Context returns the generic definition name used in error messages.
func (f *FileServerDefinition) Context() string {
suffix := fmt.Sprintf("file server %s", f.FilePath)
var prefix string
if f.Parent != nil {
prefix = f.Parent.Context() + " "
}
return prefix + suffix
}
// Finalize inherits security scheme from parent and top level design.
func (f *FileServerDefinition) Finalize() {
// Make sure request path starts with a "/" so codegen can rely on it.
if !strings.HasPrefix(f.RequestPath, "/") {
f.RequestPath = "/" + f.RequestPath
}
// Inherit security
if f.Security == nil {
f.Security = f.Parent.Security // ResourceDefinition
if f.Security == nil {
f.Security = Design.Security
}
}
if f.Security != nil && f.Security.Scheme.Kind == NoSecurityKind {
f.Security = nil
}
}
// IsDir returns true if the file server serves a directory, false otherwise.
func (f *FileServerDefinition) IsDir() bool {
return WildcardRegex.MatchString(f.RequestPath)
}
// ByFilePath makes FileServerDefinition sortable for code generators.
type ByFilePath []*FileServerDefinition
func (b ByFilePath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b ByFilePath) Len() int { return len(b) }
func (b ByFilePath) Less(i, j int) bool { return b[i].FilePath < b[j].FilePath }
// Context returns the generic definition name used in error messages.
func (l *LinkDefinition) Context() string {
var prefix, suffix string
if l.Name != "" {
prefix = fmt.Sprintf("link %#v", l.Name)
} else {
prefix = "unnamed link"
}
if l.Parent != nil {
suffix = fmt.Sprintf(" of %s", l.Parent.Context())
}
return prefix + suffix
}
// Attribute returns the linked attribute.
func (l *LinkDefinition) Attribute() *AttributeDefinition {
p := l.Parent.ToObject()
if p == nil {
return nil
}
att, _ := p[l.Name]
return att
}
// MediaType returns the media type of the linked attribute.
func (l *LinkDefinition) MediaType() *MediaTypeDefinition {
att := l.Attribute()
mt, _ := att.Type.(*MediaTypeDefinition)
return mt
}
// Context returns the generic definition name used in error messages.
func (v *ViewDefinition) Context() string {
var prefix, suffix string
if v.Name != "" {
prefix = fmt.Sprintf("view %#v", v.Name)
} else {
prefix = "unnamed view"
}
if v.Parent != nil {
suffix = fmt.Sprintf(" of %s", v.Parent.Context())
}
return prefix + suffix
}
// Context returns the generic definition name used in error messages.
func (r *RouteDefinition) Context() string {
return fmt.Sprintf(`route %s "%s" of %s`, r.Verb, r.Path, r.Parent.Context())
}
// Params returns the route parameters.
// For example for the route "GET /foo/:fooID" Params returns []string{"fooID"}.
func (r *RouteDefinition) Params() []string {
return ExtractWildcards(r.FullPath())
}
// FullPath returns the action full path computed by concatenating the API and resource base paths
// with the action specific path.
func (r *RouteDefinition) FullPath() string {
if r.IsAbsolute() {
return httppath.Clean(r.Path[1:])
}
var base string
if r.Parent != nil && r.Parent.Parent != nil {
base = r.Parent.Parent.FullPath()
}
joinedPath := path.Join(base, r.Path)
if strings.HasSuffix(r.Path, "/") {
//add slash removed by Join back again (it may be important for routing)
joinedPath += "/"
}
return httppath.Clean(joinedPath)
}
// IsAbsolute returns true if the action path should not be concatenated to the resource and API
// base paths.
func (r *RouteDefinition) IsAbsolute() bool {
return strings.HasPrefix(r.Path, "//")
}
func iterateHeaders(headers *AttributeDefinition, isRequired func(name string) bool, it HeaderIterator) error {
if headers == nil || !headers.Type.IsObject() {
return nil
}
headersMap := headers.Type.ToObject()
names := make([]string, len(headersMap))
i := 0
for n := range headersMap {
names[i] = n
i++
}
sort.Strings(names)
for _, n := range names {
header := headersMap[n]
if err := it(n, isRequired(n), header); err != nil {
return err
}
}
return nil
}
1
https://gitee.com/mirrors/goa.git
git@gitee.com:mirrors/goa.git
mirrors
goa
goa
v1.4.3

Search