@@ -6,9 +6,61 @@ import (
66 "strings"
77 "text/template"
88
9+ "github.com/pb33f/libopenapi/datamodel/high/base"
10+
911 "github.com/oapi-codegen/oapi-codegen-exp/experimental/codegen/internal/templates"
1012)
1113
14+ // templateEntry describes a single template to load.
15+ type templateEntry struct {
16+ Name string // Template name for ExecuteTemplate
17+ Template string // Path within the templates FS (relative to "files/")
18+ }
19+
20+ // loadTemplates parses one or more sets of template entries into the given template.
21+ // Each set is a slice of templateEntry. All entries are loaded in order.
22+ func loadTemplates (tmpl * template.Template , sets ... []templateEntry ) error {
23+ for _ , set := range sets {
24+ for _ , entry := range set {
25+ content , err := templates .TemplateFS .ReadFile ("files/" + entry .Template )
26+ if err != nil {
27+ return fmt .Errorf ("reading template %s: %w" , entry .Template , err )
28+ }
29+ if _ , err := tmpl .New (entry .Name ).Parse (string (content )); err != nil {
30+ return fmt .Errorf ("parsing template %s: %w" , entry .Template , err )
31+ }
32+ }
33+ }
34+ return nil
35+ }
36+
37+ // clientTemplateEntries converts ClientTemplates map to a slice of templateEntry.
38+ func clientTemplateEntries () []templateEntry {
39+ entries := make ([]templateEntry , 0 , len (templates .ClientTemplates ))
40+ for _ , ct := range templates .ClientTemplates {
41+ entries = append (entries , templateEntry {Name : ct .Name , Template : ct .Template })
42+ }
43+ return entries
44+ }
45+
46+ // initiatorTemplateEntries converts InitiatorTemplates map to a slice of templateEntry.
47+ func initiatorTemplateEntries () []templateEntry {
48+ entries := make ([]templateEntry , 0 , len (templates .InitiatorTemplates ))
49+ for _ , it := range templates .InitiatorTemplates {
50+ entries = append (entries , templateEntry {Name : it .Name , Template : it .Template })
51+ }
52+ return entries
53+ }
54+
55+ // sharedServerTemplateEntries converts SharedServerTemplates map to a slice of templateEntry.
56+ func sharedServerTemplateEntries () []templateEntry {
57+ entries := make ([]templateEntry , 0 , len (templates .SharedServerTemplates ))
58+ for _ , st := range templates .SharedServerTemplates {
59+ entries = append (entries , templateEntry {Name : st .Name , Template : st .Template })
60+ }
61+ return entries
62+ }
63+
1264// ClientGenerator generates client code from operation descriptors.
1365type ClientGenerator struct {
1466 tmpl * template.Template
@@ -20,35 +72,11 @@ type ClientGenerator struct {
2072// NewClientGenerator creates a new client generator.
2173// modelsPackage can be nil if models are in the same package.
2274// rp holds the package prefixes for runtime sub-packages; all empty when embedded.
23- func NewClientGenerator (schemaIndex map [string ]* SchemaDescriptor , generateSimple bool , modelsPackage * ModelsPackage , rp RuntimePrefixes ) (* ClientGenerator , error ) {
24- tmpl := template .New ("client" ).Funcs (templates .Funcs ()).Funcs (clientFuncs (schemaIndex , modelsPackage )).Funcs (template.FuncMap {
25- "runtimeParamsPrefix" : func () string { return rp .Params },
26- "runtimeTypesPrefix" : func () string { return rp .Types },
27- "runtimeHelpersPrefix" : func () string { return rp .Helpers },
28- })
29-
30- // Parse client templates
31- for _ , ct := range templates .ClientTemplates {
32- content , err := templates .TemplateFS .ReadFile ("files/" + ct .Template )
33- if err != nil {
34- return nil , err
35- }
36- _ , err = tmpl .New (ct .Name ).Parse (string (content ))
37- if err != nil {
38- return nil , err
39- }
40- }
75+ func NewClientGenerator (schemaIndex map [string ]* SchemaDescriptor , generateSimple bool , modelsPackage * ModelsPackage , rp RuntimePrefixes , typeMapping TypeMapping ) (* ClientGenerator , error ) {
76+ tmpl := template .New ("client" ).Funcs (templates .Funcs ()).Funcs (clientFuncs (schemaIndex , modelsPackage , typeMapping )).Funcs (rp .FuncMap ())
4177
42- // Parse shared templates (param_types is shared with server)
43- for _ , st := range templates .SharedServerTemplates {
44- content , err := templates .TemplateFS .ReadFile ("files/" + st .Template )
45- if err != nil {
46- return nil , err
47- }
48- _ , err = tmpl .New (st .Name ).Parse (string (content ))
49- if err != nil {
50- return nil , err
51- }
78+ if err := loadTemplates (tmpl , clientTemplateEntries (), sharedServerTemplateEntries ()); err != nil {
79+ return nil , err
5280 }
5381
5482 return & ClientGenerator {
@@ -60,7 +88,7 @@ func NewClientGenerator(schemaIndex map[string]*SchemaDescriptor, generateSimple
6088}
6189
6290// clientFuncs returns template functions specific to client generation.
63- func clientFuncs (schemaIndex map [string ]* SchemaDescriptor , modelsPackage * ModelsPackage ) template.FuncMap {
91+ func clientFuncs (schemaIndex map [string ]* SchemaDescriptor , modelsPackage * ModelsPackage , typeMapping TypeMapping ) template.FuncMap {
6492 return template.FuncMap {
6593 "pathFmt" : pathFmt ,
6694 "isSimpleOperation" : isSimpleOperation ,
@@ -70,7 +98,7 @@ func clientFuncs(schemaIndex map[string]*SchemaDescriptor, modelsPackage *Models
7098 return op .DefaultTypedBody ()
7199 },
72100 "goTypeForContent" : func (content * ResponseContentDescriptor ) string {
73- return goTypeForContent (content , schemaIndex , modelsPackage )
101+ return goTypeForContent (content , schemaIndex , modelsPackage , typeMapping )
74102 },
75103 "modelsPkg" : func () string {
76104 return modelsPackage .Prefix ()
@@ -170,7 +198,7 @@ func errorResponseForOperation(op *OperationDescriptor) *ResponseDescriptor {
170198
171199// goTypeForContent returns the Go type for a response content descriptor.
172200// If modelsPackage is set, type names are prefixed with the package name.
173- func goTypeForContent (content * ResponseContentDescriptor , schemaIndex map [string ]* SchemaDescriptor , modelsPackage * ModelsPackage ) string {
201+ func goTypeForContent (content * ResponseContentDescriptor , schemaIndex map [string ]* SchemaDescriptor , modelsPackage * ModelsPackage , typeMapping TypeMapping ) string {
174202 if content == nil || content .Schema == nil {
175203 return "any"
176204 }
@@ -205,14 +233,61 @@ func goTypeForContent(content *ResponseContentDescriptor, schemaIndex map[string
205233 return pkgPrefix + content .Schema .StableName
206234 }
207235
208- // Try to derive from the schema itself
236+ // Try to derive from the schema itself using TypeMapping
209237 if content .Schema .Schema != nil {
210- return schemaToGoType (content .Schema .Schema )
238+ return resolveSchemaType (content .Schema .Schema , typeMapping )
239+ }
240+
241+ return "any"
242+ }
243+
244+ // resolveSchemaType converts a schema to a Go type string using the provided TypeMapping.
245+ // This is a standalone helper used as a last-resort fallback in goTypeForContent.
246+ func resolveSchemaType (schema * base.Schema , tm TypeMapping ) string {
247+ if schema == nil {
248+ return "any"
249+ }
250+
251+ // Check for array
252+ if schema .Items != nil && schema .Items .A != nil {
253+ itemType := "any"
254+ if itemSchema := schema .Items .A .Schema (); itemSchema != nil {
255+ itemType = resolveSchemaType (itemSchema , tm )
256+ }
257+ return "[]" + itemType
258+ }
259+
260+ // Check explicit type
261+ for _ , t := range schema .Type {
262+ switch t {
263+ case "string" :
264+ return resolveFormatType (tm .String , schema .Format )
265+ case "integer" :
266+ return resolveFormatType (tm .Integer , schema .Format )
267+ case "number" :
268+ return resolveFormatType (tm .Number , schema .Format )
269+ case "boolean" :
270+ return tm .Boolean .Default .Type
271+ case "array" :
272+ return "[]any"
273+ case "object" :
274+ return "map[string]any"
275+ }
211276 }
212277
213278 return "any"
214279}
215280
281+ // resolveFormatType looks up a type from a FormatMapping, falling back to its default.
282+ func resolveFormatType (fm FormatMapping , format string ) string {
283+ if format != "" {
284+ if spec , ok := fm .Formats [format ]; ok {
285+ return spec .Type
286+ }
287+ }
288+ return fm .Default .Type
289+ }
290+
216291// GenerateBase generates the base client types and helpers.
217292func (g * ClientGenerator ) GenerateBase () (string , error ) {
218293 var buf bytes.Buffer
@@ -269,8 +344,14 @@ func (g *ClientGenerator) GenerateParamTypes(ops []*OperationDescriptor) (string
269344
270345// GenerateRequestBodyTypes generates type aliases for request bodies.
271346func (g * ClientGenerator ) GenerateRequestBodyTypes (ops []* OperationDescriptor ) string {
347+ return generateRequestBodyTypes (ops , g .schemaIndex , g .modelsPackage )
348+ }
349+
350+ // generateRequestBodyTypes generates type aliases for request bodies.
351+ // This is shared between ClientGenerator and InitiatorGenerator.
352+ func generateRequestBodyTypes (ops []* OperationDescriptor , schemaIndex map [string ]* SchemaDescriptor , modelsPackage * ModelsPackage ) string {
272353 var buf bytes.Buffer
273- pkgPrefix := g . modelsPackage .Prefix ()
354+ pkgPrefix := modelsPackage .Prefix ()
274355
275356 for _ , op := range ops {
276357 for _ , body := range op .Bodies {
@@ -282,7 +363,7 @@ func (g *ClientGenerator) GenerateRequestBodyTypes(ops []*OperationDescriptor) s
282363 if body .Schema != nil {
283364 if body .Schema .Ref != "" {
284365 // Reference to a component schema
285- if target , ok := g . schemaIndex [body .Schema .Ref ]; ok {
366+ if target , ok := schemaIndex [body .Schema .Ref ]; ok {
286367 targetType = pkgPrefix + target .ShortName
287368 }
288369 } else if body .Schema .ShortName != "" {
@@ -293,8 +374,7 @@ func (g *ClientGenerator) GenerateRequestBodyTypes(ops []*OperationDescriptor) s
293374 targetType = "any"
294375 }
295376
296- // Generate type alias: type addPetJSONRequestBody = models.NewPet
297- buf .WriteString (fmt .Sprintf ("type %s = %s\n \n " , body .GoTypeName , targetType ))
377+ fmt .Fprintf (& buf , "type %s = %s\n \n " , body .GoTypeName , targetType )
298378 }
299379 }
300380
0 commit comments