Skip to content

Commit

Permalink
fix: supported verb req/resp types (#2198)
Browse files Browse the repository at this point in the history
support Data, Any or TypeAlias over either supported type
  • Loading branch information
worstell authored Jul 30, 2024
1 parent 749fb19 commit c4dd5cc
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 31 deletions.
2 changes: 1 addition & 1 deletion backend/controller/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (c *ConsoleService) GetModules(ctx context.Context, req *connect.Request[pb
var jsonRequestSchema string
if verbSchema.Request != nil {
if requestData, ok := verbSchema.Request.(*schema.Ref); ok {
jsonSchema, err := schema.DataToJSONSchema(sch, *requestData)
jsonSchema, err := schema.RequestResponseToJSONSchema(sch, *requestData)
if err != nil {
return nil, err
}
Expand Down
16 changes: 9 additions & 7 deletions backend/controller/ingress/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,18 +217,20 @@ func buildRequestMap(route *dal.IngressRoute, r *http.Request, ref *schema.Ref,
requestMap[k] = v
}
default:
data, err := sch.ResolveMonomorphised(ref)
symbol, err := sch.ResolveRequestResponseType(ref)
if err != nil {
return nil, err
}

queryMap, err := parseQueryParams(r.URL.Query(), data)
if err != nil {
return nil, fmt.Errorf("HTTP query params are not valid: %w", err)
}
if data, ok := symbol.(*schema.Data); ok {
queryMap, err := parseQueryParams(r.URL.Query(), data)
if err != nil {
return nil, fmt.Errorf("HTTP query params are not valid: %w", err)
}

for key, value := range queryMap {
requestMap[key] = value
for key, value := range queryMap {
requestMap[key] = value
}
}
}

Expand Down
12 changes: 6 additions & 6 deletions backend/schema/jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ import (
"github.com/swaggest/jsonschema-go"
)

// DataToJSONSchema converts the schema for a Data object to a JSON Schema.
// RequestResponseToJSONSchema converts the schema for a Verb request or response object to a JSON Schema.
//
// It takes in the full schema in order to resolve and define references.
func DataToJSONSchema(sch *Schema, ref Ref) (*jsonschema.Schema, error) {
data, err := sch.ResolveMonomorphised(&ref)
func RequestResponseToJSONSchema(sch *Schema, ref Ref) (*jsonschema.Schema, error) {
symbol, err := sch.ResolveRequestResponseType(&ref)
if err != nil {
return nil, err
}
if data == nil {
return nil, fmt.Errorf("unknown data type %s", ref)
if symbol == nil {
return nil, fmt.Errorf("unknown request/response reference %s", ref)
}

// Encode root, and collect all data types reachable from the root.
refs := map[RefKey]*Ref{}
root := nodeToJSSchema(data, refs)
root := nodeToJSSchema(symbol, refs)
if len(refs) == 0 {
return root, nil
}
Expand Down
6 changes: 3 additions & 3 deletions backend/schema/jsonschema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ var jsonSchemaSample = &Schema{
}

func TestDataToJSONSchema(t *testing.T) {
schema, err := DataToJSONSchema(jsonSchemaSample, Ref{Module: "foo", Name: "Foo"})
schema, err := RequestResponseToJSONSchema(jsonSchemaSample, Ref{Module: "foo", Name: "Foo"})
assert.NoError(t, err)
actual, err := json.MarshalIndent(schema, "", " ")
assert.NoError(t, err)
Expand Down Expand Up @@ -317,7 +317,7 @@ func TestJSONSchemaValidation(t *testing.T) {
}
`

schema, err := DataToJSONSchema(jsonSchemaSample, Ref{Module: "foo", Name: "Foo"})
schema, err := RequestResponseToJSONSchema(jsonSchemaSample, Ref{Module: "foo", Name: "Foo"})
assert.NoError(t, err)
schemaJSON, err := json.MarshalIndent(schema, "", " ")
assert.NoError(t, err)
Expand Down Expand Up @@ -355,7 +355,7 @@ func TestInvalidEnumValidation(t *testing.T) {
}
`

schema, err := DataToJSONSchema(jsonSchemaSample, Ref{Module: "foo", Name: "Foo"})
schema, err := RequestResponseToJSONSchema(jsonSchemaSample, Ref{Module: "foo", Name: "Foo"})
assert.NoError(t, err)
schemaJSON, err := json.MarshalIndent(schema, "", " ")
assert.NoError(t, err)
Expand Down
29 changes: 15 additions & 14 deletions backend/schema/jsonvalidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,28 +218,29 @@ func ValidateJSONValue(fieldType Type, path path, value any, sch *Schema) error

// ValidateRequestMap validates a given JSON map against the provided schema.
func ValidateRequestMap(ref *Ref, path path, request map[string]any, sch *Schema) error {
data, err := sch.ResolveMonomorphised(ref)
symbol, err := sch.ResolveRequestResponseType(ref)
if err != nil {
return err
}

var errs []error
for _, field := range data.Fields {
fieldPath := append(path, "."+field.Name) //nolint:gocritic

value, haveValue := request[field.Name]
if !haveValue && !allowMissingField(field) {
errs = append(errs, fmt.Errorf("%s is required", fieldPath))
continue
}
if data, ok := symbol.(*Data); ok {
for _, field := range data.Fields {
fieldPath := append(path, "."+field.Name) //nolint:gocritic

value, haveValue := request[field.Name]
if !haveValue && !allowMissingField(field) {
errs = append(errs, fmt.Errorf("%s is required", fieldPath))
continue
}

if haveValue {
err := ValidateJSONValue(field.Type, fieldPath, value, sch)
if err != nil {
errs = append(errs, err)
if haveValue {
err := ValidateJSONValue(field.Type, fieldPath, value, sch)
if err != nil {
errs = append(errs, err)
}
}
}

}

return errors.Join(errs...)
Expand Down
19 changes: 19 additions & 0 deletions backend/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,26 @@ func (s *Schema) Hash() [sha256.Size]byte {
return sha256.Sum256([]byte(s.String()))
}

// ResolveRequestResponseType resolves a reference to a supported request/response type, which can be a Data or an Any,
// or a TypeAlias over either supported type.
func (s *Schema) ResolveRequestResponseType(ref *Ref) (Symbol, error) {
decl, ok := s.Resolve(ref).Get()
if !ok {
return nil, fmt.Errorf("unknown ref %s", ref)
}

if ta, ok := decl.(*TypeAlias); ok {
if typ, ok := ta.Type.(*Any); ok {
return typ, nil
}
}

return s.resolveToDataMonomorphised(ref, nil)
}

// ResolveMonomorphised resolves a reference to a monomorphised Data type.
// Also supports resolving the monomorphised Data type underlying a TypeAlias, where applicable.
//
// If a Ref is not found, returns ErrNotFound.
func (s *Schema) ResolveMonomorphised(ref *Ref) (*Data, error) {
return s.resolveToDataMonomorphised(ref, nil)
Expand Down

0 comments on commit c4dd5cc

Please sign in to comment.