Skip to content

Commit 9d321e6

Browse files
author
Jose Aranguren
committed
[go-server] Moved helper code from router and updated logger
1 parent 74e49ac commit 9d321e6

File tree

13 files changed

+1515
-1518
lines changed

13 files changed

+1515
-1518
lines changed

modules/openapi-generator/src/main/resources/go-server/helpers.mustache

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22
package {{packageName}}
33

44
import (
5+
"encoding/json"
6+
"errors"
7+
"io"
8+
"mime/multipart"
9+
"net/http"
10+
"net/url"
11+
"os"
512
"reflect"
13+
"strconv"
14+
"strings"
15+
"time"
616
)
717

818
// Response return a ImplResponse struct filled
@@ -64,3 +74,292 @@ func AssertRecurseValueRequired[T any](value reflect.Value, callback func(T) err
6474
}
6575
return nil
6676
}
77+
78+
// EncodeJSONResponse uses the json encoder to write an interface to the http response with an optional status code
79+
func EncodeJSONResponse(i interface{}, status *int,{{#addResponseHeaders}} headers map[string][]string,{{/addResponseHeaders}} w http.ResponseWriter) error {
80+
wHeader := w.Header()
81+
{{#addResponseHeaders}}
82+
for key, values := range headers {
83+
for _, value := range values {
84+
wHeader.Add(key, value)
85+
}
86+
}
87+
{{/addResponseHeaders}}
88+
89+
f, ok := i.(*os.File)
90+
if ok {
91+
data, err := io.ReadAll(f)
92+
if err != nil {
93+
return err
94+
}
95+
wHeader.Set("Content-Type", http.DetectContentType(data))
96+
wHeader.Set("Content-Disposition", "attachment; filename="+f.Name())
97+
if status != nil {
98+
w.WriteHeader(*status)
99+
} else {
100+
w.WriteHeader(http.StatusOK)
101+
}
102+
_, err = w.Write(data)
103+
return err
104+
}
105+
wHeader.Set("Content-Type", "application/json; charset=UTF-8")
106+
107+
if status != nil {
108+
w.WriteHeader(*status)
109+
} else {
110+
w.WriteHeader(http.StatusOK)
111+
}
112+
113+
if i != nil {
114+
return json.NewEncoder(w).Encode(i)
115+
}
116+
117+
return nil
118+
}
119+
120+
// ReadFormFileToTempFile reads file data from a request form and writes it to a temporary file
121+
func ReadFormFileToTempFile(r *http.Request, key string) (*os.File, error) {
122+
_, fileHeader, err := r.FormFile(key)
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
return readFileHeaderToTempFile(fileHeader)
128+
}
129+
130+
// ReadFormFilesToTempFiles reads files array data from a request form and writes it to a temporary files
131+
func ReadFormFilesToTempFiles(r *http.Request, key string) ([]*os.File, error) {
132+
if err := r.ParseMultipartForm(32 << 20); err != nil {
133+
return nil, err
134+
}
135+
136+
files := make([]*os.File, 0, len(r.MultipartForm.File[key]))
137+
138+
for _, fileHeader := range r.MultipartForm.File[key] {
139+
file, err := readFileHeaderToTempFile(fileHeader)
140+
if err != nil {
141+
return nil, err
142+
}
143+
144+
files = append(files, file)
145+
}
146+
147+
return files, nil
148+
}
149+
150+
// readFileHeaderToTempFile reads multipart.FileHeader and writes it to a temporary file
151+
func readFileHeaderToTempFile(fileHeader *multipart.FileHeader) (*os.File, error) {
152+
formFile, err := fileHeader.Open()
153+
if err != nil {
154+
return nil, err
155+
}
156+
157+
defer formFile.Close()
158+
159+
// Use .* as suffix, because the asterisk is a placeholder for the random value,
160+
// and the period allows consumers of this file to remove the suffix to obtain the original file name
161+
file, err := os.CreateTemp("", fileHeader.Filename+".*")
162+
if err != nil {
163+
return nil, err
164+
}
165+
166+
defer file.Close()
167+
168+
_, err = io.Copy(file, formFile)
169+
if err != nil {
170+
return nil, err
171+
}
172+
173+
return file, nil
174+
}
175+
176+
func parseTimes(param string) ([]time.Time, error) {
177+
splits := strings.Split(param, ",")
178+
times := make([]time.Time, 0, len(splits))
179+
for _, v := range splits {
180+
t, err := parseTime(v)
181+
if err != nil {
182+
return nil, err
183+
}
184+
times = append(times, t)
185+
}
186+
return times, nil
187+
}
188+
189+
// parseTime will parses a string parameter into a time.Time using the RFC3339 format
190+
func parseTime(param string) (time.Time, error) {
191+
if param == "" {
192+
return time.Time{}, nil
193+
}
194+
return time.Parse(time.RFC3339, param)
195+
}
196+
197+
type Number interface {
198+
~int32 | ~int64 | ~float32 | ~float64
199+
}
200+
201+
type ParseString[T Number | string | bool] func(v string) (T, error)
202+
203+
// parseFloat64 parses a string parameter to an float64.
204+
func parseFloat64(param string) (float64, error) {
205+
if param == "" {
206+
return 0, nil
207+
}
208+
209+
return strconv.ParseFloat(param, 64)
210+
}
211+
212+
// parseFloat32 parses a string parameter to an float32.
213+
func parseFloat32(param string) (float32, error) {
214+
if param == "" {
215+
return 0, nil
216+
}
217+
218+
v, err := strconv.ParseFloat(param, 32)
219+
return float32(v), err
220+
}
221+
222+
// parseInt64 parses a string parameter to an int64.
223+
func parseInt64(param string) (int64, error) {
224+
if param == "" {
225+
return 0, nil
226+
}
227+
228+
return strconv.ParseInt(param, 10, 64)
229+
}
230+
231+
// parseInt32 parses a string parameter to an int32.
232+
func parseInt32(param string) (int32, error) {
233+
if param == "" {
234+
return 0, nil
235+
}
236+
237+
val, err := strconv.ParseInt(param, 10, 32)
238+
return int32(val), err
239+
}
240+
241+
// parseBool parses a string parameter to an bool.
242+
func parseBool(param string) (bool, error) {
243+
if param == "" {
244+
return false, nil
245+
}
246+
247+
return strconv.ParseBool(param)
248+
}
249+
250+
type Operation[T Number | string | bool] func(actual string) (T, bool, error)
251+
252+
func WithRequire[T Number | string | bool](parse ParseString[T]) Operation[T] {
253+
var empty T
254+
return func(actual string) (T, bool, error) {
255+
if actual == "" {
256+
return empty, false, errors.New(errMsgRequiredMissing)
257+
}
258+
259+
v, err := parse(actual)
260+
return v, false, err
261+
}
262+
}
263+
264+
func WithDefaultOrParse[T Number | string | bool](def T, parse ParseString[T]) Operation[T] {
265+
return func(actual string) (T, bool, error) {
266+
if actual == "" {
267+
return def, true, nil
268+
}
269+
270+
v, err := parse(actual)
271+
return v, false, err
272+
}
273+
}
274+
275+
func WithParse[T Number | string | bool](parse ParseString[T]) Operation[T] {
276+
return func(actual string) (T, bool, error) {
277+
v, err := parse(actual)
278+
return v, false, err
279+
}
280+
}
281+
282+
type Constraint[T Number | string | bool] func(actual T) error
283+
284+
func WithMinimum[T Number](expected T) Constraint[T] {
285+
return func(actual T) error {
286+
if actual < expected {
287+
return errors.New(errMsgMinValueConstraint)
288+
}
289+
290+
return nil
291+
}
292+
}
293+
294+
func WithMaximum[T Number](expected T) Constraint[T] {
295+
return func(actual T) error {
296+
if actual > expected {
297+
return errors.New(errMsgMaxValueConstraint)
298+
}
299+
300+
return nil
301+
}
302+
}
303+
304+
// parseNumericParameter parses a numeric parameter to its respective type.
305+
func parseNumericParameter[T Number](param string, fn Operation[T], checks ...Constraint[T]) (T, error) {
306+
v, ok, err := fn(param)
307+
if err != nil {
308+
return 0, err
309+
}
310+
311+
if !ok {
312+
for _, check := range checks {
313+
if err := check(v); err != nil {
314+
return 0, err
315+
}
316+
}
317+
}
318+
319+
return v, nil
320+
}
321+
322+
// parseBoolParameter parses a string parameter to a bool
323+
func parseBoolParameter(param string, fn Operation[bool]) (bool, error) {
324+
v, _, err := fn(param)
325+
return v, err
326+
}
327+
328+
// parseNumericArrayParameter parses a string parameter containing array of values to its respective type.
329+
func parseNumericArrayParameter[T Number](param, delim string, required bool, fn Operation[T], checks ...Constraint[T]) ([]T, error) {
330+
if param == "" {
331+
if required {
332+
return nil, errors.New(errMsgRequiredMissing)
333+
}
334+
335+
return nil, nil
336+
}
337+
338+
str := strings.Split(param, delim)
339+
values := make([]T, len(str))
340+
341+
for i, s := range str {
342+
v, ok, err := fn(s)
343+
if err != nil {
344+
return nil, err
345+
}
346+
347+
if !ok {
348+
for _, check := range checks {
349+
if err := check(v); err != nil {
350+
return nil, err
351+
}
352+
}
353+
}
354+
355+
values[i] = v
356+
}
357+
358+
return values, nil
359+
}
360+
361+
362+
// parseQuery parses query parameters and returns an error if any malformed value pairs are encountered.
363+
func parseQuery(rawQuery string) (url.Values, error) {
364+
return url.ParseQuery(rawQuery)
365+
}

modules/openapi-generator/src/main/resources/go-server/logger.mustache

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@ import (
55
"log"
66
"net/http"
77
"time"
8+
{{#routers}}
9+
{{#chi}}
10+
"github.com/go-chi/chi/v5/middleware"
11+
{{/chi}}
12+
{{/routers}}
813
)
914

15+
{{#routers}}
16+
{{#mux}}
1017
func Logger(inner http.Handler, name string) http.Handler {
1118
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1219
start := time.Now()
@@ -22,3 +29,10 @@ func Logger(inner http.Handler, name string) http.Handler {
2229
)
2330
})
2431
}
32+
{{/mux}}
33+
{{#chi}}
34+
func Logger(inner http.Handler) http.Handler {
35+
return middleware.Logger(inner)
36+
}
37+
{{/chi}}
38+
{{/routers}}

0 commit comments

Comments
 (0)