Skip to content

Commit b1b4630

Browse files
Merge branch 'master' into prefer-request
2 parents 8533b2c + f78ef39 commit b1b4630

File tree

5 files changed

+139
-8
lines changed

5 files changed

+139
-8
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A simple, quick, cross-platform API mock server that returns examples specified
1515
- Server validation (enabled with `--validate-server`)
1616
- Validates scheme, hostname/port, and base path
1717
- Supports `localhost` out of the box
18+
- Use the `--add-server` flag, in conjunction with `--validate-server`, to dynamically include more servers in the validation logic
1819
- Request parameter & body validation (enabled with `--validate-request`)
1920
- Configuration via:
2021
- Files (`/etc/apisprout/config.json|yaml`)
@@ -30,6 +31,9 @@ apisprout my-api.yaml
3031
# Validate server name and use base path
3132
apisprout --validate-server my-api.yaml
3233

34+
# Dynamically Include a new server / path in the validation
35+
apisprout --add-server http://localhost:8080/mock --validate-server my-api.yaml
36+
3337
# Load from a URL
3438
apisprout https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/api-with-examples.yaml
3539
```
@@ -60,6 +64,16 @@ Alternatively, you can use `go get`:
6064
go get github.com/danielgtaylor/apisprout
6165
```
6266

67+
## Extra Features
68+
69+
### Remote Reload
70+
71+
If your API spec is loaded from a remote URL, you can live-reload it by hitting the `/__reload` endpoint.
72+
73+
### Health Check
74+
75+
A simple endpoint which returns status code `200` is available at `/__health`. This endpoint successfully returns `200` even if `--validate-server` is turned on, and the endpoint is being accessed from a non-validated host.
76+
6377
## Contributing
6478

6579
Contributions are very welcome. Please open a tracking issue or pull request and we can work to get things merged in.

apisprout.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ func main() {
116116
addParameter(flags, "disable-cors", "", false, "Disable CORS headers")
117117
addParameter(flags, "header", "H", "", "Add a custom header when fetching API")
118118
addParameter(flags, "add-server", "", "", "Add a new valid server URL, use with --validate-server")
119+
addParameter(flags, "https", "", false, "Use HTTPS instead of HTTP")
120+
addParameter(flags, "public-key", "", "", "Public key for HTTPS, use with --https")
121+
addParameter(flags, "private-key", "", "", "Private key for HTTPS, use with --https")
119122

120123
// Run the app!
121124
root.Execute()
@@ -193,19 +196,25 @@ func getExample(negotiator *ContentNegotiator, prefer map[string]string, op *ope
193196
other = append(other, s)
194197
}
195198
responses = append(success, other...)
196-
} else {
197-
if op.Responses[prefer["status"]] == nil {
198-
return 0, "", blankHeaders, nil, ErrNoExample
199-
}
199+
} else if op.Responses[prefer["status"]] != nil {
200200
responses = []string{prefer["status"]}
201+
} else if op.Responses["default"] != nil {
202+
responses = []string{"default"}
203+
} else {
204+
return 0, "", blankHeaders, nil, ErrNoExample
201205
}
202206

203207
// Now try to find the first example we can and return it!
204208
for _, s := range responses {
205209
response := op.Responses[s]
206210
status, err := strconv.Atoi(s)
207211
if err != nil {
208-
// Treat default and other named statuses as 200.
212+
// If we are using the default with prefer, we can use its status
213+
// code:
214+
status, err = strconv.Atoi(prefer)
215+
}
216+
if err != nil {
217+
// Otherwise, treat default and other named statuses as 200.
209218
status = http.StatusOK
210219
}
211220

@@ -511,6 +520,12 @@ func server(cmd *cobra.Command, args []string) {
511520
})
512521
}
513522

523+
// Add a health check route which returns 200
524+
http.HandleFunc("/__health", func(w http.ResponseWriter, r *http.Request) {
525+
w.WriteHeader(200)
526+
log.Printf("Health check")
527+
})
528+
514529
// Register our custom HTTP handler that will use the router to find
515530
// the appropriate OpenAPI operation and try to return an example.
516531
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
@@ -675,7 +690,11 @@ func server(cmd *cobra.Command, args []string) {
675690
w.Write(encoded)
676691
})
677692

