diff --git a/_example/doc/schema/schema.json b/_example/doc/schema/schema.json index f9e2358..f285cd6 100644 --- a/_example/doc/schema/schema.json +++ b/_example/doc/schema/schema.json @@ -87,6 +87,76 @@ "detail" ] }, + "misc": { + "$schema": "http://json-schema.org/draft-04/hyper-schema", + "title": "Misc", + "description": "Misc", + "stability": "prototype", + "strictProperties": true, + "type": [ + "object" + ], + "definitions": { + "id": { + "description": "misc id", + "example": "ec0a1edc-062e-11e7-8b1e-040ccee2aa06", + "readOnly": true, + "format": "uuid", + "type": [ + "string" + ] + } + }, + "links": [ + { + "description": "Register bool value", + "href": "/bool/register", + "title": "detail", + "method": "POST", + "rel": "create", + "schema": { + "properties": { + "bool": { + "description": "bool", + "example": true, + "type": [ + "boolean" + ] + } + }, + "required": [ + "bool" + ] + }, + "targetSchema": { + "properties": { + "id": { + "$ref": "#/definitions/misc/definitions/id" + }, + "isTrue": { + "description": "isTrue", + "example": true, + "type": [ + "boolean" + ] + } + }, + "required": [ + "id", + "isTrue" + ] + } + } + ], + "properties": { + "id": { + "$ref": "#/definitions/user/definitions/id" + } + }, + "required": [ + "id" + ] + }, "task": { "$schema": "http://json-schema.org/draft-04/hyper-schema", "title": "Task", @@ -353,6 +423,9 @@ "error": { "$ref": "#/definitions/error" }, + "misc": { + "$ref": "#/definitions/misc" + }, "task": { "$ref": "#/definitions/task" }, diff --git a/_example/doc/schema/schema.md b/_example/doc/schema/schema.md index 204a75b..da82d0f 100644 --- a/_example/doc/schema/schema.md +++ b/_example/doc/schema/schema.md @@ -24,6 +24,8 @@ Tasky-App-Version 1.0.0 ## The table of contents - Error +- Misc + - POST /bool/register - Task - GET /tasks/{task_id} - POST /tasks @@ -47,6 +49,59 @@ This resource represents API error | **errorFields/name** | *string* | param field name | `"status"` | +## Misc + +Stability: `prototype` + +Misc + +### Attributes + +| Name | Type | Description | Example | +| ------- | ------- | ------- | ------- | +| **[id](#resource-user)** | *uuid* | user id | `"ec0a1edc-062e-11e7-8b1e-040ccee2aa06"` | + +### Misc detail + +Register bool value + +``` +POST /bool/register +``` + +#### Required Parameters + +| Name | Type | Description | Example | +| ------- | ------- | ------- | ------- | +| **bool** | *boolean* | bool | `true` | + + + +#### Curl Example + +```bash +$ curl -n -X POST https://tasky.io/v1/bool/register \ + -d '{ + "bool": true +}' \ + -H "Content-Type: application/json" +``` + + +#### Response Example + +``` +HTTP/1.1 201 Created +``` + +```json +{ + "id": "ec0a1edc-062e-11e7-8b1e-040ccee2aa06", + "isTrue": true +} +``` + + ## Task Stability: `prototype` diff --git a/_example/doc/schema/schemata/misc.yml b/_example/doc/schema/schemata/misc.yml new file mode 100644 index 0000000..e81d40a --- /dev/null +++ b/_example/doc/schema/schemata/misc.yml @@ -0,0 +1,48 @@ +--- +"$schema": http://json-schema.org/draft-04/hyper-schema +title: Misc +description: Misc +stability: prototype +strictProperties: true +type: + - object + +definitions: + id: + description: misc id + example: "ec0a1edc-062e-11e7-8b1e-040ccee2aa06" + readOnly: true + format: uuid + type: string + +links: + - description: "Register bool value" + href: "/bool/register" + title: detail + method: POST + rel: create + schema: + properties: + bool: + description: bool + example: true + type: boolean + required: + - bool + targetSchema: + properties: + id: + $ref: "/schemata/misc#/definitions/id" + isTrue: + description: isTrue + example: true + type: boolean + required: + - id + - isTrue +properties: + id: + $ref: "/schemata/user#/definitions/id" +required: + - id +id: schemata/misc 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 230c0f8..7a376b8 100644 --- a/_example/validator.go +++ b/_example/validator.go @@ -1,7 +1,8 @@ package taskyapi -import jsval "github.com/lestrrat-go/go-jsval" +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") + } +}