Skip to content

Commit f948ae3

Browse files
committed
Add code generation for http.Request and JSON, add API endpoint /files/pasteAdd (with dummy function)
1 parent a6d7d12 commit f948ae3

5 files changed

Lines changed: 332 additions & 68 deletions

File tree

build/go-generate/updateApiRouting.go

Lines changed: 168 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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

108136
func 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("\np.%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 {

internal/webserver/api/Api.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,10 @@ func createAndOutputPresignedUrl(ids []string, w http.ResponseWriter, filename s
685685
_, _ = w.Write(result)
686686
}
687687

688+
func apiPasteAdd(w http.ResponseWriter, r requestParser, user models.User) {
689+
//TODO
690+
}
691+
688692
func apiUploadFile(w http.ResponseWriter, r requestParser, user models.User) {
689693
request, ok := r.(*paramFilesAdd)
690694
if !ok {

internal/webserver/api/routing.go

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ var routes = []apiRoute{
9696
execution: apiUploadFile,
9797
RequestParser: &paramFilesAdd{},
9898
},
99+
{
100+
Url: "/files/addPaste",
101+
ApiPerm: models.ApiPermUpload,
102+
execution: apiPasteAdd,
103+
RequestParser: &paramPasteAdd{},
104+
},
99105
{
100106
Url: "/files/delete",
101107
ApiPerm: models.ApiPermDelete,
@@ -325,41 +331,52 @@ func (p *paramFilesListSingle) ProcessParameter(r *http.Request) error {
325331

326332
type paramFilesDownloadSingle struct {
327333
Id string
328-
WebRequest *http.Request
329-
IncreaseCounter bool `header:"increaseCounter"`
330-
PresignUrl bool `header:"presignUrl"`
334+
WebRequest *http.Request `isHttpRequest:"true"`
335+
IncreaseCounter bool `header:"increaseCounter"`
336+
PresignUrl bool `header:"presignUrl"`
331337
foundHeaders map[string]bool
332338
}
333339

334340
func (p *paramFilesDownloadSingle) ProcessParameter(r *http.Request) error {
335-
p.WebRequest = r
336341
url := parseRequestUrl(r)
337342
p.Id = strings.TrimPrefix(url, "/files/download/")
338343
return nil
339344
}
340345

341346
type paramFilesDownloadZip struct {
342347
Ids []string
343-
WebRequest *http.Request
344-
FileIds string `header:"ids" required:"true"`
345-
Filename string `header:"filename" supportBase64:"true"`
346-
IncreaseCounter bool `header:"increaseCounter"`
347-
PresignUrl bool `header:"presignUrl"`
348+
FileIds string `header:"ids" required:"true"`
349+
Filename string `header:"filename" supportBase64:"true"`
350+
IncreaseCounter bool `header:"increaseCounter"`
351+
PresignUrl bool `header:"presignUrl"`
352+
WebRequest *http.Request `isHttpRequest:"true"`
348353
foundHeaders map[string]bool
349354
}
350355

351-
func (p *paramFilesDownloadZip) ProcessParameter(r *http.Request) error {
356+
func (p *paramFilesDownloadZip) ProcessParameter(_ *http.Request) error {
352357
p.Ids = strings.Split(p.FileIds, ",")
353-
p.WebRequest = r
354358
return nil
355359
}
356360

357361
type paramFilesAdd struct {
358-
Request *http.Request
362+
Request *http.Request `isHttpRequest:"true"`
363+
}
364+
365+
func (p *paramFilesAdd) ProcessParameter(_ *http.Request) error {
366+
return nil
367+
}
368+
369+
type paramPasteAdd struct {
370+
PasteContent string `json:"pasteContent" required:"true"`
371+
AllowedDownloads int `json:"allowedDownloads"`
372+
ExpiryDays int `json:"expiryDays"`
373+
Password string `json:"password"`
374+
UnlimitedDownloads bool
375+
UnlimitedExpiry bool
376+
foundHeaders map[string]bool
359377
}
360378

361-
func (p *paramFilesAdd) ProcessParameter(r *http.Request) error {
362-
p.Request = r
379+
func (p *paramPasteAdd) ProcessParameter(r *http.Request) error {
363380
return nil
364381
}
365382

@@ -619,13 +636,12 @@ func (p *paramE2eStore) ProcessParameter(r *http.Request) error {
619636
}
620637

621638
type paramLogsDelete struct {
622-
Timestamp int64 `header:"timestamp"`
623-
Request *http.Request
639+
Timestamp int64 `header:"timestamp"`
640+
Request *http.Request `isHttpRequest:"true"`
624641
foundHeaders map[string]bool
625642
}
626643

627-
func (p *paramLogsDelete) ProcessParameter(r *http.Request) error {
628-
p.Request = r
644+
func (p *paramLogsDelete) ProcessParameter(_ *http.Request) error {
629645
return nil
630646
}
631647

@@ -639,11 +655,10 @@ func (p *paramLogsGet) ProcessParameter(_ *http.Request) error {
639655
}
640656

641657
type paramChunkAdd struct {
642-
Request *http.Request
658+
Request *http.Request `isHttpRequest:"true"`
643659
}
644660

645-
func (p *paramChunkAdd) ProcessParameter(r *http.Request) error {
646-
p.Request = r
661+
func (p *paramChunkAdd) ProcessParameter(_ *http.Request) error {
647662
return nil
648663
}
649664

@@ -652,14 +667,13 @@ func (p *paramChunkAdd) GetRequest() *http.Request {
652667
}
653668

654669
type paramChunkUploadRequestAdd struct {
655-
Request *http.Request
656-
FileRequestId string `header:"fileRequestId" required:"true"`
657-
ApiKey string `header:"apikey" unpublished:"true"` // not published in API documentation
670+
Request *http.Request `isHttpRequest:"true"`
671+
FileRequestId string `header:"fileRequestId" required:"true"`
672+
ApiKey string `header:"apikey" unpublished:"true"` // not published in API documentation
658673
foundHeaders map[string]bool
659674
}
660675

661-
func (p *paramChunkUploadRequestAdd) ProcessParameter(r *http.Request) error {
662-
p.Request = r
676+
func (p *paramChunkUploadRequestAdd) ProcessParameter(_ *http.Request) error {
663677
return nil
664678
}
665679
func (p *paramChunkUploadRequestAdd) GetRequest() *http.Request {

0 commit comments

Comments
 (0)