Skip to content

Commit f92b859

Browse files
committed
BREAKING: create generic engine.OutputOpenAPISpec for use in all engines
1 parent a0f00d5 commit f92b859

File tree

7 files changed

+203
-144
lines changed

7 files changed

+203
-144
lines changed

engine.go

Lines changed: 129 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,139 @@
11
package fuego
22

3-
func NewEngine() *Engine {
4-
return &Engine{
5-
OpenAPI: NewOpenAPI(),
6-
ErrorHandler: ErrorHandler,
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"log/slog"
8+
"os"
9+
"path/filepath"
10+
)
11+
12+
func NewEngine(config ...OpenAPIConfig) *Engine {
13+
if len(config) > 1 {
14+
panic("config should not be more than one")
15+
}
16+
engine := &Engine{
17+
OpenAPI: NewOpenAPI(),
18+
OpenAPIConfig: defaultOpenAPIConfig,
19+
ErrorHandler: ErrorHandler,
20+
}
21+
if len(config) > 0 {
22+
engine.setOpenAPIConfig(config[0])
723
}
24+
return engine
825
}
926

1027
// The Engine is the main struct of the framework.
1128
type Engine struct {
12-
OpenAPI *OpenAPI
13-
ErrorHandler func(error) error
29+
OpenAPI *OpenAPI
30+
ErrorHandler func(error) error
31+
OpenAPIConfig OpenAPIConfig
1432

1533
acceptedContentTypes []string
1634
}
35+
36+
type EngineOpenAPIConfig struct {
37+
// If true, the engine will not print messages
38+
DisableMessages bool
39+
// If true, the engine will not save the OpenAPI JSON spec locally
40+
DisableLocalSave bool
41+
// Local path to save the OpenAPI JSON spec
42+
JsonFilePath string
43+
// Pretty prints the OpenAPI spec with proper JSON indentation
44+
PrettyFormatJson bool
45+
}
46+
47+
// OutputOpenAPISpec takes the OpenAPI spec and outputs it to a JSON file
48+
func (e *Engine) OutputOpenAPISpec() []byte {
49+
e.OpenAPI.computeTags()
50+
51+
// Validate
52+
err := e.OpenAPI.Description().Validate(context.Background())
53+
if err != nil {
54+
slog.Error("Error validating spec", "error", err)
55+
}
56+
57+
// Marshal spec to JSON
58+
jsonSpec, err := e.marshalSpec()
59+
if err != nil {
60+
slog.Error("Error marshaling spec to JSON", "error", err)
61+
}
62+
63+
if !e.OpenAPIConfig.DisableLocalSave {
64+
err := e.saveOpenAPIToFile(e.OpenAPIConfig.JsonFilePath, jsonSpec)
65+
if err != nil {
66+
slog.Error("Error saving spec to local path", "error", err, "path", e.OpenAPIConfig.JsonFilePath)
67+
}
68+
}
69+
return jsonSpec
70+
}
71+
72+
func (e *Engine) saveOpenAPIToFile(jsonSpecLocalPath string, jsonSpec []byte) error {
73+
jsonFolder := filepath.Dir(jsonSpecLocalPath)
74+
75+
err := os.MkdirAll(jsonFolder, 0o750)
76+
if err != nil {
77+
return errors.New("error creating docs directory")
78+
}
79+
80+
f, err := os.Create(jsonSpecLocalPath) // #nosec G304 (file path provided by developer, not by user)
81+
if err != nil {
82+
return errors.New("error creating file")
83+
}
84+
defer f.Close()
85+
86+
_, err = f.Write(jsonSpec)
87+
if err != nil {
88+
return errors.New("error writing file ")
89+
}
90+
91+
e.printOpenAPIMessage("JSON file: " + jsonSpecLocalPath)
92+
return nil
93+
}
94+
95+
func (s *Engine) marshalSpec() ([]byte, error) {
96+
if s.OpenAPIConfig.PrettyFormatJson {
97+
return json.MarshalIndent(s.OpenAPI.Description(), "", " ")
98+
}
99+
return json.Marshal(s.OpenAPI.Description())
100+
}
101+
102+
func (e *Engine) setOpenAPIConfig(config OpenAPIConfig) {
103+
if config.JsonUrl != "" {
104+
e.OpenAPIConfig.JsonUrl = config.JsonUrl
105+
}
106+
107+
if config.SwaggerUrl != "" {
108+
e.OpenAPIConfig.SwaggerUrl = config.SwaggerUrl
109+
}
110+
111+
if config.JsonFilePath != "" {
112+
e.OpenAPIConfig.JsonFilePath = config.JsonFilePath
113+
}
114+
115+
if config.UIHandler != nil {
116+
e.OpenAPIConfig.UIHandler = config.UIHandler
117+
}
118+
119+
e.OpenAPIConfig.DisableSwagger = config.DisableSwagger
120+
e.OpenAPIConfig.DisableSwaggerUI = config.DisableSwaggerUI
121+
e.OpenAPIConfig.DisableLocalSave = config.DisableLocalSave
122+
e.OpenAPIConfig.PrettyFormatJson = config.PrettyFormatJson
123+
124+
if !validateJsonSpecUrl(e.OpenAPIConfig.JsonUrl) {
125+
slog.Error("Error serving openapi json spec. Value of 's.OpenAPIConfig.JsonSpecUrl' option is not valid", "url", e.OpenAPIConfig.JsonUrl)
126+
return
127+
}
128+
129+
if !validateSwaggerUrl(e.OpenAPIConfig.SwaggerUrl) {
130+
slog.Error("Error serving swagger ui. Value of 's.OpenAPIConfig.SwaggerUrl' option is not valid", "url", e.OpenAPIConfig.SwaggerUrl)
131+
return
132+
}
133+
}
134+
135+
func (e *Engine) printOpenAPIMessage(msg string) {
136+
if !e.OpenAPIConfig.DisableMessages {
137+
slog.Info(msg)
138+
}
139+
}

examples/generate-opengraph-image/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ var optionReturnsPNG = func(br *fuego.BaseRoute) {
2222
func main() {
2323
s := fuego.NewServer(
2424
fuego.WithOpenAPIConfig(fuego.OpenAPIConfig{
25-
PrettyFormatJson: true,
25+
EngineOpenAPIConfig: fuego.EngineOpenAPIConfig{
26+
PrettyFormatJson: true,
27+
},
2628
}),
2729
)
2830

examples/petstore/lib/server_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ func TestPetstoreOpenAPIGeneration(t *testing.T) {
1515
server := NewPetStoreServer(
1616
fuego.WithoutStartupMessages(),
1717
fuego.WithOpenAPIConfig(fuego.OpenAPIConfig{
18-
JsonFilePath: "testdata/doc/openapi.json",
19-
PrettyFormatJson: true,
18+
EngineOpenAPIConfig: fuego.EngineOpenAPIConfig{
19+
JsonFilePath: "testdata/doc/openapi.json",
20+
PrettyFormatJson: true,
21+
},
2022
}),
2123
)
2224

openapi.go

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
package fuego
22

33
import (
4-
"context"
5-
"encoding/json"
6-
"errors"
74
"fmt"
85
"log/slog"
96
"net/http"
10-
"os"
11-
"path/filepath"
127
"reflect"
138
"regexp"
149
"slices"
@@ -112,64 +107,13 @@ func (s *Server) OutputOpenAPISpec() openapi3.T {
112107
Description: "local server",
113108
})
114109

115-
s.OpenAPI.computeTags()
116-
117-
// Validate
118-
err := s.OpenAPI.Description().Validate(context.Background())
119-
if err != nil {
120-
slog.Error("Error validating spec", "error", err)
121-
}
122-
123-
// Marshal spec to JSON
124-
jsonSpec, err := s.marshalSpec()
125-
if err != nil {
126-
slog.Error("Error marshaling spec to JSON", "error", err)
127-
}
128-
129110
if !s.OpenAPIConfig.DisableSwagger {
130-
s.registerOpenAPIRoutes(jsonSpec)
131-
}
132-
133-
if !s.OpenAPIConfig.DisableLocalSave {
134-
err := s.saveOpenAPIToFile(s.OpenAPIConfig.JsonFilePath, jsonSpec)
135-
if err != nil {
136-
slog.Error("Error saving spec to local path", "error", err, "path", s.OpenAPIConfig.JsonFilePath)
137-
}
111+
s.registerOpenAPIRoutes(s.Engine.OutputOpenAPISpec())
138112
}
139113

140114
return *s.OpenAPI.Description()
141115
}
142116

143-
func (s *Server) marshalSpec() ([]byte, error) {
144-
if s.OpenAPIConfig.PrettyFormatJson {
145-
return json.MarshalIndent(s.OpenAPI.Description(), "", " ")
146-
}
147-
return json.Marshal(s.OpenAPI.Description())
148-
}
149-
150-
func (s *Server) saveOpenAPIToFile(jsonSpecLocalPath string, jsonSpec []byte) error {
151-
jsonFolder := filepath.Dir(jsonSpecLocalPath)
152-
153-
err := os.MkdirAll(jsonFolder, 0o750)
154-
if err != nil {
155-
return errors.New("error creating docs directory")
156-
}
157-
158-
f, err := os.Create(jsonSpecLocalPath) // #nosec G304 (file path provided by developer, not by user)
159-
if err != nil {
160-
return errors.New("error creating file")
161-
}
162-
defer f.Close()
163-
164-
_, err = f.Write(jsonSpec)
165-
if err != nil {
166-
return errors.New("error writing file ")
167-
}
168-
169-
s.printOpenAPIMessage("JSON file: " + jsonSpecLocalPath)
170-
return nil
171-
}
172-
173117
// Registers the routes to serve the OpenAPI spec and Swagger UI.
174118
func (s *Server) registerOpenAPIRoutes(jsonSpec []byte) {
175119
GetStd(s, s.OpenAPIConfig.JsonUrl, func(w http.ResponseWriter, r *http.Request) {
@@ -189,12 +133,6 @@ func (s *Server) registerOpenAPIRoutes(jsonSpec []byte) {
189133
}
190134
}
191135

192-
func (s *Server) printOpenAPIMessage(msg string) {
193-
if !s.disableStartupMessages {
194-
slog.Info(msg)
195-
}
196-
}
197-
198136
func validateJsonSpecUrl(jsonSpecUrl string) bool {
199137
jsonSpecUrlRegexp := regexp.MustCompile(`^\/[\/a-zA-Z0-9\-\_]+(.json)$`)
200138
return jsonSpecUrlRegexp.MatchString(jsonSpecUrl)

openapi_test.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,9 @@ func TestServer_OutputOpenApiSpec(t *testing.T) {
247247
s := NewServer(
248248
WithOpenAPIConfig(
249249
OpenAPIConfig{
250-
JsonFilePath: docPath,
250+
EngineOpenAPIConfig: EngineOpenAPIConfig{
251+
JsonFilePath: docPath,
252+
},
251253
},
252254
),
253255
)
@@ -268,8 +270,10 @@ func TestServer_OutputOpenApiSpec(t *testing.T) {
268270
s := NewServer(
269271
WithOpenAPIConfig(
270272
OpenAPIConfig{
271-
JsonFilePath: docPath,
272-
DisableLocalSave: true,
273+
EngineOpenAPIConfig: EngineOpenAPIConfig{
274+
JsonFilePath: docPath,
275+
DisableLocalSave: true,
276+
},
273277
},
274278
),
275279
)
@@ -288,9 +292,11 @@ func TestServer_OutputOpenApiSpec(t *testing.T) {
288292
s := NewServer(
289293
WithOpenAPIConfig(
290294
OpenAPIConfig{
291-
JsonFilePath: docPath,
292-
DisableLocalSave: true,
293-
DisableSwagger: true,
295+
EngineOpenAPIConfig: EngineOpenAPIConfig{
296+
JsonFilePath: docPath,
297+
DisableLocalSave: true,
298+
},
299+
DisableSwagger: true,
294300
},
295301
),
296302
)
@@ -310,8 +316,10 @@ func TestServer_OutputOpenApiSpec(t *testing.T) {
310316
s := NewServer(
311317
WithOpenAPIConfig(
312318
OpenAPIConfig{
313-
JsonFilePath: docPath,
314-
PrettyFormatJson: true,
319+
EngineOpenAPIConfig: EngineOpenAPIConfig{
320+
JsonFilePath: docPath,
321+
PrettyFormatJson: true,
322+
},
315323
},
316324
),
317325
)
@@ -426,8 +434,10 @@ func TestLocalSave(t *testing.T) {
426434
func TestAutoGroupTags(t *testing.T) {
427435
s := NewServer(
428436
WithOpenAPIConfig(OpenAPIConfig{
429-
DisableLocalSave: true,
430-
DisableSwagger: true,
437+
EngineOpenAPIConfig: EngineOpenAPIConfig{
438+
DisableLocalSave: true,
439+
},
440+
DisableSwagger: true,
431441
}),
432442
)
433443
Get(s, "/a", func(ContextNoBody) (MyStruct, error) {

0 commit comments

Comments
 (0)