From 46dbf3ed79f277a10f634e041569838eaf0bda47 Mon Sep 17 00:00:00 2001 From: Nymphium Date: Tue, 30 Apr 2024 21:13:00 +0900 Subject: [PATCH] Respect `required` fields in {target,} schema --- _example/struct.go | 15 +++++++++++++++ _example/validator.go | 16 ++++++++++++++++ parser.go | 43 +++++++++++++++++++++++++++++++++---------- parser_test.go | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/_example/struct.go b/_example/struct.go index bc915fc..4eb9acf 100644 --- a/_example/struct.go +++ b/_example/struct.go @@ -12,6 +12,11 @@ type Error struct { } `json:"errorFields,omitempty"` } +// Misc struct for misc resource +type Misc struct { + ID string `json:"id"` +} + // Task struct for task resource type Task struct { CompletedAt time.Time `json:"completedAt"` @@ -31,6 +36,16 @@ type User struct { Name string `json:"name"` } +// MiscCreateRequest struct for misc +// POST: /bool/register +type MiscCreateRequest struct { + Bool bool `json:"bool,omitempty"` +} + +// MiscCreateResponse struct for misc +// POST: /bool/register +type MiscCreateResponse Misc + // TaskInstancesRequest struct for task // GET: /tasks type TaskInstancesRequest struct { diff --git a/_example/validator.go b/_example/validator.go index 5e53d9a..7a376b8 100644 --- a/_example/validator.go +++ b/_example/validator.go @@ -2,6 +2,7 @@ package taskyapi import "github.com/lestrrat-go/jsval" +var MiscCreateValidator *jsval.JSVal var TaskCreateValidator *jsval.JSVal var TaskInstancesValidator *jsval.JSVal var TaskSelfValidator *jsval.JSVal @@ -22,6 +23,21 @@ func init() { R1 = jsval.String() M.SetReference("#/definitions/task/definitions/tags", R0) M.SetReference("#/definitions/task/definitions/title", R1) + MiscCreateValidator = jsval.New(). + SetName("MiscCreateValidator"). + SetConstraintMap(M). + SetRoot( + jsval.Object(). + Required("bool"). + AdditionalProperties( + jsval.EmptyConstraint, + ). + AddProp( + "bool", + jsval.Boolean(), + ), + ) + TaskCreateValidator = jsval.New(). SetName("TaskCreateValidator"). SetConstraintMap(M). diff --git a/parser.go b/parser.go index ac23da2..9e6e93c 100644 --- a/parser.go +++ b/parser.go @@ -105,7 +105,7 @@ func sortValidator(vals []*jsval.JSVal) []*jsval.JSVal { } // NewProperty new property -func NewProperty(name string, tp *schema.Schema, df *schema.Schema, root *schema.Schema, method string) (*Property, error) { +func NewProperty(name string, tp *schema.Schema, df *schema.Schema, root *schema.Schema, method string, schemaRequired bool) (*Property, error) { // save reference before resolving ref ref := tp.Reference fieldSchema, err := resolveSchema(tp, root) @@ -116,7 +116,7 @@ func NewProperty(name string, tp *schema.Schema, df *schema.Schema, root *schema Name: name, Format: string(fieldSchema.Format), Types: fieldSchema.Type, - Required: df.IsPropRequired(name), + Required: df.IsPropRequired(name) || schemaRequired, Pattern: fieldSchema.Pattern, Reference: ref, Schema: fieldSchema, @@ -145,7 +145,7 @@ func NewProperty(name string, tp *schema.Schema, df *schema.Schema, root *schema // log.Printf("inline obj: %s: %v", name, fieldSchema.Properties) var inlineFields []*Property for k, prop := range fieldSchema.Properties { - f, err := NewProperty(k, prop, df, root, method) + f, err := NewProperty(k, prop, df, root, method, false) if err != nil { return nil, errors.Wrapf(err, "failed to perse inline object: %s", k) } @@ -167,7 +167,7 @@ func NewProperty(name string, tp *schema.Schema, df *schema.Schema, root *schema // log.Printf("resolved inline obj: %s: %v", name, item.Properties) var inlineFields []*Property for k, prop := range item.Properties { - f, err := NewProperty(k, prop, df, root, method) + f, err := NewProperty(k, prop, df, root, method, false) if err != nil { return nil, errors.Wrapf(err, "failed to perse inline object: %s", k) } @@ -179,7 +179,7 @@ func NewProperty(name string, tp *schema.Schema, df *schema.Schema, root *schema // log.Printf("resolved inline obj: %s: %v", name, resolvedItem.Properties) var inlineFields []*Property for k, prop := range resolvedItem.Properties { - f, err := NewProperty(k, prop, df, root, method) + f, err := NewProperty(k, prop, df, root, method, false) if err != nil { return nil, errors.Wrapf(err, "failed to perse inline object: %s", k) } @@ -196,7 +196,7 @@ func NewProperty(name string, tp *schema.Schema, df *schema.Schema, root *schema // inline object without definitions var inlineFields []*Property for k, prop := range fieldSchema.Properties { - f, err := NewProperty(k, prop, df, root, method) + f, err := NewProperty(k, prop, df, root, method, false) if err != nil { return nil, errors.Wrapf(err, "failed to perse inline object: %s", k) } @@ -248,7 +248,15 @@ func (p *Parser) ParseResources() (map[string]Resource, error) { // parse resource field var flds []*Property for name, tp := range df.Properties { - fld, err := NewProperty(name, tp, df, p.schema, "") + schemaRequired := false + for _, required := range df.Required { + if required == name { + schemaRequired = true + break + } + } + + fld, err := NewProperty(name, tp, df, p.schema, "", schemaRequired) if err != nil { return nil, errors.Wrapf(err, "failed to parse %s", id) } @@ -294,7 +302,15 @@ func (p *Parser) ParseActions(res map[string]Resource) (map[string][]Action, err if e.Schema != nil { var flds []*Property for name, tp := range e.Schema.Properties { - fld, err := NewProperty(name, tp, df, p.schema, e.Method) + schemaRequired := false + for _, required := range e.Schema.Required { + if required == name { + schemaRequired = true + break + } + } + + fld, err := NewProperty(name, tp, df, p.schema, e.Method, schemaRequired) if err != nil { return nil, errors.Wrapf(err, "failed to parse %s", id) } @@ -314,7 +330,14 @@ func (p *Parser) ParseActions(res map[string]Resource) (map[string][]Action, err case e.TargetSchema.Reference == "": var flds []*Property for name, tp := range e.TargetSchema.Properties { - fld, err := NewProperty(name, tp, df, p.schema, e.Method) + schemaRequired := false + for _, required := range e.TargetSchema.Required { + if required == name { + schemaRequired = true + break + } + } + fld, err := NewProperty(name, tp, df, p.schema, e.Method, schemaRequired) if err != nil { return nil, errors.Wrapf(err, "failed to parse %s", id) } @@ -335,7 +358,7 @@ func (p *Parser) ParseActions(res map[string]Resource) (map[string][]Action, err IsPrimary: false, } case e.TargetSchema.Reference != "" && !IsRefToMainResource(e.TargetSchema.Reference): - fld, err := NewProperty(e.TargetSchema.ID, e.TargetSchema, df, p.schema, e.Method) + fld, err := NewProperty(e.TargetSchema.ID, e.TargetSchema, df, p.schema, e.Method, false) if err != nil { return nil, errors.Wrapf(err, "failed to parse %s", id) } diff --git a/parser_test.go b/parser_test.go index d2387b5..4586a24 100644 --- a/parser_test.go +++ b/parser_test.go @@ -68,3 +68,37 @@ func TestParseActions(t *testing.T) { } } } + +func TestParseActions_SchemaRequired(t *testing.T) { + parser := testNewParser(t) + r, err := parser.ParseResources() + if err != nil { + t.Fatal(err) + } + + res, err := parser.ParseActions(r) + if err != nil { + t.Fatal(err) + } + + a := res["misc"][0] + if a.Href != "/bool/register" { + t.Fatalf("href is not /bool/register, %s", a.Href) + } + + name := a.Request.Properties[0] + if name.Name != "bool" { + t.Fatalf("not a target: %s", name.Name) + } + if !name.Required { + t.Fatal("bool is required") + } + + isTrue := a.Response.Properties[1] + if isTrue.Name != "isTrue" { + t.Fatalf("not a target: %s", isTrue.Name) + } + if !isTrue.Required { + t.Fatal("isTrue is required") + } +}