这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,23 +103,14 @@ func (c *oaComponents) addSchema(t reflect.Type, mode schema.Mode, hint string,
return c.addExistingSchema(s, name, generateSchemaField)
}

//AddExistingSchema adds an existing schema instance under the given name.
// AddExistingSchema adds an existing schema instance under the given name.
func (c *oaComponents) AddExistingSchema(s *schema.Schema, name string, generateSchemaField bool) string {
return c.addExistingSchema(s, name, generateSchemaField)
}

func (c *oaComponents) addExistingSchema(s *schema.Schema, name string, generateSchemaField bool) string {
if generateSchemaField && s.Type == schema.TypeObject && s.Properties != nil {
if s.Properties["$schema"] == nil {
// Some editors allow you to place a $schema key which gives you rich
// validation and code completion support. Let's enable that by allowing
// a field here if it doesn't already exist in the model.
s.Properties["$schema"] = &schema.Schema{
Type: schema.TypeString,
Format: "uri",
Description: "An optional URL to a JSON Schema document describing this resource",
}
}
if generateSchemaField {
s.AddSchemaField()
}

orig := name
Expand Down
10 changes: 5 additions & 5 deletions openapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,26 @@ func TestComponentSchemas(t *testing.T) {
}

// Adding two different versions of the same component.
ref := components.AddSchema(reflect.TypeOf(&componentFoo{}), schema.ModeRead, "hint")
ref := components.AddSchema(reflect.TypeOf(&componentFoo{}), schema.ModeRead, "hint", true)
assert.Equal(t, ref, "#/components/schemas/componentFoo")
assert.NotNil(t, components.Schemas["componentFoo"])

ref = components.AddSchema(reflect.TypeOf(&componentFoo{}), schema.ModeWrite, "hint")
ref = components.AddSchema(reflect.TypeOf(&componentFoo{}), schema.ModeWrite, "hint", true)
assert.Equal(t, ref, "#/components/schemas/componentFoo2")
assert.NotNil(t, components.Schemas["componentFoo2"])

// Re-adding the second should not create a third.
ref = components.AddSchema(reflect.TypeOf(&componentFoo{}), schema.ModeWrite, "hint")
ref = components.AddSchema(reflect.TypeOf(&componentFoo{}), schema.ModeWrite, "hint", true)
assert.Equal(t, ref, "#/components/schemas/componentFoo2")
assert.Nil(t, components.Schemas["componentFoo3"])

// Adding a list of pointers to a struct.
ref = components.AddSchema(reflect.TypeOf([]*componentBar{}), schema.ModeAll, "hint")
ref = components.AddSchema(reflect.TypeOf([]*componentBar{}), schema.ModeAll, "hint", true)
assert.Equal(t, ref, "#/components/schemas/componentBarList")
assert.NotNil(t, components.Schemas["componentBarList"])

// Adding an anonymous empty struct, should use the hint.
ref = components.AddSchema(reflect.TypeOf(struct{}{}), schema.ModeAll, "hint")
ref = components.AddSchema(reflect.TypeOf(struct{}{}), schema.ModeAll, "hint", true)
assert.Equal(t, ref, "#/components/schemas/hint")
assert.NotNil(t, components.Schemas["hint"])
}
3 changes: 3 additions & 0 deletions operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ func (o *Operation) Run(handler interface{}) {

if o.requestSchema == nil {
o.requestSchema, err = schema.GenerateWithMode(body.Type, schema.ModeWrite, nil)
if o.resource != nil && o.resource.router != nil && !o.resource.router.disableSchemaProperty {
o.requestSchema.AddSchemaField()
}
if err != nil {
panic(fmt.Errorf("unable to generate JSON schema: %w", err))
}
Expand Down
49 changes: 49 additions & 0 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,52 @@ func TestDefaultResponse(t *testing.T) {
// This should not panic and should return the 200 OK
assert.Equal(t, http.StatusOK, w.Result().StatusCode)
}

func TestRoundTrip(t *testing.T) {
app := newTestRouter()

// This should not crash.
resource := app.Resource("/")

type Thing struct {
Name string `json:"name"`
}

resource.Get("get-root", "docs", NewResponse(0, "").Model(Thing{})).Run(func(ctx Context) {
ctx.WriteModel(http.StatusOK, Thing{Name: "Test"})
})

resource.Put("put-root", "", NewResponse(200, "")).Run(func(ctx Context, input struct {
Body Thing
}) {
// If we get here then all the validation passed okay!
assert.Equal(t, "Test", input.Body.Name)
})

w1 := httptest.NewRecorder()
req1, _ := http.NewRequest(http.MethodGet, "/openapi.json", nil)
app.ServeHTTP(w1, req1)
t.Log(w1.Body.String())

w1 = httptest.NewRecorder()
req1, _ = http.NewRequest(http.MethodGet, "/schemas/Thing.json", nil)
app.ServeHTTP(w1, req1)
t.Log(w1.Body.String())

w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/", nil)
app.ServeHTTP(w, req)

// This should not panic and should return the 200 OK
assert.Equal(t, http.StatusOK, w.Result().StatusCode)

thing := w.Body

w = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodPut, "/", thing)
app.ServeHTTP(w, req)

t.Log(w.Body.String())

assert.Equal(t, http.StatusOK, w.Result().StatusCode)
}
18 changes: 18 additions & 0 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,24 @@ func (s *Schema) RemoveProperty(name string) {
}
}

// AddSchemaField adds a $schema field if none is present, allowing the
// resource to be self-descriptive and enabling editor features like
// code completion / suggestions as you type & inline linting / validation.
func (s *Schema) AddSchemaField() {
if s.Type == TypeObject && s.Properties != nil {
if s.Properties["$schema"] == nil {
// Some editors allow you to place a $schema key which gives you rich
// validation and code completion support. Let's enable that by allowing
// a field here if it doesn't already exist in the model.
s.Properties["$schema"] = &Schema{
Type: TypeString,
Format: "uri",
Description: "An optional URL to a JSON Schema document describing this resource",
}
}
}
}

// Generate creates a JSON schema for a Go type. Struct field tags
// can be used to provide additional metadata such as descriptions and
// validation.
Expand Down
9 changes: 9 additions & 0 deletions schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Example() {
Expand Down Expand Up @@ -621,3 +622,11 @@ func TestExampleBadJSON(t *testing.T) {
_, err := Generate(reflect.TypeOf(Foo{}))
assert.Error(t, err)
}

func TestAddSchemaField(t *testing.T) {
dummy := struct{ Name string }{Name: "test"}
s, err := Generate(reflect.TypeOf(dummy))
require.NoError(t, err)
s.AddSchemaField()
assert.NotNil(t, s.Properties["$schema"])
}