From 91abe796603a4f12f93fbd45fc24b7c98291fda1 Mon Sep 17 00:00:00 2001 From: "Daniel G. Taylor" Date: Tue, 12 Mar 2019 23:07:28 -0700 Subject: [PATCH] Add readOnly/writeOnly support. Part 1 of #30 --- CHANGELOG.md | 1 + apisprout.go | 2 +- example.go | 49 +++++++++++++++++++++++++++++++++++++++++-------- example_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a5030..72637df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Add `readOnly` and `writeOnly` support to the example generator. - Revamped support for `--validate-server` (short `-s`) - Requires the use of server base path(s) on the client. - Localhost is now always allowed on all known base paths. diff --git a/apisprout.go b/apisprout.go index d57fa10..ece1385 100644 --- a/apisprout.go +++ b/apisprout.go @@ -153,7 +153,7 @@ func getTypedExample(mt *openapi3.MediaType) (interface{}, error) { } if mt.Schema != nil { - return OpenAPIExample(mt.Schema.Value) + return OpenAPIExample(ModeResponse, mt.Schema.Value) } // TODO: generate data from JSON schema, if no examples available? diff --git a/example.go b/example.go index a44a477..b2d13ba 100644 --- a/example.go +++ b/example.go @@ -6,6 +6,16 @@ import ( "github.com/getkin/kin-openapi/openapi3" ) +// Mode defines a mode of operation for example generation. +type Mode int + +const ( + // ModeRequest is for the request body (writes to the server) + ModeRequest Mode = iota + // ModeResponse is for the response body (reads from the server) + ModeResponse +) + func getSchemaExample(schema *openapi3.Schema) (interface{}, bool) { if schema.Example != nil { return schema.Example, true @@ -66,10 +76,26 @@ func stringFormatExample(format string) string { return "" } +// excludeFromMode will exclude a schema if the mode is request and the schema +// is read-only, or if the mode is response and the schema is write only. +func excludeFromMode(mode Mode, schema *openapi3.Schema) bool { + if schema == nil { + return true + } + + if mode == ModeRequest && schema.ReadOnly { + return true + } else if mode == ModeResponse && schema.WriteOnly { + return true + } + + return false +} + // OpenAPIExample creates an example structure from an OpenAPI 3 schema // object, which is an extended subset of JSON Schema. // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#schemaObject -func OpenAPIExample(schema *openapi3.Schema) (interface{}, error) { +func OpenAPIExample(mode Mode, schema *openapi3.Schema) (interface{}, error) { if ex, ok := getSchemaExample(schema); ok { return ex, nil } @@ -133,7 +159,7 @@ func OpenAPIExample(schema *openapi3.Schema) (interface{}, error) { example := []interface{}{} if schema.Items != nil && schema.Items.Value != nil { - ex, err := OpenAPIExample(schema.Items.Value) + ex, err := OpenAPIExample(mode, schema.Items.Value) if err != nil { return nil, fmt.Errorf("can't get example for array item") } @@ -150,7 +176,11 @@ func OpenAPIExample(schema *openapi3.Schema) (interface{}, error) { example := map[string]interface{}{} for k, v := range schema.Properties { - ex, err := OpenAPIExample(v.Value) + if excludeFromMode(mode, v.Value) { + continue + } + + ex, err := OpenAPIExample(mode, v.Value) if err != nil { return nil, fmt.Errorf("can't get example for '%s'", k) } @@ -160,12 +190,15 @@ func OpenAPIExample(schema *openapi3.Schema) (interface{}, error) { if schema.AdditionalProperties != nil && schema.AdditionalProperties.Value != nil { addl := schema.AdditionalProperties.Value - ex, err := OpenAPIExample(addl) - if err != nil { - return nil, fmt.Errorf("can't get example for additional properties") - } - example["additionalPropertyName"] = ex + if !excludeFromMode(mode, addl) { + ex, err := OpenAPIExample(mode, addl) + if err != nil { + return nil, fmt.Errorf("can't get example for additional properties") + } + + example["additionalPropertyName"] = ex + } } return example, nil diff --git a/example_test.go b/example_test.go index 8a5ea92..941b687 100644 --- a/example_test.go +++ b/example_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "strings" "testing" "github.com/getkin/kin-openapi/openapi3" @@ -377,6 +378,45 @@ var schemaTests = []struct { `{"type": "string", "default": "one", "example": "two"}`, `"two"`, }, + // ----- Modes ----- + { + "Request mode", + `{"type": "object", "required": ["normal", "readOnly", "writeOnly"], + "properties": { + "normal": { + "type": "string" + }, + "readOnly": { + "type": "string", + "readOnly": true + }, + "writeOnly": { + "type": "string", + "writeOnly": true + } + } + }`, + `{"normal": "string", "writeOnly": "string"}`, + }, + { + "Response mode", + `{"type": "object", "required": ["normal", "readOnly", "writeOnly"], + "properties": { + "normal": { + "type": "string" + }, + "readOnly": { + "type": "string", + "readOnly": true + }, + "writeOnly": { + "type": "string", + "writeOnly": true + } + } + }`, + `{"normal": "string", "readOnly": "string"}`, + }, } func TestGenExample(t *testing.T) { @@ -385,7 +425,11 @@ func TestGenExample(t *testing.T) { schema := &openapi3.Schema{} err := schema.UnmarshalJSON([]byte(tt.in)) assert.NoError(t, err) - example, err := OpenAPIExample(schema) + m := ModeRequest + if strings.Contains(tt.name, "Response") { + m = ModeResponse + } + example, err := OpenAPIExample(m, schema) if tt.out == "" { // Expected to return an error.