1// Copyright 2019 DeepMap, Inc. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14package codegen 15 16import ( 17 "bufio" 18 "bytes" 19 "fmt" 20 "strings" 21 "text/template" 22 "unicode" 23 24 "github.com/getkin/kin-openapi/openapi3" 25 "github.com/pkg/errors" 26) 27 28type ParameterDefinition struct { 29 ParamName string // The original json parameter name, eg param_name 30 In string // Where the parameter is defined - path, header, cookie, query 31 Required bool // Is this a required parameter? 32 Spec *openapi3.Parameter 33 Schema Schema 34} 35 36// This function is here as an adapter after a large refactoring so that I don't 37// have to update all the templates. It returns the type definition for a parameter, 38// without the leading '*' for optional ones. 39func (pd ParameterDefinition) TypeDef() string { 40 typeDecl := pd.Schema.TypeDecl() 41 return typeDecl 42} 43 44// Generate the JSON annotation to map GoType to json type name. If Parameter 45// Foo is marshaled to json as "foo", this will create the annotation 46// 'json:"foo"' 47func (pd *ParameterDefinition) JsonTag() string { 48 if pd.Required { 49 return fmt.Sprintf("`json:\"%s\"`", pd.ParamName) 50 } else { 51 return fmt.Sprintf("`json:\"%s,omitempty\"`", pd.ParamName) 52 } 53} 54 55func (pd *ParameterDefinition) IsJson() bool { 56 p := pd.Spec 57 if len(p.Content) == 1 { 58 _, found := p.Content["application/json"] 59 return found 60 } 61 return false 62} 63 64func (pd *ParameterDefinition) IsPassThrough() bool { 65 p := pd.Spec 66 if len(p.Content) > 1 { 67 return true 68 } 69 if len(p.Content) == 1 { 70 return !pd.IsJson() 71 } 72 return false 73} 74 75func (pd *ParameterDefinition) IsStyled() bool { 76 p := pd.Spec 77 return p.Schema != nil 78} 79 80func (pd *ParameterDefinition) Style() string { 81 style := pd.Spec.Style 82 if style == "" { 83 in := pd.Spec.In 84 switch in { 85 case "path", "header": 86 return "simple" 87 case "query", "cookie": 88 return "form" 89 default: 90 panic("unknown parameter format") 91 } 92 } 93 return style 94} 95 96func (pd *ParameterDefinition) Explode() bool { 97 if pd.Spec.Explode == nil { 98 in := pd.Spec.In 99 switch in { 100 case "path", "header": 101 return false 102 case "query", "cookie": 103 return true 104 default: 105 panic("unknown parameter format") 106 } 107 } 108 return *pd.Spec.Explode 109} 110 111func (pd ParameterDefinition) GoVariableName() string { 112 name := LowercaseFirstCharacter(pd.GoName()) 113 if IsGoKeyword(name) { 114 name = "p" + UppercaseFirstCharacter(name) 115 } 116 if unicode.IsNumber([]rune(name)[0]) { 117 name = "n" + name 118 } 119 return name 120} 121 122func (pd ParameterDefinition) GoName() string { 123 return SchemaNameToTypeName(pd.ParamName) 124} 125 126func (pd ParameterDefinition) IndirectOptional() bool { 127 return !pd.Required && !pd.Schema.SkipOptionalPointer 128} 129 130type ParameterDefinitions []ParameterDefinition 131 132func (p ParameterDefinitions) FindByName(name string) *ParameterDefinition { 133 for _, param := range p { 134 if param.ParamName == name { 135 return ¶m 136 } 137 } 138 return nil 139} 140 141// This function walks the given parameters dictionary, and generates the above 142// descriptors into a flat list. This makes it a lot easier to traverse the 143// data in the template engine. 144func DescribeParameters(params openapi3.Parameters, path []string) ([]ParameterDefinition, error) { 145 outParams := make([]ParameterDefinition, 0) 146 for _, paramOrRef := range params { 147 param := paramOrRef.Value 148 149 goType, err := paramToGoType(param, append(path, param.Name)) 150 if err != nil { 151 return nil, fmt.Errorf("error generating type for param (%s): %s", 152 param.Name, err) 153 } 154 155 pd := ParameterDefinition{ 156 ParamName: param.Name, 157 In: param.In, 158 Required: param.Required, 159 Spec: param, 160 Schema: goType, 161 } 162 163 // If this is a reference to a predefined type, simply use the reference 164 // name as the type. $ref: "#/components/schemas/custom_type" becomes 165 // "CustomType". 166 if IsGoTypeReference(paramOrRef.Ref) { 167 goType, err := RefPathToGoType(paramOrRef.Ref) 168 if err != nil { 169 return nil, fmt.Errorf("error dereferencing (%s) for param (%s): %s", 170 paramOrRef.Ref, param.Name, err) 171 } 172 pd.Schema.GoType = goType 173 } 174 outParams = append(outParams, pd) 175 } 176 return outParams, nil 177} 178 179type SecurityDefinition struct { 180 ProviderName string 181 Scopes []string 182} 183 184func DescribeSecurityDefinition(securityRequirements openapi3.SecurityRequirements) []SecurityDefinition { 185 outDefs := make([]SecurityDefinition, 0) 186 187 for _, sr := range securityRequirements { 188 for _, k := range SortedSecurityRequirementKeys(sr) { 189 v := sr[k] 190 outDefs = append(outDefs, SecurityDefinition{ProviderName: k, Scopes: v}) 191 } 192 } 193 194 return outDefs 195} 196 197// This structure describes an Operation 198type OperationDefinition struct { 199 OperationId string // The operation_id description from Swagger, used to generate function names 200 201 PathParams []ParameterDefinition // Parameters in the path, eg, /path/:param 202 HeaderParams []ParameterDefinition // Parameters in HTTP headers 203 QueryParams []ParameterDefinition // Parameters in the query, /path?param 204 CookieParams []ParameterDefinition // Parameters in cookies 205 TypeDefinitions []TypeDefinition // These are all the types we need to define for this operation 206 SecurityDefinitions []SecurityDefinition // These are the security providers 207 BodyRequired bool 208 Bodies []RequestBodyDefinition // The list of bodies for which to generate handlers. 209 Summary string // Summary string from Swagger, used to generate a comment 210 Method string // GET, POST, DELETE, etc. 211 Path string // The Swagger path for the operation, like /resource/{id} 212 Spec *openapi3.Operation 213} 214 215// Returns the list of all parameters except Path parameters. Path parameters 216// are handled differently from the rest, since they're mandatory. 217func (o *OperationDefinition) Params() []ParameterDefinition { 218 result := append(o.QueryParams, o.HeaderParams...) 219 result = append(result, o.CookieParams...) 220 return result 221} 222 223// Returns all parameters 224func (o *OperationDefinition) AllParams() []ParameterDefinition { 225 result := append(o.QueryParams, o.HeaderParams...) 226 result = append(result, o.CookieParams...) 227 result = append(result, o.PathParams...) 228 return result 229} 230 231// If we have parameters other than path parameters, they're bundled into an 232// object. Returns true if we have any of those. This is used from the template 233// engine. 234func (o *OperationDefinition) RequiresParamObject() bool { 235 return len(o.Params()) > 0 236} 237 238// This is called by the template engine to determine whether to generate body 239// marshaling code on the client. This is true for all body types, whether or 240// not we generate types for them. 241func (o *OperationDefinition) HasBody() bool { 242 return o.Spec.RequestBody != nil 243} 244 245// This returns the Operations summary as a multi line comment 246func (o *OperationDefinition) SummaryAsComment() string { 247 if o.Summary == "" { 248 return "" 249 } 250 trimmed := strings.TrimSuffix(o.Summary, "\n") 251 parts := strings.Split(trimmed, "\n") 252 for i, p := range parts { 253 parts[i] = "// " + p 254 } 255 return strings.Join(parts, "\n") 256} 257 258// Produces a list of type definitions for a given Operation for the response 259// types which we know how to parse. These will be turned into fields on a 260// response object for automatic deserialization of responses in the generated 261// Client code. See "client-with-responses.tmpl". 262func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefinition, error) { 263 var tds []ResponseTypeDefinition 264 265 responses := o.Spec.Responses 266 sortedResponsesKeys := SortedResponsesKeys(responses) 267 for _, responseName := range sortedResponsesKeys { 268 responseRef := responses[responseName] 269 270 // We can only generate a type if we have a value: 271 if responseRef.Value != nil { 272 sortedContentKeys := SortedContentKeys(responseRef.Value.Content) 273 for _, contentTypeName := range sortedContentKeys { 274 contentType := responseRef.Value.Content[contentTypeName] 275 // We can only generate a type if we have a schema: 276 if contentType.Schema != nil { 277 responseSchema, err := GenerateGoSchema(contentType.Schema, []string{responseName}) 278 if err != nil { 279 return nil, errors.Wrap(err, fmt.Sprintf("Unable to determine Go type for %s.%s", o.OperationId, contentTypeName)) 280 } 281 282 var typeName string 283 switch { 284 case StringInArray(contentTypeName, contentTypesJSON): 285 typeName = fmt.Sprintf("JSON%s", ToCamelCase(responseName)) 286 // YAML: 287 case StringInArray(contentTypeName, contentTypesYAML): 288 typeName = fmt.Sprintf("YAML%s", ToCamelCase(responseName)) 289 // XML: 290 case StringInArray(contentTypeName, contentTypesXML): 291 typeName = fmt.Sprintf("XML%s", ToCamelCase(responseName)) 292 default: 293 continue 294 } 295 296 td := ResponseTypeDefinition{ 297 TypeDefinition: TypeDefinition{ 298 TypeName: typeName, 299 Schema: responseSchema, 300 }, 301 ResponseName: responseName, 302 ContentTypeName: contentTypeName, 303 } 304 if IsGoTypeReference(contentType.Schema.Ref) { 305 refType, err := RefPathToGoType(contentType.Schema.Ref) 306 if err != nil { 307 return nil, errors.Wrap(err, "error dereferencing response Ref") 308 } 309 td.Schema.RefType = refType 310 } 311 tds = append(tds, td) 312 } 313 } 314 } 315 } 316 return tds, nil 317} 318 319// This describes a request body 320type RequestBodyDefinition struct { 321 // Is this body required, or optional? 322 Required bool 323 324 // This is the schema describing this body 325 Schema Schema 326 327 // When we generate type names, we need a Tag for it, such as JSON, in 328 // which case we will produce "JSONBody". 329 NameTag string 330 331 // This is the content type corresponding to the body, eg, application/json 332 ContentType string 333 334 // Whether this is the default body type. For an operation named OpFoo, we 335 // will not add suffixes like OpFooJSONBody for this one. 336 Default bool 337} 338 339// Returns the Go type definition for a request body 340func (r RequestBodyDefinition) TypeDef(opID string) *TypeDefinition { 341 return &TypeDefinition{ 342 TypeName: fmt.Sprintf("%s%sRequestBody", opID, r.NameTag), 343 Schema: r.Schema, 344 } 345} 346 347// Returns whether the body is a custom inline type, or pre-defined. This is 348// poorly named, but it's here for compatibility reasons post-refactoring 349// TODO: clean up the templates code, it can be simpler. 350func (r RequestBodyDefinition) CustomType() bool { 351 return r.Schema.RefType == "" 352} 353 354// When we're generating multiple functions which relate to request bodies, 355// this generates the suffix. Such as Operation DoFoo would be suffixed with 356// DoFooWithXMLBody. 357func (r RequestBodyDefinition) Suffix() string { 358 // The default response is never suffixed. 359 if r.Default { 360 return "" 361 } 362 return "With" + r.NameTag + "Body" 363} 364 365// This function returns the subset of the specified parameters which are of the 366// specified type. 367func FilterParameterDefinitionByType(params []ParameterDefinition, in string) []ParameterDefinition { 368 var out []ParameterDefinition 369 for _, p := range params { 370 if p.In == in { 371 out = append(out, p) 372 } 373 } 374 return out 375} 376 377// OperationDefinitions returns all operations for a swagger definition. 378func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) { 379 var operations []OperationDefinition 380 381 for _, requestPath := range SortedPathsKeys(swagger.Paths) { 382 pathItem := swagger.Paths[requestPath] 383 // These are parameters defined for all methods on a given path. They 384 // are shared by all methods. 385 globalParams, err := DescribeParameters(pathItem.Parameters, nil) 386 if err != nil { 387 return nil, fmt.Errorf("error describing global parameters for %s: %s", 388 requestPath, err) 389 } 390 391 // Each path can have a number of operations, POST, GET, OPTIONS, etc. 392 pathOps := pathItem.Operations() 393 for _, opName := range SortedOperationsKeys(pathOps) { 394 op := pathOps[opName] 395 if pathItem.Servers != nil { 396 op.Servers = &pathItem.Servers 397 } 398 // We rely on OperationID to generate function names, it's required 399 if op.OperationID == "" { 400 op.OperationID, err = generateDefaultOperationID(opName, requestPath) 401 if err != nil { 402 return nil, fmt.Errorf("error generating default OperationID for %s/%s: %s", 403 opName, requestPath, err) 404 } 405 op.OperationID = op.OperationID 406 } else { 407 op.OperationID = ToCamelCase(op.OperationID) 408 } 409 410 // These are parameters defined for the specific path method that 411 // we're iterating over. 412 localParams, err := DescribeParameters(op.Parameters, []string{op.OperationID + "Params"}) 413 if err != nil { 414 return nil, fmt.Errorf("error describing global parameters for %s/%s: %s", 415 opName, requestPath, err) 416 } 417 // All the parameters required by a handler are the union of the 418 // global parameters and the local parameters. 419 allParams := append(globalParams, localParams...) 420 421 // Order the path parameters to match the order as specified in 422 // the path, not in the swagger spec, and validate that the parameter 423 // names match, as downstream code depends on that. 424 pathParams := FilterParameterDefinitionByType(allParams, "path") 425 pathParams, err = SortParamsByPath(requestPath, pathParams) 426 if err != nil { 427 return nil, err 428 } 429 430 bodyDefinitions, typeDefinitions, err := GenerateBodyDefinitions(op.OperationID, op.RequestBody) 431 if err != nil { 432 return nil, errors.Wrap(err, "error generating body definitions") 433 } 434 435 opDef := OperationDefinition{ 436 PathParams: pathParams, 437 HeaderParams: FilterParameterDefinitionByType(allParams, "header"), 438 QueryParams: FilterParameterDefinitionByType(allParams, "query"), 439 CookieParams: FilterParameterDefinitionByType(allParams, "cookie"), 440 OperationId: ToCamelCase(op.OperationID), 441 // Replace newlines in summary. 442 Summary: op.Summary, 443 Method: opName, 444 Path: requestPath, 445 Spec: op, 446 Bodies: bodyDefinitions, 447 TypeDefinitions: typeDefinitions, 448 } 449 450 // check for overrides of SecurityDefinitions. 451 // See: "Step 2. Applying security:" from the spec: 452 // https://swagger.io/docs/specification/authentication/ 453 if op.Security != nil { 454 opDef.SecurityDefinitions = DescribeSecurityDefinition(*op.Security) 455 } else { 456 // use global securityDefinitions 457 // globalSecurityDefinitions contains the top-level securityDefinitions. 458 // They are the default securityPermissions which are injected into each 459 // path, except for the case where a path explicitly overrides them. 460 opDef.SecurityDefinitions = DescribeSecurityDefinition(swagger.Security) 461 462 } 463 464 if op.RequestBody != nil { 465 opDef.BodyRequired = op.RequestBody.Value.Required 466 } 467 468 // Generate all the type definitions needed for this operation 469 opDef.TypeDefinitions = append(opDef.TypeDefinitions, GenerateTypeDefsForOperation(opDef)...) 470 471 operations = append(operations, opDef) 472 } 473 } 474 return operations, nil 475} 476 477func generateDefaultOperationID(opName string, requestPath string) (string, error) { 478 var operationId string = strings.ToLower(opName) 479 480 if opName == "" { 481 return "", fmt.Errorf("operation name cannot be an empty string") 482 } 483 484 if requestPath == "" { 485 return "", fmt.Errorf("request path cannot be an empty string") 486 } 487 488 for _, part := range strings.Split(requestPath, "/") { 489 if part != "" { 490 operationId = operationId + "-" + part 491 } 492 } 493 494 return ToCamelCase(operationId), nil 495} 496 497// This function turns the Swagger body definitions into a list of our body 498// definitions which will be used for code generation. 499func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBodyRef) ([]RequestBodyDefinition, []TypeDefinition, error) { 500 if bodyOrRef == nil { 501 return nil, nil, nil 502 } 503 body := bodyOrRef.Value 504 505 var bodyDefinitions []RequestBodyDefinition 506 var typeDefinitions []TypeDefinition 507 508 for contentType, content := range body.Content { 509 var tag string 510 var defaultBody bool 511 512 switch contentType { 513 case "application/json": 514 tag = "JSON" 515 defaultBody = true 516 default: 517 continue 518 } 519 520 bodyTypeName := operationID + tag + "Body" 521 bodySchema, err := GenerateGoSchema(content.Schema, []string{bodyTypeName}) 522 if err != nil { 523 return nil, nil, errors.Wrap(err, "error generating request body definition") 524 } 525 526 // If the body is a pre-defined type 527 if IsGoTypeReference(bodyOrRef.Ref) { 528 // Convert the reference path to Go type 529 refType, err := RefPathToGoType(bodyOrRef.Ref) 530 if err != nil { 531 return nil, nil, errors.Wrap(err, fmt.Sprintf("error turning reference (%s) into a Go type", bodyOrRef.Ref)) 532 } 533 bodySchema.RefType = refType 534 } 535 536 // If the request has a body, but it's not a user defined 537 // type under #/components, we'll define a type for it, so 538 // that we have an easy to use type for marshaling. 539 if bodySchema.RefType == "" { 540 td := TypeDefinition{ 541 TypeName: bodyTypeName, 542 Schema: bodySchema, 543 } 544 typeDefinitions = append(typeDefinitions, td) 545 // The body schema now is a reference to a type 546 bodySchema.RefType = bodyTypeName 547 } 548 549 bd := RequestBodyDefinition{ 550 Required: body.Required, 551 Schema: bodySchema, 552 NameTag: tag, 553 ContentType: contentType, 554 Default: defaultBody, 555 } 556 bodyDefinitions = append(bodyDefinitions, bd) 557 } 558 return bodyDefinitions, typeDefinitions, nil 559} 560 561func GenerateTypeDefsForOperation(op OperationDefinition) []TypeDefinition { 562 var typeDefs []TypeDefinition 563 // Start with the params object itself 564 if len(op.Params()) != 0 { 565 typeDefs = append(typeDefs, GenerateParamsTypes(op)...) 566 } 567 568 // Now, go through all the additional types we need to declare. 569 for _, param := range op.AllParams() { 570 typeDefs = append(typeDefs, param.Schema.GetAdditionalTypeDefs()...) 571 } 572 573 for _, body := range op.Bodies { 574 typeDefs = append(typeDefs, body.Schema.GetAdditionalTypeDefs()...) 575 } 576 return typeDefs 577} 578 579// This defines the schema for a parameters definition object which encapsulates 580// all the query, header and cookie parameters for an operation. 581func GenerateParamsTypes(op OperationDefinition) []TypeDefinition { 582 var typeDefs []TypeDefinition 583 584 objectParams := op.QueryParams 585 objectParams = append(objectParams, op.HeaderParams...) 586 objectParams = append(objectParams, op.CookieParams...) 587 588 typeName := op.OperationId + "Params" 589 590 s := Schema{} 591 for _, param := range objectParams { 592 pSchema := param.Schema 593 if pSchema.HasAdditionalProperties { 594 propRefName := strings.Join([]string{typeName, param.GoName()}, "_") 595 pSchema.RefType = propRefName 596 typeDefs = append(typeDefs, TypeDefinition{ 597 TypeName: propRefName, 598 Schema: param.Schema, 599 }) 600 } 601 prop := Property{ 602 Description: param.Spec.Description, 603 JsonFieldName: param.ParamName, 604 Required: param.Required, 605 Schema: pSchema, 606 ExtensionProps: ¶m.Spec.ExtensionProps, 607 } 608 s.Properties = append(s.Properties, prop) 609 } 610 611 s.Description = op.Spec.Description 612 s.GoType = GenStructFromSchema(s) 613 614 td := TypeDefinition{ 615 TypeName: typeName, 616 Schema: s, 617 } 618 return append(typeDefs, td) 619} 620 621// Generates code for all types produced 622func GenerateTypesForOperations(t *template.Template, ops []OperationDefinition) (string, error) { 623 var buf bytes.Buffer 624 w := bufio.NewWriter(&buf) 625 626 err := t.ExecuteTemplate(w, "param-types.tmpl", ops) 627 if err != nil { 628 return "", errors.Wrap(err, "error generating types for params objects") 629 } 630 631 err = t.ExecuteTemplate(w, "request-bodies.tmpl", ops) 632 if err != nil { 633 return "", errors.Wrap(err, "error generating request bodies for operations") 634 } 635 636 // Generate boiler plate for all additional types. 637 var td []TypeDefinition 638 for _, op := range ops { 639 td = append(td, op.TypeDefinitions...) 640 } 641 642 addProps, err := GenerateAdditionalPropertyBoilerplate(t, td) 643 if err != nil { 644 return "", errors.Wrap(err, "error generating additional properties boilerplate for operations") 645 } 646 647 _, err = w.WriteString("\n") 648 if err != nil { 649 return "", errors.Wrap(err, "error generating additional properties boilerplate for operations") 650 } 651 652 _, err = w.WriteString(addProps) 653 if err != nil { 654 return "", errors.Wrap(err, "error generating additional properties boilerplate for operations") 655 } 656 657 err = w.Flush() 658 if err != nil { 659 return "", errors.Wrap(err, "error flushing output buffer for server interface") 660 } 661 662 return buf.String(), nil 663} 664 665// GenerateChiServer This function generates all the go code for the ServerInterface as well as 666// all the wrapper functions around our handlers. 667func GenerateChiServer(t *template.Template, operations []OperationDefinition) (string, error) { 668 var buf bytes.Buffer 669 w := bufio.NewWriter(&buf) 670 671 err := t.ExecuteTemplate(w, "chi-interface.tmpl", operations) 672 if err != nil { 673 return "", errors.Wrap(err, "error generating server interface") 674 } 675 676 err = t.ExecuteTemplate(w, "chi-middleware.tmpl", operations) 677 if err != nil { 678 return "", errors.Wrap(err, "error generating server middleware") 679 } 680 681 err = t.ExecuteTemplate(w, "chi-handler.tmpl", operations) 682 if err != nil { 683 return "", errors.Wrap(err, "error generating server http handler") 684 } 685 686 err = w.Flush() 687 if err != nil { 688 return "", errors.Wrap(err, "error flushing output buffer for server") 689 } 690 691 return buf.String(), nil 692} 693 694// GenerateEchoServer This function generates all the go code for the ServerInterface as well as 695// all the wrapper functions around our handlers. 696func GenerateEchoServer(t *template.Template, operations []OperationDefinition) (string, error) { 697 si, err := GenerateServerInterface(t, operations) 698 if err != nil { 699 return "", fmt.Errorf("Error generating server types and interface: %s", err) 700 } 701 702 wrappers, err := GenerateWrappers(t, operations) 703 if err != nil { 704 return "", fmt.Errorf("Error generating handler wrappers: %s", err) 705 } 706 707 register, err := GenerateRegistration(t, operations) 708 if err != nil { 709 return "", fmt.Errorf("Error generating handler registration: %s", err) 710 } 711 return strings.Join([]string{si, wrappers, register}, "\n"), nil 712} 713 714// Uses the template engine to generate the server interface 715func GenerateServerInterface(t *template.Template, ops []OperationDefinition) (string, error) { 716 var buf bytes.Buffer 717 w := bufio.NewWriter(&buf) 718 719 err := t.ExecuteTemplate(w, "server-interface.tmpl", ops) 720 721 if err != nil { 722 return "", fmt.Errorf("error generating server interface: %s", err) 723 } 724 err = w.Flush() 725 if err != nil { 726 return "", fmt.Errorf("error flushing output buffer for server interface: %s", err) 727 } 728 return buf.String(), nil 729} 730 731// Uses the template engine to generate all the wrappers which wrap our simple 732// interface functions and perform marshallin/unmarshalling from HTTP 733// request objects. 734func GenerateWrappers(t *template.Template, ops []OperationDefinition) (string, error) { 735 var buf bytes.Buffer 736 w := bufio.NewWriter(&buf) 737 738 err := t.ExecuteTemplate(w, "wrappers.tmpl", ops) 739 740 if err != nil { 741 return "", fmt.Errorf("error generating server interface: %s", err) 742 } 743 err = w.Flush() 744 if err != nil { 745 return "", fmt.Errorf("error flushing output buffer for server interface: %s", err) 746 } 747 return buf.String(), nil 748} 749 750// Uses the template engine to generate the function which registers our wrappers 751// as Echo path handlers. 752func GenerateRegistration(t *template.Template, ops []OperationDefinition) (string, error) { 753 var buf bytes.Buffer 754 w := bufio.NewWriter(&buf) 755 756 err := t.ExecuteTemplate(w, "register.tmpl", ops) 757 758 if err != nil { 759 return "", fmt.Errorf("error generating route registration: %s", err) 760 } 761 err = w.Flush() 762 if err != nil { 763 return "", fmt.Errorf("error flushing output buffer for route registration: %s", err) 764 } 765 return buf.String(), nil 766} 767 768// Uses the template engine to generate the function which registers our wrappers 769// as Echo path handlers. 770func GenerateClient(t *template.Template, ops []OperationDefinition) (string, error) { 771 var buf bytes.Buffer 772 w := bufio.NewWriter(&buf) 773 774 err := t.ExecuteTemplate(w, "client.tmpl", ops) 775 776 if err != nil { 777 return "", fmt.Errorf("error generating client bindings: %s", err) 778 } 779 err = w.Flush() 780 if err != nil { 781 return "", fmt.Errorf("error flushing output buffer for client: %s", err) 782 } 783 return buf.String(), nil 784} 785 786// This generates a client which extends the basic client which does response 787// unmarshaling. 788func GenerateClientWithResponses(t *template.Template, ops []OperationDefinition) (string, error) { 789 var buf bytes.Buffer 790 w := bufio.NewWriter(&buf) 791 792 err := t.ExecuteTemplate(w, "client-with-responses.tmpl", ops) 793 794 if err != nil { 795 return "", fmt.Errorf("error generating client bindings: %s", err) 796 } 797 err = w.Flush() 798 if err != nil { 799 return "", fmt.Errorf("error flushing output buffer for client: %s", err) 800 } 801 return buf.String(), nil 802} 803