github.com/blp1526/goa@v1.4.0/goagen/gen_app/writers.go (about) 1 package genapp 2 3 import ( 4 "fmt" 5 "net/http" 6 "regexp" 7 "strings" 8 "text/template" 9 10 "sort" 11 12 "github.com/goadesign/goa/design" 13 "github.com/goadesign/goa/goagen/codegen" 14 ) 15 16 // WildcardRegex is the regex used to capture path parameters. 17 var WildcardRegex = regexp.MustCompile("(?:[^/]*/:([^/]+))+") 18 19 type ( 20 // ContextsWriter generate codes for a goa application contexts. 21 ContextsWriter struct { 22 *codegen.SourceFile 23 CtxTmpl *template.Template 24 CtxNewTmpl *template.Template 25 CtxRespTmpl *template.Template 26 PayloadTmpl *template.Template 27 Finalizer *codegen.Finalizer 28 Validator *codegen.Validator 29 } 30 31 // ControllersWriter generate code for a goa application handlers. 32 // Handlers receive a HTTP request, create the action context, call the action code and send the 33 // resulting HTTP response. 34 ControllersWriter struct { 35 *codegen.SourceFile 36 CtrlTmpl *template.Template 37 MountTmpl *template.Template 38 handleCORST *template.Template 39 Finalizer *codegen.Finalizer 40 Validator *codegen.Validator 41 } 42 43 // SecurityWriter generate code for action-level security handlers. 44 SecurityWriter struct { 45 *codegen.SourceFile 46 SecurityTmpl *template.Template 47 } 48 49 // ResourcesWriter generate code for a goa application resources. 50 // Resources are data structures initialized by the application handlers and passed to controller 51 // actions. 52 ResourcesWriter struct { 53 *codegen.SourceFile 54 ResourceTmpl *template.Template 55 } 56 57 // MediaTypesWriter generate code for a goa application media types. 58 // Media types are data structures used to render the response bodies. 59 MediaTypesWriter struct { 60 *codegen.SourceFile 61 MediaTypeTmpl *template.Template 62 Validator *codegen.Validator 63 } 64 65 // UserTypesWriter generate code for a goa application user types. 66 // User types are data structures defined in the DSL with "Type". 67 UserTypesWriter struct { 68 *codegen.SourceFile 69 UserTypeTmpl *template.Template 70 Finalizer *codegen.Finalizer 71 Validator *codegen.Validator 72 } 73 74 // ContextTemplateData contains all the information used by the template to render the context 75 // code for an action. 76 ContextTemplateData struct { 77 Name string // e.g. "ListBottleContext" 78 ResourceName string // e.g. "bottles" 79 ActionName string // e.g. "list" 80 Params *design.AttributeDefinition 81 Payload *design.UserTypeDefinition 82 Headers *design.AttributeDefinition 83 Routes []*design.RouteDefinition 84 Responses map[string]*design.ResponseDefinition 85 API *design.APIDefinition 86 DefaultPkg string 87 Security *design.SecurityDefinition 88 } 89 90 // ControllerTemplateData contains the information required to generate an action handler. 91 ControllerTemplateData struct { 92 API *design.APIDefinition // API definition 93 Resource string // Lower case plural resource name, e.g. "bottles" 94 Actions []map[string]interface{} // Array of actions, each action has keys "Name", "DesignName", "Routes", "Context" and "Unmarshal" 95 FileServers []*design.FileServerDefinition // File servers 96 Encoders []*EncoderTemplateData // Encoder data 97 Decoders []*EncoderTemplateData // Decoder data 98 Origins []*design.CORSDefinition // CORS policies 99 PreflightPaths []string 100 } 101 102 // ResourceData contains the information required to generate the resource GoGenerator 103 ResourceData struct { 104 Name string // Name of resource 105 Identifier string // Identifier of resource media type 106 Description string // Description of resource 107 Type *design.MediaTypeDefinition // Type of resource media type 108 CanonicalTemplate string // CanonicalFormat represents the resource canonical path in the form of a fmt.Sprintf format. 109 CanonicalParams []string // CanonicalParams is the list of parameter names that appear in the resource canonical path in order. 110 } 111 112 // EncoderTemplateData contains the data needed to render the registration code for a single 113 // encoder or decoder package. 114 EncoderTemplateData struct { 115 // PackagePath is the Go package path to the package implmenting the encoder/decoder. 116 PackagePath string 117 // PackageName is the name of the Go package implementing the encoder/decoder. 118 PackageName string 119 // Function is the name of the package function implementing the decoder/encoder factory. 120 Function string 121 // MIMETypes is the list of supported MIME types. 122 MIMETypes []string 123 // Default is true if this encoder/decoder should be set as the default. 124 Default bool 125 } 126 ) 127 128 // IsPathParam returns true if the given parameter name corresponds to a path parameter for all 129 // the context action routes. Such parameter is required but does not need to be validated as 130 // httptreemux takes care of that. 131 func (c *ContextTemplateData) IsPathParam(param string) bool { 132 params := c.Params 133 pp := false 134 if params.Type.IsObject() { 135 for _, r := range c.Routes { 136 pp = false 137 for _, p := range r.Params() { 138 if p == param { 139 pp = true 140 break 141 } 142 } 143 if !pp { 144 break 145 } 146 } 147 } 148 return pp 149 } 150 151 // HasParamAndHeader returns true if the generated struct field name for the given header name 152 // matches the generated struct field name of a param in c.Params. 153 func (c *ContextTemplateData) HasParamAndHeader(name string) bool { 154 if c.Params == nil || c.Headers == nil { 155 return false 156 } 157 158 headerAtt := c.Headers.Type.ToObject()[name] 159 headerName := codegen.GoifyAtt(headerAtt, name, true) 160 for paramName, paramAtt := range c.Params.Type.ToObject() { 161 paramName = codegen.GoifyAtt(paramAtt, paramName, true) 162 if headerName == paramName { 163 return true 164 } 165 } 166 return false 167 } 168 169 // MustValidate returns true if code that checks for the presence of the given param must be 170 // generated. 171 func (c *ContextTemplateData) MustValidate(name string) bool { 172 return c.Params.IsRequired(name) && !c.IsPathParam(name) 173 } 174 175 // IterateResponses iterates through the responses sorted by status code. 176 func (c *ContextTemplateData) IterateResponses(it func(*design.ResponseDefinition) error) error { 177 m := make(map[int]*design.ResponseDefinition, len(c.Responses)) 178 var s []int 179 for _, resp := range c.Responses { 180 status := resp.Status 181 m[status] = resp 182 s = append(s, status) 183 } 184 sort.Ints(s) 185 for _, status := range s { 186 if err := it(m[status]); err != nil { 187 return err 188 } 189 } 190 return nil 191 } 192 193 // NewContextsWriter returns a contexts code writer. 194 // Contexts provide the glue between the underlying request data and the user controller. 195 func NewContextsWriter(filename string) (*ContextsWriter, error) { 196 file, err := codegen.SourceFileFor(filename) 197 if err != nil { 198 return nil, err 199 } 200 return &ContextsWriter{ 201 SourceFile: file, 202 Finalizer: codegen.NewFinalizer(), 203 Validator: codegen.NewValidator(), 204 }, nil 205 } 206 207 // Execute writes the code for the context types to the writer. 208 func (w *ContextsWriter) Execute(data *ContextTemplateData) error { 209 if err := w.ExecuteTemplate("context", ctxT, nil, data); err != nil { 210 return err 211 } 212 fn := template.FuncMap{ 213 "newCoerceData": newCoerceData, 214 "arrayAttribute": arrayAttribute, 215 "printVal": codegen.PrintVal, 216 "canonicalHeaderKey": http.CanonicalHeaderKey, 217 "isPathParam": data.IsPathParam, 218 } 219 if err := w.ExecuteTemplate("new", ctxNewT, fn, data); err != nil { 220 return err 221 } 222 if data.Payload != nil { 223 found := false 224 for _, t := range design.Design.Types { 225 if t.TypeName == data.Payload.TypeName { 226 found = true 227 break 228 } 229 } 230 if !found { 231 fn := template.FuncMap{ 232 "finalizeCode": w.Finalizer.Code, 233 "validationCode": w.Validator.Code, 234 } 235 if err := w.ExecuteTemplate("payload", payloadT, fn, data); err != nil { 236 return err 237 } 238 } 239 } 240 return data.IterateResponses(func(resp *design.ResponseDefinition) error { 241 respData := map[string]interface{}{ 242 "Context": data, 243 "Response": resp, 244 } 245 var mt *design.MediaTypeDefinition 246 if resp.Type != nil { 247 var ok bool 248 if mt, ok = resp.Type.(*design.MediaTypeDefinition); !ok { 249 respData["Type"] = resp.Type 250 respData["ContentType"] = resp.MediaType 251 return w.ExecuteTemplate("response", ctxTRespT, nil, respData) 252 } 253 } else { 254 mt = design.Design.MediaTypeWithIdentifier(resp.MediaType) 255 } 256 if mt != nil { 257 var views []string 258 if resp.ViewName != "" { 259 views = []string{resp.ViewName} 260 } else { 261 views = make([]string, len(mt.Views)) 262 i := 0 263 for name := range mt.Views { 264 views[i] = name 265 i++ 266 } 267 sort.Strings(views) 268 } 269 for _, view := range views { 270 projected, _, err := mt.Project(view) 271 if err != nil { 272 return err 273 } 274 respData["Projected"] = projected 275 respData["ViewName"] = view 276 respData["MediaType"] = mt 277 respData["ContentType"] = mt.ContentType 278 if view == "default" { 279 respData["RespName"] = codegen.Goify(resp.Name, true) 280 } else { 281 base := fmt.Sprintf("%s%s", resp.Name, strings.Title(view)) 282 respData["RespName"] = codegen.Goify(base, true) 283 } 284 if err := w.ExecuteTemplate("response", ctxMTRespT, fn, respData); err != nil { 285 return err 286 } 287 } 288 return nil 289 } 290 return w.ExecuteTemplate("response", ctxNoMTRespT, nil, respData) 291 }) 292 } 293 294 // NewControllersWriter returns a handlers code writer. 295 // Handlers provide the glue between the underlying request data and the user controller. 296 func NewControllersWriter(filename string) (*ControllersWriter, error) { 297 file, err := codegen.SourceFileFor(filename) 298 if err != nil { 299 return nil, err 300 } 301 return &ControllersWriter{ 302 SourceFile: file, 303 Finalizer: codegen.NewFinalizer(), 304 Validator: codegen.NewValidator(), 305 }, nil 306 } 307 308 // WriteInitService writes the initService function 309 func (w *ControllersWriter) WriteInitService(encoders, decoders []*EncoderTemplateData) error { 310 ctx := map[string]interface{}{ 311 "API": design.Design, 312 "Encoders": encoders, 313 "Decoders": decoders, 314 } 315 return w.ExecuteTemplate("service", serviceT, nil, ctx) 316 } 317 318 // Execute writes the handlers GoGenerator 319 func (w *ControllersWriter) Execute(data []*ControllerTemplateData) error { 320 if len(data) == 0 { 321 return nil 322 } 323 for _, d := range data { 324 if err := w.ExecuteTemplate("controller", ctrlT, nil, d); err != nil { 325 return err 326 } 327 if err := w.ExecuteTemplate("mount", mountT, nil, d); err != nil { 328 return err 329 } 330 if len(d.Origins) > 0 { 331 if err := w.ExecuteTemplate("handleCORS", handleCORST, nil, d); err != nil { 332 return err 333 } 334 } 335 fn := template.FuncMap{ 336 "newCoerceData": newCoerceData, 337 "finalizeCode": w.Finalizer.Code, 338 "validationCode": w.Validator.Code, 339 } 340 if err := w.ExecuteTemplate("unmarshal", unmarshalT, fn, d); err != nil { 341 return err 342 } 343 } 344 return nil 345 } 346 347 // NewSecurityWriter returns a security functionality code writer. 348 // Those functionalities are there to support action-middleware related to security. 349 func NewSecurityWriter(filename string) (*SecurityWriter, error) { 350 file, err := codegen.SourceFileFor(filename) 351 if err != nil { 352 return nil, err 353 } 354 return &SecurityWriter{SourceFile: file}, nil 355 } 356 357 // Execute adds the different security schemes and middleware supporting functions. 358 func (w *SecurityWriter) Execute(schemes []*design.SecuritySchemeDefinition) error { 359 return w.ExecuteTemplate("security_schemes", securitySchemesT, nil, schemes) 360 } 361 362 // NewResourcesWriter returns a contexts code writer. 363 // Resources provide the glue between the underlying request data and the user controller. 364 func NewResourcesWriter(filename string) (*ResourcesWriter, error) { 365 file, err := codegen.SourceFileFor(filename) 366 if err != nil { 367 return nil, err 368 } 369 return &ResourcesWriter{SourceFile: file}, nil 370 } 371 372 // Execute writes the code for the context types to the writer. 373 func (w *ResourcesWriter) Execute(data *ResourceData) error { 374 return w.ExecuteTemplate("resource", resourceT, nil, data) 375 } 376 377 // NewMediaTypesWriter returns a contexts code writer. 378 // Media types contain the data used to render response bodies. 379 func NewMediaTypesWriter(filename string) (*MediaTypesWriter, error) { 380 file, err := codegen.SourceFileFor(filename) 381 if err != nil { 382 return nil, err 383 } 384 return &MediaTypesWriter{SourceFile: file, Validator: codegen.NewValidator()}, nil 385 } 386 387 // Execute writes the code for the context types to the writer. 388 func (w *MediaTypesWriter) Execute(mt *design.MediaTypeDefinition) error { 389 var ( 390 mLinks *design.UserTypeDefinition 391 fn = template.FuncMap{"validationCode": w.Validator.Code} 392 ) 393 err := mt.IterateViews(func(view *design.ViewDefinition) error { 394 p, links, err := mt.Project(view.Name) 395 if mLinks == nil { 396 mLinks = links 397 } 398 if err != nil { 399 return err 400 } 401 return w.ExecuteTemplate("mediatype", mediaTypeT, fn, p) 402 }) 403 if err != nil { 404 return err 405 } 406 if mLinks != nil { 407 if err := w.ExecuteTemplate("mediatypelink", mediaTypeLinkT, fn, mLinks); err != nil { 408 return err 409 } 410 } 411 return nil 412 } 413 414 // NewUserTypesWriter returns a contexts code writer. 415 // User types contain custom data structured defined in the DSL with "Type". 416 func NewUserTypesWriter(filename string) (*UserTypesWriter, error) { 417 file, err := codegen.SourceFileFor(filename) 418 if err != nil { 419 return nil, err 420 } 421 return &UserTypesWriter{ 422 SourceFile: file, 423 Finalizer: codegen.NewFinalizer(), 424 Validator: codegen.NewValidator(), 425 }, nil 426 } 427 428 // Execute writes the code for the context types to the writer. 429 func (w *UserTypesWriter) Execute(t *design.UserTypeDefinition) error { 430 fn := template.FuncMap{ 431 "finalizeCode": w.Finalizer.Code, 432 "validationCode": w.Validator.Code, 433 } 434 return w.ExecuteTemplate("types", userTypeT, fn, t) 435 } 436 437 // newCoerceData is a helper function that creates a map that can be given to the "Coerce" template. 438 func newCoerceData(name string, att *design.AttributeDefinition, pointer bool, pkg string, depth int) map[string]interface{} { 439 return map[string]interface{}{ 440 "Name": name, 441 "VarName": codegen.Goify(name, false), 442 "Pointer": pointer, 443 "Attribute": att, 444 "Pkg": pkg, 445 "Depth": depth, 446 } 447 } 448 449 // arrayAttribute returns the array element attribute definition. 450 func arrayAttribute(a *design.AttributeDefinition) *design.AttributeDefinition { 451 return a.Type.(*design.Array).ElemType 452 } 453 454 const ( 455 // ctxT generates the code for the context data type. 456 // template input: *ContextTemplateData 457 ctxT = `// {{ .Name }} provides the {{ .ResourceName }} {{ .ActionName }} action context. 458 type {{ .Name }} struct { 459 context.Context 460 *goa.ResponseData 461 *goa.RequestData 462 {{ if .Headers }}{{ range $name, $att := .Headers.Type.ToObject }}{{ if not ($.HasParamAndHeader $name) }}{{/* 463 */}} {{ goifyatt $att $name true }} {{ if and $att.Type.IsPrimitive ($.Headers.IsPrimitivePointer $name) }}*{{ end }}{{ gotyperef .Type nil 0 false }} 464 {{ end }}{{ end }}{{ end }}{{ if .Params }}{{ range $name, $att := .Params.Type.ToObject }}{{/* 465 */}} {{ goifyatt $att $name true }} {{ if and $att.Type.IsPrimitive ($.Params.IsPrimitivePointer $name) }}*{{ end }}{{ gotyperef .Type nil 0 false }} 466 {{ end }}{{ end }}{{ if .Payload }} Payload {{ gotyperef .Payload nil 0 false }} 467 {{ end }}} 468 ` 469 // coerceT generates the code that coerces the generic deserialized 470 // data to the actual type. 471 // template input: map[string]interface{} as returned by newCoerceData 472 coerceT = `{{ if eq .Attribute.Type.Kind 1 }}{{/* 473 474 */}}{{/* BooleanType */}}{{/* 475 */}}{{ $varName := or (and (not .Pointer) .VarName) tempvar }}{{/* 476 */}}{{ tabs .Depth }}if {{ .VarName }}, err2 := strconv.ParseBool(raw{{ goify .Name true }}); err2 == nil { 477 {{ if .Pointer }}{{ tabs .Depth }} {{ $varName }} := &{{ .VarName }} 478 {{ end }}{{ tabs .Depth }} {{ .Pkg }} = {{ $varName }} 479 {{ tabs .Depth }}} else { 480 {{ tabs .Depth }} err = goa.MergeErrors(err, goa.InvalidParamTypeError("{{ .Name }}", raw{{ goify .Name true }}, "boolean")) 481 {{ tabs .Depth }}} 482 {{ end }}{{ if eq .Attribute.Type.Kind 2 }}{{/* 483 484 */}}{{/* IntegerType */}}{{/* 485 */}}{{ $tmp := tempvar }}{{/* 486 */}}{{ tabs .Depth }}if {{ .VarName }}, err2 := strconv.Atoi(raw{{ goify .Name true }}); err2 == nil { 487 {{ if .Pointer }}{{ $tmp2 := tempvar }}{{ tabs .Depth }} {{ $tmp2 }} := {{ .VarName }} 488 {{ tabs .Depth }} {{ $tmp }} := &{{ $tmp2 }} 489 {{ tabs .Depth }} {{ .Pkg }} = {{ $tmp }} 490 {{ else }}{{ tabs .Depth }} {{ .Pkg }} = {{ .VarName }} 491 {{ end }}{{ tabs .Depth }}} else { 492 {{ tabs .Depth }} err = goa.MergeErrors(err, goa.InvalidParamTypeError("{{ .Name }}", raw{{ goify .Name true }}, "integer")) 493 {{ tabs .Depth }}} 494 {{ end }}{{ if eq .Attribute.Type.Kind 3 }}{{/* 495 496 */}}{{/* NumberType */}}{{/* 497 */}}{{ $varName := or (and (not .Pointer) .VarName) tempvar }}{{/* 498 */}}{{ tabs .Depth }}if {{ .VarName }}, err2 := strconv.ParseFloat(raw{{ goify .Name true }}, 64); err2 == nil { 499 {{ if .Pointer }}{{ tabs .Depth }} {{ $varName }} := &{{ .VarName }} 500 {{ end }}{{ tabs .Depth }} {{ .Pkg }} = {{ $varName }} 501 {{ tabs .Depth }}} else { 502 {{ tabs .Depth }} err = goa.MergeErrors(err, goa.InvalidParamTypeError("{{ .Name }}", raw{{ goify .Name true }}, "number")) 503 {{ tabs .Depth }}} 504 {{ end }}{{ if eq .Attribute.Type.Kind 4 }}{{/* 505 506 */}}{{/* StringType */}}{{/* 507 */}}{{ tabs .Depth }}{{ .Pkg }} = {{ if .Pointer }}&{{ end }}raw{{ goify .Name true }} 508 {{ end }}{{ if eq .Attribute.Type.Kind 5 }}{{/* 509 510 */}}{{/* DateTimeType */}}{{/* 511 */}}{{ $varName := or (and (not .Pointer) .VarName) tempvar }}{{/* 512 */}}{{ tabs .Depth }}if {{ .VarName }}, err2 := time.Parse(time.RFC3339, raw{{ goify .Name true }}); err2 == nil { 513 {{ if .Pointer }}{{ tabs .Depth }} {{ $varName }} := &{{ .VarName }} 514 {{ end }}{{ tabs .Depth }} {{ .Pkg }} = {{ $varName }} 515 {{ tabs .Depth }}} else { 516 {{ tabs .Depth }} err = goa.MergeErrors(err, goa.InvalidParamTypeError("{{ .Name }}", raw{{ goify .Name true }}, "datetime")) 517 {{ tabs .Depth }}} 518 {{ end }}{{ if eq .Attribute.Type.Kind 6 }}{{/* 519 520 */}}{{/* UUIDType */}}{{/* 521 */}}{{ $varName := or (and (not .Pointer) .VarName) tempvar }}{{/* 522 */}}{{ tabs .Depth }}if {{ .VarName }}, err2 := uuid.FromString(raw{{ goify .Name true }}); err2 == nil { 523 {{ if .Pointer }}{{ tabs .Depth }} {{ $varName }} := &{{ .VarName }} 524 {{ end }}{{ tabs .Depth }} {{ .Pkg }} = {{ $varName }} 525 {{ tabs .Depth }}} else { 526 {{ tabs .Depth }} err = goa.MergeErrors(err, goa.InvalidParamTypeError("{{ .Name }}", raw{{ goify .Name true }}, "uuid")) 527 {{ tabs .Depth }}} 528 {{ end }}{{ if eq .Attribute.Type.Kind 7 }}{{/* 529 530 */}}{{/* AnyType */}}{{/* 531 */}}{{ if .Pointer }}{{ $tmp := tempvar }}{{ tabs .Depth }}{{ $tmp }} := interface{}(raw{{ goify .Name true }}) 532 {{ tabs .Depth }}{{ .Pkg }} = &{{ $tmp }} 533 {{ else }}{{ tabs .Depth }}{{ .Pkg }} = raw{{ goify .Name true }} 534 {{ end }}{{ end }}{{ if eq .Attribute.Type.Kind 13 }}{{/* 535 536 */}}{{/* FileType */}}{{/* 537 */}}{{ tabs .Depth }}if err2 == nil { 538 {{ tabs .Depth }} {{ .Pkg }} = {{ printf "raw%s" (goify .VarName true) }} 539 {{ tabs .Depth }}} else { 540 {{ tabs .Depth }} err = goa.MergeErrors(err, goa.InvalidParamTypeError("{{ .Name }}", "{{ .Name }}", "file")) 541 {{ tabs .Depth }}} 542 {{ end }}` 543 544 // ctxNewT generates the code for the context factory method. 545 // template input: *ContextTemplateData 546 ctxNewT = `{{ define "Coerce" }}` + coerceT + `{{ end }}` + ` 547 // New{{ goify .Name true }} parses the incoming request URL and body, performs validations and creates the 548 // context used by the {{ .ResourceName }} controller {{ .ActionName }} action. 549 func New{{ .Name }}(ctx context.Context, r *http.Request, service *goa.Service) (*{{ .Name }}, error) { 550 var err error 551 resp := goa.ContextResponse(ctx) 552 resp.Service = service 553 req := goa.ContextRequest(ctx) 554 req.Request = r 555 rctx := {{ .Name }}{Context: ctx, ResponseData: resp, RequestData: req}{{/* 556 */}} 557 {{ if .Headers }}{{ range $name, $att := .Headers.Type.ToObject }} header{{ goify $name true }} := req.Header["{{ canonicalHeaderKey $name }}"] 558 {{ $mustValidate := $.Headers.IsRequired $name }}{{ if $mustValidate }} if len(header{{ goify $name true }}) == 0 { 559 err = goa.MergeErrors(err, goa.MissingHeaderError("{{ $name }}")) 560 } else { 561 {{ else }} if len(header{{ goify $name true }}) > 0 { 562 {{ end }}{{/* if $mustValidate */}}{{ if $att.Type.IsArray }} req.Params["{{ $name }}"] = header{{ goify $name true }} 563 {{ if eq (arrayAttribute $att).Type.Kind 4 }} headers := header{{ goify $name true }} 564 {{ else }} headers := make({{ gotypedef $att 2 true false }}, len(header{{ goify $name true }})) 565 for i, raw{{ goify $name true}} := range header{{ goify $name true}} { 566 {{ template "Coerce" (newCoerceData $name (arrayAttribute $att) ($.Headers.IsPrimitivePointer $name) "headers[i]" 3) }}{{/* 567 */}} } 568 {{ end }} {{ printf "rctx.%s" (goifyatt $att $name true) }} = headers 569 {{ else }} raw{{ goify $name true}} := header{{ goify $name true}}[0] 570 req.Params["{{ $name }}"] = []string{raw{{ goify $name true }}} 571 {{ template "Coerce" (newCoerceData $name $att ($.Headers.IsPrimitivePointer $name) (printf "rctx.%s" (goifyatt $att $name true)) 2) }}{{ end }}{{/* 572 */}}{{ $validation := validationChecker $att ($.Headers.IsNonZero $name) ($.Headers.IsRequired $name) ($.Headers.HasDefaultValue $name) (printf "rctx.%s" (goifyatt $att $name true)) $name 2 false }}{{/* 573 */}}{{ if $validation }}{{ $validation }} 574 {{ end }} } 575 {{ end }}{{ end }}{{/* if .Headers }}{{/* 576 577 */}}{{ if .Params }}{{ range $name, $att := .Params.Type.ToObject }}{{/* 578 */}} param{{ goify $name true }} := req.Params["{{ $name }}"] 579 {{ $mustValidate := $.MustValidate $name }}{{ if $mustValidate }} if len(param{{ goify $name true }}) == 0 { 580 {{ if $.Params.HasDefaultValue $name }}{{printf "rctx.%s" (goifyatt $att $name true) }} = {{ printVal $att.Type $att.DefaultValue }}{{else}}{{/* 581 */}}err = goa.MergeErrors(err, goa.MissingParamError("{{ $name }}")){{end}} 582 } else { 583 {{ else }}{{ if $.Params.HasDefaultValue $name }} if len(param{{ goify $name true }}) == 0 { 584 {{printf "rctx.%s" (goifyatt $att $name true) }} = {{ printVal $att.Type $att.DefaultValue }} 585 } else { 586 {{ else }} if len(param{{ goify $name true }}) > 0 { 587 {{ end }}{{ end }}{{/* if $mustValidate */}}{{ if $att.Type.IsArray }}{{ if eq (arrayAttribute $att).Type.Kind 4 }} params := param{{ goify $name true }} 588 {{ else }} params := make({{ gotypedef $att 2 true false }}, len(param{{ goify $name true }})) 589 for i, raw{{ goify $name true}} := range param{{ goify $name true}} { 590 {{ template "Coerce" (newCoerceData $name (arrayAttribute $att) ($.Params.IsPrimitivePointer $name) "params[i]" 3) }}{{/* 591 */}} } 592 {{ end }} {{ printf "rctx.%s" (goifyatt $att $name true) }} = params 593 {{ else }} raw{{ goify $name true}} := param{{ goify $name true}}[0] 594 {{ template "Coerce" (newCoerceData $name $att ($.Params.IsPrimitivePointer $name) (printf "rctx.%s" (goifyatt $att $name true)) 2) }}{{ end }}{{/* 595 */}}{{ if $att.Type.IsArray }}{{ $validation := validationChecker (arrayAttribute $att) true true false "param" (printf "%s[0]" $name) 2 false }}{{/* 596 */}}{{ if $validation }}for _, param := range {{ printf "rctx.%s" (goifyatt $att $name true) }} { 597 {{ $validation }} 598 }{{ end }}{{/* 599 */}}{{ else }}{{ $validation := validationChecker $att ($.Params.IsNonZero $name) ($.Params.IsRequired $name) ($.Params.HasDefaultValue $name) (printf "rctx.%s" (goifyatt $att $name true)) $name 2 false }}{{/* 600 */}}{{ if $validation }}{{ $validation }}{{ end }}{{ end }} } 601 {{ end }}{{ end }}{{/* if .Params */}} return &rctx, err 602 } 603 ` 604 605 // ctxMTRespT generates the response helpers for responses with media types. 606 // template input: map[string]interface{} 607 ctxMTRespT = `// {{ goify .RespName true }} sends a HTTP response with status code {{ .Response.Status }}. 608 func (ctx *{{ .Context.Name }}) {{ goify .RespName true }}(r {{ gotyperef .Projected .Projected.AllRequired 0 false }}) error { 609 if ctx.ResponseData.Header().Get("Content-Type") == "" { 610 ctx.ResponseData.Header().Set("Content-Type", "{{ .ContentType }}") 611 } 612 {{ if .Projected.Type.IsArray }} if r == nil { 613 r = {{ gotyperef .Projected .Projected.AllRequired 0 false }}{} 614 } 615 {{ end }} return ctx.ResponseData.Service.Send(ctx.Context, {{ .Response.Status }}, r) 616 } 617 ` 618 619 // ctxTRespT generates the response helpers for responses with overridden types. 620 // template input: map[string]interface{} 621 ctxTRespT = `// {{ goify .Response.Name true }} sends a HTTP response with status code {{ .Response.Status }}. 622 func (ctx *{{ .Context.Name }}) {{ goify .Response.Name true }}(r {{ gotyperef .Type nil 0 false }}) error { 623 if ctx.ResponseData.Header().Get("Content-Type") == "" { 624 ctx.ResponseData.Header().Set("Content-Type", "{{ .ContentType }}") 625 } 626 return ctx.ResponseData.Service.Send(ctx.Context, {{ .Response.Status }}, r) 627 } 628 ` 629 630 // ctxNoMTRespT generates the response helpers for responses with no known media type. 631 // template input: *ContextTemplateData 632 ctxNoMTRespT = ` 633 // {{ goify .Response.Name true }} sends a HTTP response with status code {{ .Response.Status }}. 634 func (ctx *{{ .Context.Name }}) {{ goify .Response.Name true }}({{ if .Response.MediaType }}resp []byte{{ end }}) error { 635 {{ if .Response.MediaType }} if ctx.ResponseData.Header().Get("Content-Type") == "" { 636 ctx.ResponseData.Header().Set("Content-Type", "{{ .Response.MediaType }}") 637 } 638 {{ end }} ctx.ResponseData.WriteHeader({{ .Response.Status }}){{ if .Response.MediaType }} 639 _, err := ctx.ResponseData.Write(resp) 640 return err{{ else }} 641 return nil{{ end }} 642 } 643 ` 644 645 // payloadT generates the payload type definition GoGenerator 646 // template input: *ContextTemplateData 647 payloadT = `{{ $payload := .Payload }}{{ if .Payload.IsObject }}// {{ gotypename .Payload nil 0 true }} is the {{ .ResourceName }} {{ .ActionName }} action payload.{{/* 648 */}}{{ $privateTypeName := gotypename .Payload nil 1 true }} 649 type {{ $privateTypeName }} {{ gotypedef .Payload 0 true true }} 650 651 {{ $assignment := finalizeCode .Payload.AttributeDefinition "payload" 1 }}{{ if $assignment }}// Finalize sets the default values defined in the design. 652 func (payload {{ gotyperef .Payload .Payload.AllRequired 0 true }}) Finalize() { 653 {{ $assignment }} 654 }{{ end }} 655 656 {{ $validation := validationCode .Payload.AttributeDefinition false false false "payload" "raw" 1 true }}{{ if $validation }}// Validate runs the validation rules defined in the design. 657 func (payload {{ gotyperef .Payload .Payload.AllRequired 0 true }}) Validate() (err error) { 658 {{ $validation }} 659 return 660 }{{ end }} 661 {{ $typeName := gotypename .Payload .Payload.AllRequired 1 false }} 662 // Publicize creates {{ $typeName }} from {{ $privateTypeName }} 663 func (payload {{ gotyperef .Payload .Payload.AllRequired 0 true }}) Publicize() {{ gotyperef .Payload .Payload.AllRequired 0 false }} { 664 var pub {{ $typeName }} 665 {{ recursivePublicizer .Payload.AttributeDefinition "payload" "pub" 1 }} 666 return &pub 667 }{{ end }} 668 669 // {{ gotypename .Payload nil 0 false }} is the {{ .ResourceName }} {{ .ActionName }} action payload. 670 type {{ gotypename .Payload nil 1 false }} {{ gotypedef .Payload 0 true false }} 671 672 {{ $validation := validationCode .Payload.AttributeDefinition false false false "payload" "raw" 1 false }}{{ if $validation }}// Validate runs the validation rules defined in the design. 673 func (payload {{ gotyperef .Payload .Payload.AllRequired 0 false }}) Validate() (err error) { 674 {{ $validation }} 675 return 676 }{{ end }} 677 ` 678 // ctrlT generates the controller interface for a given resource. 679 // template input: *ControllerTemplateData 680 ctrlT = `// {{ .Resource }}Controller is the controller interface for the {{ .Resource }} actions. 681 type {{ .Resource }}Controller interface { 682 goa.Muxer 683 {{ if .FileServers }} goa.FileServer 684 {{ end }}{{ range .Actions }} {{ .Name }}(*{{ .Context }}) error 685 {{ end }}} 686 ` 687 688 // serviceT generates the service initialization code. 689 // template input: *ControllerTemplateData 690 serviceT = ` 691 // initService sets up the service encoders, decoders and mux. 692 func initService(service *goa.Service) { 693 // Setup encoders and decoders 694 {{ range .Encoders }}{{/* 695 */}} service.Encoder.Register({{ .PackageName }}.{{ .Function }}, "{{ join .MIMETypes "\", \"" }}") 696 {{ end }}{{ range .Decoders }}{{/* 697 */}} service.Decoder.Register({{ .PackageName }}.{{ .Function }}, "{{ join .MIMETypes "\", \"" }}") 698 {{ end }} 699 700 // Setup default encoder and decoder 701 {{ range .Encoders }}{{ if .Default }}{{/* 702 */}} service.Encoder.Register({{ .PackageName }}.{{ .Function }}, "*/*") 703 {{ end }}{{ end }}{{ range .Decoders }}{{ if .Default }}{{/* 704 */}} service.Decoder.Register({{ .PackageName }}.{{ .Function }}, "*/*") 705 {{ end }}{{ end }}} 706 ` 707 708 // mountT generates the code for a resource "Mount" function. 709 // template input: *ControllerTemplateData 710 mountT = ` 711 // Mount{{ .Resource }}Controller "mounts" a {{ .Resource }} resource controller on the given service. 712 func Mount{{ .Resource }}Controller(service *goa.Service, ctrl {{ .Resource }}Controller) { 713 initService(service) 714 var h goa.Handler 715 {{ $res := .Resource }}{{ if .Origins }}{{ range .PreflightPaths }}{{/* 716 */}} service.Mux.Handle("OPTIONS", {{ printf "%q" . }}, ctrl.MuxHandler("preflight", handle{{ $res }}Origin(cors.HandlePreflight()), nil)) 717 {{ end }}{{ end }}{{ range .Actions }}{{ $action := . }} 718 h = func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 719 // Check if there was an error loading the request 720 if err := goa.ContextError(ctx); err != nil { 721 return err 722 } 723 // Build the context 724 rctx, err := New{{ .Context }}(ctx, req, service) 725 if err != nil { 726 return err 727 } 728 {{ if .Payload }} // Build the payload 729 if rawPayload := goa.ContextRequest(ctx).Payload; rawPayload != nil { 730 rctx.Payload = rawPayload.({{ gotyperef .Payload nil 1 false }}) 731 {{ if not .PayloadOptional }} } else { 732 return goa.MissingPayloadError() 733 {{ end }} } 734 {{ end }} return ctrl.{{ .Name }}(rctx) 735 } 736 {{ if .Security }} h = handleSecurity({{ printf "%q" .Security.Scheme.SchemeName }}, h{{ range .Security.Scopes }}, {{ printf "%q" . }}{{ end }}) 737 {{ end }}{{ if $.Origins }} h = handle{{ $res }}Origin(h) 738 {{ end }}{{ range .Routes }} service.Mux.Handle("{{ .Verb }}", {{ printf "%q" .FullPath }}, ctrl.MuxHandler({{ printf "%q" $action.DesignName }}, h, {{ if $action.Payload }}{{ $action.Unmarshal }}{{ else }}nil{{ end }})) 739 service.LogInfo("mount", "ctrl", {{ printf "%q" $res }}, "action", {{ printf "%q" $action.Name }}, "route", {{ printf "%q" (printf "%s %s" .Verb .FullPath) }}{{ with $action.Security }}, "security", {{ printf "%q" .Scheme.SchemeName }}{{ end }}) 740 {{ end }}{{ end }}{{ range .FileServers }} 741 h = ctrl.FileHandler({{ printf "%q" .RequestPath }}, {{ printf "%q" .FilePath }}) 742 {{ if .Security }} h = handleSecurity({{ printf "%q" .Security.Scheme.SchemeName }}, h{{ range .Security.Scopes }}, {{ printf "%q" . }}{{ end }}) 743 {{ end }}{{ if $.Origins }} h = handle{{ $res }}Origin(h) 744 {{ end }} service.Mux.Handle("GET", "{{ .RequestPath }}", ctrl.MuxHandler("serve", h, nil)) 745 service.LogInfo("mount", "ctrl", {{ printf "%q" $res }}, "files", {{ printf "%q" .FilePath }}, "route", {{ printf "%q" (printf "GET %s" .RequestPath) }}{{ with .Security }}, "security", {{ printf "%q" .Scheme.SchemeName }}{{ end }}) 746 {{ end }}} 747 ` 748 749 // handleCORST generates the code that checks whether a CORS request is authorized 750 // template input: *ControllerTemplateData 751 handleCORST = `// handle{{ .Resource }}Origin applies the CORS response headers corresponding to the origin. 752 func handle{{ .Resource }}Origin(h goa.Handler) goa.Handler { 753 {{ range $i, $policy := .Origins }}{{ if $policy.Regexp }} spec{{$i}} := regexp.MustCompile({{ printf "%q" $policy.Origin }}) 754 {{ end }}{{ end }} 755 return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 756 origin := req.Header.Get("Origin") 757 if origin == "" { 758 // Not a CORS request 759 return h(ctx, rw, req) 760 } 761 {{ range $i, $policy := .Origins }} {{ if $policy.Regexp }}if cors.MatchOriginRegexp(origin, spec{{$i}}){{else}}if cors.MatchOrigin(origin, {{ printf "%q" $policy.Origin }}){{end}} { 762 ctx = goa.WithLogContext(ctx, "origin", origin) 763 rw.Header().Set("Access-Control-Allow-Origin", origin) 764 {{ if not (eq $policy.Origin "*") }} rw.Header().Set("Vary", "Origin") 765 {{ end }}{{ if $policy.Exposed }} rw.Header().Set("Access-Control-Expose-Headers", "{{ join $policy.Exposed ", " }}") 766 {{ end }}{{ if gt $policy.MaxAge 0 }} rw.Header().Set("Access-Control-Max-Age", "{{ $policy.MaxAge }}") 767 {{ end }} rw.Header().Set("Access-Control-Allow-Credentials", "{{ $policy.Credentials }}") 768 if acrm := req.Header.Get("Access-Control-Request-Method"); acrm != "" { 769 // We are handling a preflight request 770 {{ if $policy.Methods }} rw.Header().Set("Access-Control-Allow-Methods", "{{ join $policy.Methods ", " }}") 771 {{ end }}{{ if $policy.Headers }} rw.Header().Set("Access-Control-Allow-Headers", "{{ join $policy.Headers ", " }}") 772 {{ end }} } 773 return h(ctx, rw, req) 774 } 775 {{ end }} 776 return h(ctx, rw, req) 777 } 778 } 779 ` 780 781 // unmarshalT generates the code for an action payload unmarshal function. 782 // template input: *ControllerTemplateData 783 unmarshalT = `{{ define "Coerce" }}` + coerceT + `{{ end }}` + `{{ range .Actions }}{{ if .Payload }} 784 // {{ .Unmarshal }} unmarshals the request body into the context request data Payload field. 785 func {{ .Unmarshal }}(ctx context.Context, service *goa.Service, req *http.Request) error { 786 {{ if .PayloadMultipart}}var err error 787 var payload {{ gotypename .Payload nil 1 true }} 788 {{ $o := .Payload.ToObject }}{{ range $name, $att := $o -}} 789 {{ if eq $att.Type.Kind 13 }}_, raw{{ goify $name true }}, err2 := req.FormFile("{{ $name }}"){{ else }}{{/* 790 */}} raw{{ goify $name true }} := req.FormValue("{{ $name }}"){{ end }} 791 {{ template "Coerce" (newCoerceData $name $att true (printf "payload.%s" (goifyatt $att $name true)) 1) }}{{ end }}{{/* 792 */}} if err != nil { 793 return err 794 }{{ else if .Payload.IsObject }}payload := &{{ gotypename .Payload nil 1 true }}{} 795 if err := service.DecodeRequest(req, payload); err != nil { 796 return err 797 }{{ $assignment := finalizeCode .Payload.AttributeDefinition "payload" 1 }}{{ if $assignment }} 798 payload.Finalize(){{ end }}{{ else }}var payload {{ gotypename .Payload nil 1 false }} 799 if err := service.DecodeRequest(req, &payload); err != nil { 800 return err 801 }{{ end }}{{ $validation := validationCode .Payload.AttributeDefinition false false false "payload" "raw" 1 true }}{{ if $validation }} 802 if err := payload.Validate(); err != nil { 803 // Initialize payload with private data structure so it can be logged 804 goa.ContextRequest(ctx).Payload = payload 805 return err 806 }{{ end }} 807 goa.ContextRequest(ctx).Payload = payload{{ if .Payload.IsObject }}.Publicize(){{ end }} 808 return nil 809 } 810 {{ end }} 811 {{ end }}` 812 813 // resourceT generates the code for a resource. 814 // template input: *ResourceData 815 resourceT = `{{ if .CanonicalTemplate }}// {{ .Name }}Href returns the resource href. 816 func {{ .Name }}Href({{ if .CanonicalParams }}{{ join .CanonicalParams ", " }} interface{}{{ end }}) string { 817 {{ range $param := .CanonicalParams }} param{{$param}} := strings.TrimLeftFunc(fmt.Sprintf("%v", {{$param}}), func(r rune) bool { return r == '/' }) 818 {{ end }}{{ if .CanonicalParams }} return fmt.Sprintf("{{ .CanonicalTemplate }}", param{{ join .CanonicalParams ", param" }}) 819 {{ else }} return "{{ .CanonicalTemplate }}" 820 {{ end }}} 821 {{ end }}` 822 823 // mediaTypeT generates the code for a media type. 824 // template input: MediaTypeTemplateData 825 mediaTypeT = `// {{ gotypedesc . true }} 826 // 827 // Identifier: {{ .Identifier }}{{ $typeName := gotypename . .AllRequired 0 false }} 828 type {{ $typeName }} {{ gotypedef . 0 true false }} 829 830 {{ $validation := validationCode .AttributeDefinition false false false "mt" "response" 1 false }}{{ if $validation }}// Validate validates the {{$typeName}} media type instance. 831 func (mt {{ gotyperef . .AllRequired 0 false }}) Validate() (err error) { 832 {{ $validation }} 833 return 834 } 835 {{ end }} 836 ` 837 838 // mediaTypeLinkT generates the code for a media type link. 839 // template input: MediaTypeLinkTemplateData 840 mediaTypeLinkT = `// {{ gotypedesc . true }}{{ $typeName := gotypename . .AllRequired 0 false }} 841 type {{ $typeName }} {{ gotypedef . 0 true false }} 842 {{ $validation := validationCode .AttributeDefinition false false false "ut" "response" 1 false }}{{ if $validation }}// Validate validates the {{$typeName}} type instance. 843 func (ut {{ gotyperef . .AllRequired 0 false }}) Validate() (err error) { 844 {{ $validation }} 845 return 846 }{{ end }} 847 ` 848 849 // userTypeT generates the code for a user type. 850 // template input: UserTypeTemplateData 851 userTypeT = `// {{ gotypedesc . false }}{{ $privateTypeName := gotypename . .AllRequired 0 true }} 852 type {{ $privateTypeName }} {{ gotypedef . 0 true true }} 853 {{ $assignment := finalizeCode .AttributeDefinition "ut" 1 }}{{ if $assignment }}// Finalize sets the default values for {{$privateTypeName}} type instance. 854 func (ut {{ gotyperef . .AllRequired 0 true }}) Finalize() { 855 {{ $assignment }} 856 }{{ end }} 857 {{ $validation := validationCode .AttributeDefinition false false false "ut" "request" 1 true }}{{ if $validation }}// Validate validates the {{$privateTypeName}} type instance. 858 func (ut {{ gotyperef . .AllRequired 0 true }}) Validate() (err error) { 859 {{ $validation }} 860 return 861 }{{ end }} 862 {{ $typeName := gotypename . .AllRequired 0 false }} 863 // Publicize creates {{ $typeName }} from {{ $privateTypeName }} 864 func (ut {{ gotyperef . .AllRequired 0 true }}) Publicize() {{ gotyperef . .AllRequired 0 false }} { 865 var pub {{ gotypename . .AllRequired 0 false }} 866 {{ recursivePublicizer .AttributeDefinition "ut" "pub" 1 }} 867 return &pub 868 } 869 870 // {{ gotypedesc . true }} 871 type {{ $typeName }} {{ gotypedef . 0 true false }} 872 {{ $validation := validationCode .AttributeDefinition false false false "ut" "type" 1 false }}{{ if $validation }}// Validate validates the {{$typeName}} type instance. 873 func (ut {{ gotyperef . .AllRequired 0 false }}) Validate() (err error) { 874 {{ $validation }} 875 return 876 }{{ end }} 877 ` 878 879 // securitySchemesT generates the code for the security module. 880 // template input: []*design.SecuritySchemeDefinition 881 securitySchemesT = ` 882 type ( 883 // Private type used to store auth handler info in request context 884 authMiddlewareKey string 885 ) 886 887 {{ range . }} 888 {{ $funcName := printf "Use%sMiddleware" (goify .SchemeName true) }}// {{ $funcName }} mounts the {{ .SchemeName }} auth middleware onto the service. 889 func {{ $funcName }}(service *goa.Service, middleware goa.Middleware) { 890 service.Context = context.WithValue(service.Context, authMiddlewareKey({{ printf "%q" .SchemeName }}), middleware) 891 } 892 893 {{ $funcName := printf "New%sSecurity" (goify .SchemeName true) }}// {{ $funcName }} creates a {{ .SchemeName }} security definition. 894 func {{ $funcName }}() *goa.{{ .Context }} { 895 def := goa.{{ .Context }}{ 896 {{ if eq .Context "APIKeySecurity" }}{{/* 897 */}} In: {{ if eq .In "header" }}goa.LocHeader{{ else }}goa.LocQuery{{ end }}, 898 Name: {{ printf "%q" .Name }}, 899 {{ else if eq .Context "OAuth2Security" }}{{/* 900 */}} Flow: {{ printf "%q" .Flow }}, 901 TokenURL: {{ printf "%q" .TokenURL }}, 902 AuthorizationURL: {{ printf "%q" .AuthorizationURL }},{{ with .Scopes }} 903 Scopes: map[string]string{ 904 {{ range $k, $v := . }} {{ printf "%q" $k }}: {{ printf "%q" $v }}, 905 {{ end }}{{/* 906 */}} },{{ end }}{{/* 907 */}}{{ else if eq .Context "BasicAuthSecurity" }}{{/* 908 */}}{{ else if eq .Context "JWTSecurity" }}{{/* 909 */}} In: {{ if eq .In "header" }}goa.LocHeader{{ else }}goa.LocQuery{{ end }}, 910 Name: {{ printf "%q" .Name }}, 911 TokenURL: {{ printf "%q" .TokenURL }},{{ with .Scopes }} 912 Scopes: map[string]string{ 913 {{ range $k, $v := . }} {{ printf "%q" $k }}: {{ printf "%q" $v }}, 914 {{ end }}{{/* 915 */}} },{{ end }} 916 {{ end }}{{/* 917 */}} } 918 {{ if .Description }} def.Description = {{ printf "%q" .Description }} 919 {{ end }} return &def 920 } 921 922 {{ end }}// handleSecurity creates a handler that runs the auth middleware for the security scheme. 923 func handleSecurity(schemeName string, h goa.Handler, scopes ...string) goa.Handler { 924 return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { 925 scheme := ctx.Value(authMiddlewareKey(schemeName)) 926 am, ok := scheme.(goa.Middleware) 927 if !ok { 928 return goa.NoAuthMiddleware(schemeName) 929 } 930 ctx = goa.WithRequiredScopes(ctx, scopes) 931 return am(h)(ctx, rw, req) 932 } 933 } 934 ` 935 )