@@ -49,21 +49,49 @@ func findDeclaredTypes(filePath string) ([]*ast.TypeSpec, error) {
4949 return declaredTypes , nil
5050}
5151
52- func hasTags (fields []* ast.Field ) bool {
52+ // hasParsableTags returns true if any field has a "header:" or "json:" struct tag.
53+ func hasParsableTags (fields []* ast.Field ) bool {
54+ return hasHeaderTags (fields ) || hasJsonTags (fields ) || hasHttpRequestTags (fields )
55+ }
56+
57+ // hasHeaderTags returns true if any field has a "header:" struct tag.
58+ func hasHeaderTags (fields []* ast.Field ) bool {
5359 for _ , field := range fields {
5460 if field .Tag != nil {
55- // Extract the header tag by accessing the field.Tag.Value
56- tag := field .Tag .Value
57- if tag != "" {
58- // Remove backticks
59- tag = tag [1 : len (tag )- 1 ]
61+ tag := field .Tag .Value [1 : len (field .Tag .Value )- 1 ]
62+ for _ , part := range strings .Split (tag , " " ) {
63+ if strings .HasPrefix (part , "header:" ) {
64+ return true
65+ }
66+ }
67+ }
68+ }
69+ return false
70+ }
6071
61- // Check if the tag has the "header" key and extract its value
62- tagParts := strings .Split (tag , " " )
63- for _ , part := range tagParts {
64- if strings .HasPrefix (part , "header:" ) {
65- return true
66- }
72+ // hasJsonTags returns true if any field has a "json:" struct tag.
73+ func hasJsonTags (fields []* ast.Field ) bool {
74+ for _ , field := range fields {
75+ if field .Tag != nil {
76+ tag := field .Tag .Value [1 : len (field .Tag .Value )- 1 ]
77+ for _ , part := range strings .Split (tag , " " ) {
78+ if strings .HasPrefix (part , "json:" ) {
79+ return true
80+ }
81+ }
82+ }
83+ }
84+ return false
85+ }
86+
87+ // hasJsonTags returns true if any field has a "isHttpRequest:" struct tag.
88+ func hasHttpRequestTags (fields []* ast.Field ) bool {
89+ for _ , field := range fields {
90+ if field .Tag != nil {
91+ tag := field .Tag .Value [1 : len (field .Tag .Value )- 1 ]
92+ for _ , part := range strings .Split (tag , " " ) {
93+ if strings .HasPrefix (part , "isHttpRequest:" ) {
94+ return true
6795 }
6896 }
6997 }
@@ -97,32 +125,124 @@ func headerExists(headerName string, required, isString, base64Support bool) str
97125 base64SupportEntry = ", has base64support"
98126 }
99127 return fmt .Sprintf ("\n " + `
100- // RequestParser header value %s, required: %v%s
101- exists, err = checkHeaderExists(r, %s, %v, %v)
102- if err != nil {
103- return err
104- }
105- p.foundHeaders[%s] = exists` , headerName , required , base64SupportEntry , headerName , required , isString , headerName )
128+ // RequestParser header value %s, required: %v%s
129+ exists, err = checkHeaderExists(r, %s, %v, %v)
130+ if err != nil {
131+ return err
132+ }
133+ p.foundHeaders[%s] = exists` , headerName , required , base64SupportEntry , headerName , required , isString , headerName )
106134}
107135
108136func generateParseRequestMethod (typeName string , fields []* ast.Field ) string {
109137 // Start generating the ParseRequest method
110- if ! hasTags (fields ) {
138+ if ! hasParsableTags (fields ) {
111139 return fmt .Sprintf (`
112- // ParseRequest parses the header file. As %s has no fields with the
113- // tag header , this method does nothing, except calling ProcessParameter()
140+ // ParseRequest parses the header file. As %s has no fields with the tags header,
141+ // json or isHttpRequest , this method does nothing except calling ProcessParameter()
114142 func (p *%s) ParseRequest(r *http.Request) error {
115143 return p.ProcessParameter(r)
116144 }
117145 %s` , typeName , typeName , writeNewInstanceCode (typeName ))
118146 }
119147
120- method := fmt .Sprintf (`// ParseRequest reads r and saves the passed header values in the %s struct
148+ needsHeader := hasHeaderTags (fields )
149+ needsJson := hasJsonTags (fields )
150+ needsHttpRequest := hasHttpRequestTags (fields )
151+
152+ // Build preamble: header parsing needs foundHeaders + exists; JSON-only still
153+ // needs err for the Decode call.
154+ preamble := ""
155+ if needsHeader {
156+ preamble = `var err error
157+ var exists bool
158+ p.foundHeaders = make(map[string]bool)`
159+ } else {
160+ if needsJson {
161+ preamble = "var err error"
162+ }
163+ }
164+
165+ var readValues []string
166+ if needsHttpRequest {
167+ readValues = append (readValues , "HTTP request" )
168+ }
169+ if needsHeader {
170+ readValues = append (readValues , "header" )
171+ }
172+ if needsJson {
173+ readValues = append (readValues , "JSON" )
174+ }
175+
176+ method := fmt .Sprintf (`// ParseRequest reads r and saves the passed %s values in the %s struct
121177 // In the end, ProcessParameter() is called
122178 func (p *%s) ParseRequest(r *http.Request) error {
123- var err error
124- var exists bool
125- p.foundHeaders = make(map[string]bool)` , typeName , typeName )
179+ %s` , strings .Join (readValues , " and " ), typeName , typeName , preamble )
180+
181+ // Emit the JSON decode block before individual field assignments.
182+ // A single anonymous struct is decoded once; fields are then assigned individually
183+ // so that required-field checks and the struct's own field names are preserved.
184+ if needsJson {
185+ type jsonField struct {
186+ fieldName string
187+ jsonKey string
188+ fieldType string
189+ required bool
190+ }
191+ var jsonFields []jsonField
192+ for _ , field := range fields {
193+ if field .Tag == nil {
194+ continue
195+ }
196+ tag := field .Tag .Value [1 : len (field .Tag .Value )- 1 ]
197+ for _ , part := range strings .Split (tag , " " ) {
198+ if strings .HasPrefix (part , "json:" ) {
199+ jsonKey := strings .TrimPrefix (part , "json:" )
200+ jsonKey = strings .Trim (jsonKey , "\" " )
201+ jsonKey = strings .Split (jsonKey , "," )[0 ] // strip omitempty etc.
202+ fieldType := field .Type .(* ast.Ident ).Name
203+ required := hasRequiredTag (strings .Split (tag , " " ))
204+ jsonFields = append (jsonFields , jsonField {
205+ fieldName : field .Names [0 ].Name ,
206+ jsonKey : jsonKey ,
207+ fieldType : fieldType ,
208+ required : required ,
209+ })
210+ }
211+ }
212+ }
213+
214+ // Build an anonymous intermediate struct matching the JSON shape
215+ intermediateFields := ""
216+ for _ , jf := range jsonFields {
217+ intermediateFields += fmt .Sprintf ("\n \t \t \t %s %s `json:\" %s\" `" , jf .fieldName , jf .fieldType , jf .jsonKey )
218+ }
219+ method += fmt .Sprintf (`
220+ var jsonBody struct {%s
221+ }
222+ if err = json.NewDecoder(r.Body).Decode(&jsonBody); err != nil {
223+ return err
224+ }` , intermediateFields )
225+
226+ // Emit required checks followed by assignment for each json field
227+ for _ , jf := range jsonFields {
228+ if jf .required {
229+ switch jf .fieldType {
230+ case "string" :
231+ method += fmt .Sprintf (`
232+ if jsonBody.%s == "" {
233+ return fmt.Errorf("json field \"%s\" is required")
234+ }` , jf .fieldName , jf .jsonKey )
235+ case "int" , "int64" :
236+ method += fmt .Sprintf (`
237+ if jsonBody.%s == 0 {
238+ return fmt.Errorf("json field \"%s\" is required")
239+ }` , jf .fieldName , jf .jsonKey )
240+ }
241+ }
242+ method += fmt .Sprintf (`
243+ p.%s = jsonBody.%s` , jf .fieldName , jf .fieldName )
244+ }
245+ }
126246
127247 // Iterate over the fields and generate parsing logic for those with a header tag
128248 for _ , field := range fields {
@@ -138,6 +258,9 @@ func generateParseRequestMethod(typeName string, fields []*ast.Field) string {
138258 required := hasRequiredTag (tagParts )
139259 base64Support := hasBase64Tag (tagParts )
140260 for _ , part := range tagParts {
261+ if strings .HasPrefix (part , "isHttpRequest:" ) {
262+ method += fmt .Sprintf ("\n p.%s = r" , field .Names [0 ].Name )
263+ }
141264 if strings .HasPrefix (part , "header:" ) {
142265 // Extract the header name after 'header:'
143266 headerName := strings .TrimPrefix (part , "header:" )
@@ -262,10 +385,27 @@ func main() {
262385
263386 var output strings.Builder
264387
265- output .WriteString (`// Code generated by updateApiRouting.go - DO NOT EDIT.
388+ // Conditionally import "encoding/json" only when at least one struct uses
389+ // json tags, to avoid an unused-import compile error in the generated file.
390+ needsJsonImport := false
391+ for _ , typeSpec := range types {
392+ if structType , ok := typeSpec .Type .(* ast.StructType ); ok {
393+ if hasJsonTags (structType .Fields .List ) {
394+ needsJsonImport = true
395+ break
396+ }
397+ }
398+ }
399+
400+ jsonImport := ""
401+ if needsJsonImport {
402+ jsonImport = "\n \t \" encoding/json\" "
403+ }
404+
405+ output .WriteString (fmt .Sprintf (`// Code generated by updateApiRouting.go - DO NOT EDIT.
266406 package api
267407
268- import (
408+ import (%s
269409 "encoding/base64"
270410 "fmt"
271411 "net/http"
@@ -275,7 +415,7 @@ func main() {
275415 // Do not modify: This is an automatically generated file created by updateApiRouting.go
276416 // It contains the code that is used to parse the headers submitted in an API request
277417
278- ` )
418+ ` , jsonImport ) )
279419
280420 // Process each struct type
281421 for _ , typeSpec := range types {
0 commit comments