678-
fmt.Printf("🌱 Sprouting %s on port %d", swagger.Info.Title, viper.GetInt("port"))
693+
format := "🌱 Sprouting %s on port %d"
694+
if viper.GetBool("https") {
695+
format = "🌱 Securely sprouting %s on port %d"
696+
}
697+
fmt.Printf(format, swagger.Info.Title, viper.GetInt("port"))
679698

680699
if viper.GetBool("validate-server") && len(swagger.Servers) != 0 {
681700
fmt.Printf(" with valid servers:\n")
@@ -686,5 +705,14 @@ func server(cmd *cobra.Command, args []string) {
686705
fmt.Printf("\n")
687706
}
688707

689-
http.ListenAndServe(fmt.Sprintf(":%d", viper.GetInt("port")), nil)
708+
port := fmt.Sprintf(":%d", viper.GetInt("port"))
709+
if viper.GetBool("https") {
710+
err = http.ListenAndServeTLS(port, viper.GetString("public-key"),
711+
viper.GetString("private-key"), nil)
712+
} else {
713+
err = http.ListenAndServe(port, nil)
714+
}
715+
if err != nil {
716+
log.Fatal(err)
717+
}
690718
}

example.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,35 @@ func OpenAPIExample(mode Mode, schema *openapi3.Schema) (interface{}, error) {
100100
return ex, nil
101101
}
102102

103+
// Handle combining keywords
104+
if len(schema.OneOf) > 0 {
105+
return OpenAPIExample(mode, schema.OneOf[0].Value)
106+
}
107+
if len(schema.AnyOf) > 0 {
108+
return OpenAPIExample(mode, schema.AnyOf[0].Value)
109+
}
110+
if len(schema.AllOf) > 0 {
111+
example := map[string]interface{}{}
112+
113+
for _, allOf := range schema.AllOf {
114+
candidate, err := OpenAPIExample(mode, allOf.Value)
115+
if err != nil {
116+
return nil, err
117+
}
118+
119+
value, ok := candidate.(map[string]interface{})
120+
if !ok {
121+
return nil, ErrNoExample
122+
}
123+
124+
for k, v := range value {
125+
example[k] = v
126+
}
127+
}
128+
129+
return example, nil
130+
}
131+
103132
switch {
104133
case schema.Type == "boolean":
105134
return true, nil

example_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,67 @@ var schemaTests = []struct {
417417
}`,
418418
`{"normal": "string", "readOnly": "string"}`,
419419
},
420+
// ----- Combination keywords -----
421+
{
422+
"Combine with allOf",
423+
`{
424+
"allOf": [
425+
{
426+
"type": "object",
427+
"properties": {
428+
"foo": {"type": "string"}
429+
}
430+
},
431+
{
432+
"type": "object",
433+
"properties": {
434+
"bar": {"type": "boolean"}
435+
}
436+
}
437+
]
438+
}`,
439+
`{"foo": "string", "bar": true}`,
440+
},
441+
{
442+
"Combine with anyOf",
443+
`{
444+
"anyOf": [
445+
{
446+
"type": "object",
447+
"properties": {
448+
"foo": {"type": "string"}
449+
}
450+
},
451+
{
452+
"type": "object",
453+
"properties": {
454+
"bar": {"type": "boolean"}
455+
}
456+
}
457+
]
458+
}`,
459+
`{"foo": "string"}`,
460+
},
461+
{
462+
"Combine with oneOf",
463+
`{
464+
"oneOf": [
465+
{
466+
"type": "object",
467+
"properties": {
468+
"foo": {"type": "string"}
469+
}
470+
},
471+
{
472+
"type": "object",
473+
"properties": {
474+
"bar": {"type": "boolean"}
475+
}
476+
}
477+
]
478+
}`,
479+
`{"foo": "string"}`,
480+
},
420481
}
421482

422483
func TestGenExample(t *testing.T) {

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ module github.com/danielgtaylor/apisprout
33
go 1.12
44

55
require (
6-
github.com/davecgh/go-spew v1.1.1
76
github.com/fsnotify/fsnotify v1.4.7
87
github.com/getkin/kin-openapi v0.2.0
98
github.com/gobwas/glob v0.2.3

0 commit comments

Comments
 (0)