-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #116 from cmars/feat/versionware-validation
feat: openapi request and response validation middleware
- Loading branch information
Showing
31 changed files
with
2,732 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
generators: | ||
version-spec: | ||
scope: version | ||
filename: "resources/{{ .Resource }}/{{ .Version }}/spec.yaml" | ||
template: "../../testdata/.vervet/resource/version/spec.yaml.tmpl" | ||
|
||
apis: | ||
example: | ||
resources: | ||
- path: 'resources' | ||
generators: | ||
- version-spec | ||
overlays: | ||
- inline: |- | ||
servers: | ||
- url: https://example.com/api/v3 | ||
description: Test API v3 | ||
output: | ||
path: 'releases' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package chi_test | ||
|
||
import ( | ||
"bytes" | ||
"log" | ||
"net/http" | ||
"net/http/httptest" | ||
"time" | ||
|
||
"github.com/getkin/kin-openapi/openapi3filter" | ||
"github.com/go-chi/chi/v5" | ||
chiware "github.com/go-chi/chi/v5/middleware" | ||
"github.com/prometheus/client_golang/prometheus/promhttp" | ||
metrics "github.com/slok/go-http-metrics/metrics/prometheus" | ||
promware "github.com/slok/go-http-metrics/middleware" | ||
promware_std "github.com/slok/go-http-metrics/middleware/std" | ||
"github.com/snyk/vervet" | ||
"github.com/snyk/vervet/versionware" | ||
|
||
. "github.com/snyk/vervet/versionware/example" | ||
"github.com/snyk/vervet/versionware/example/releases" | ||
release_2021_11_01 "github.com/snyk/vervet/versionware/example/resources/things/2021-11-01" | ||
release_2021_11_08 "github.com/snyk/vervet/versionware/example/resources/things/2021-11-08" | ||
release_2021_11_20 "github.com/snyk/vervet/versionware/example/resources/things/2021-11-20" | ||
"github.com/snyk/vervet/versionware/example/store" | ||
) | ||
|
||
func ExampleChi() { | ||
// Set up a test HTTP server | ||
var h http.Handler | ||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
h.ServeHTTP(w, r) | ||
})) | ||
defer srv.Close() | ||
|
||
// Top level router for test server | ||
root := chi.NewRouter() | ||
h = root | ||
|
||
// Middleware to wrap all requests | ||
root.Use(chiware.RequestID) | ||
root.Use(chiware.RealIP) | ||
root.Use(chiware.Logger) | ||
root.Use(chiware.Recoverer) | ||
root.Use(chiware.Timeout(30 * time.Second)) | ||
root.Use(promware_std.HandlerProvider("", promware.New(promware.Config{ | ||
Recorder: metrics.NewRecorder(metrics.Config{}), | ||
}))) | ||
|
||
// Create a router for just the versioned API | ||
apiRouter := chi.NewRouter() | ||
|
||
// Load OpenAPI specs for all released API versions. | ||
specs, err := vervet.LoadVersions(releases.Versions) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
// Add request and response validation middleware to the API router | ||
validator, err := versionware.NewValidator(&versionware.ValidatorConfig{ | ||
// We're going to mount our API at /api below... | ||
ServerURL: srv.URL + "/api", | ||
Options: []openapi3filter.ValidatorOption{openapi3filter.Strict(true)}, | ||
}, specs...) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
// Only validate the API requests (not other top-level stuff) | ||
apiRouter.Use(validator.Middleware) | ||
|
||
// A new storage backend | ||
s := store.New() | ||
|
||
// Router for the "things" resource | ||
// As the service grows, these could be pulled out into per-resource sub-packages... | ||
thingsRouter := chi.NewRouter() | ||
thingsRouter.Get("/{id}", versionware.NewHandler([]versionware.VersionHandler{{ | ||
Version: release_2021_11_01.Version, | ||
Handler: http.HandlerFunc(release_2021_11_01.GetThing(s)), | ||
}}...).ServeHTTP) | ||
thingsRouter.Get("/", versionware.NewHandler([]versionware.VersionHandler{{ | ||
Version: release_2021_11_08.Version, | ||
Handler: release_2021_11_08.ListThings(s), | ||
}}...).ServeHTTP) | ||
thingsRouter.Post("/", versionware.NewHandler([]versionware.VersionHandler{{ | ||
Version: release_2021_11_01.Version, | ||
Handler: http.HandlerFunc(release_2021_11_01.CreateThing(s)), | ||
}}...).ServeHTTP) | ||
thingsRouter.Delete("/{id}", versionware.NewHandler([]versionware.VersionHandler{{ | ||
Version: release_2021_11_20.Version, | ||
Handler: release_2021_11_20.DeleteThing(s), | ||
}}...).ServeHTTP) | ||
|
||
// Mount the "things" resource router at /things in the API | ||
apiRouter.Mount("/things", thingsRouter) | ||
|
||
// Mount the entire API at /api | ||
root.Mount("/api", apiRouter) | ||
|
||
// Observability stuff at the top-level, not part of the API | ||
root.Get("/metrics", promhttp.Handler().ServeHTTP) | ||
root.Get("/healthcheck", func(w http.ResponseWriter, r *http.Request) { | ||
w.Write([]byte("OK")) | ||
}) | ||
|
||
// Do a health check | ||
PrintResp(srv.Client().Get(srv.URL + "/healthcheck")) | ||
|
||
// Create some things | ||
PrintResp(srv.Client().Post( | ||
srv.URL+"/api/things?version=2021-11-01~experimental", "application/json", | ||
bytes.NewBufferString(`{"name":"foo","color":"blue","strangeness":32}`))) | ||
PrintResp(srv.Client().Post( | ||
srv.URL+"/api/things?version=2021-11-01~experimental", "application/json", | ||
bytes.NewBufferString(`{"name":"shiny","color":"green","strangeness":99}`))) | ||
PrintResp(srv.Client().Post( | ||
srv.URL+"/api/things?version=2021-11-01~experimental", "application/json", | ||
bytes.NewBufferString(`{"name":"cochineal","color":"red","strangeness":5}`))) | ||
|
||
// 404: no matching version | ||
PrintResp(srv.Client().Post( | ||
srv.URL+"/api/things?version=2021-10-01~experimental", "application/json", | ||
bytes.NewBufferString(`{"name":"cochineal","color":"red","strangeness":5}`))) | ||
|
||
// 400: create an invalid thing | ||
PrintResp(srv.Client().Post( | ||
srv.URL+"/api/things?version=2021-11-01~experimental", "application/json", | ||
bytes.NewBufferString(`{"name":"eggplant","color":"purple","banality":17}`))) | ||
|
||
// 200: get a thing | ||
PrintResp(srv.Client().Get(srv.URL + "/api/things/1?version=2021-11-10~experimental")) | ||
|
||
// Output: | ||
// 200 OK | ||
// 200 {"id":"1","created":"2022-01-14T00:23:50Z","attributes":{"name":"foo","color":"blue","strangeness":32}} | ||
// 200 {"id":"2","created":"2022-01-14T00:23:50Z","attributes":{"name":"shiny","color":"green","strangeness":99}} | ||
// 200 {"id":"3","created":"2022-01-14T00:23:50Z","attributes":{"name":"cochineal","color":"red","strangeness":5}} | ||
// 404 Not Found | ||
// 400 bad request | ||
// 200 {"id":"1","created":"2022-01-14T00:23:50Z","attributes":{"name":"foo","color":"blue","strangeness":32}} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package example | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"strings" | ||
) | ||
|
||
// PrintResp prints the response and error from an http client request. This | ||
// is used in example tests. | ||
func PrintResp(resp *http.Response, err error) { | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer resp.Body.Close() | ||
contents, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
panic(err) | ||
} | ||
fmt.Println(resp.StatusCode, strings.TrimSpace(string(contents))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package example | ||
|
||
//go:generate make -C ../.. build | ||
//go:generate ../../vervet compile |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
module github.com/snyk/vervet/versionware/example | ||
|
||
go 1.16 | ||
|
||
require ( | ||
github.com/getkin/kin-openapi v0.87.0 | ||
github.com/go-chi/chi/v5 v5.0.7 | ||
github.com/gorilla/mux v1.8.0 | ||
github.com/prometheus/client_golang v1.11.0 | ||
github.com/slok/go-http-metrics v0.10.0 | ||
github.com/snyk/vervet v1.5.1 | ||
) | ||
|
||
replace github.com/snyk/vervet => ../.. |
Oops, something went wrong.