diff --git a/rswag-specs/lib/rswag/specs/example_group_helpers.rb b/rswag-specs/lib/rswag/specs/example_group_helpers.rb index 9eaa7ba..b79d76f 100644 --- a/rswag-specs/lib/rswag/specs/example_group_helpers.rb +++ b/rswag-specs/lib/rswag/specs/example_group_helpers.rb @@ -43,7 +43,7 @@ module Rswag # TODO: setup travis CI? # MUST HAVES - # TODO: look at handling different ways schemas can be defined in 3.0 for requestBody and response + # TODO: *** look at handling different ways schemas can be defined in 3.0 for requestBody and response # can we handle all of them? # Then can look at handling different request_body things like $ref, etc # TODO: look at adding request_body method to handle diffs in Open API 2.0 to 3.0 @@ -75,12 +75,20 @@ module Rswag # it can contain a 'value' key which is a direct hash (easiest) # it can contain a 'external_value' key which makes an external call to load the json # it can contain a '$ref' key. Which points to #/components/examples/blog - if passed_examples.first.is_a?(Symbol) - example_key_name = passed_examples.first # can come up with better scheme here - # TODO: write more tests around this adding to the parameter - # if symbol try and use save_request_example - param_attributes = { name: example_key_name, in: :body, required: required, param_value: example_key_name, schema: schema } - parameter(param_attributes) + passed_examples.each do |passed_example| + if passed_example.is_a?(Symbol) + example_key_name = passed_example + # TODO: write more tests around this adding to the parameter + # if symbol try and use save_request_example + param_attributes = { name: example_key_name, in: :body, required: required, param_value: example_key_name, schema: schema } + parameter(param_attributes) + elsif passed_example.is_a?(Hash) && passed_example[:externalValue] + param_attributes = { name: passed_example, in: :body, required: required, param_value: passed_example[:externalValue], schema: schema } + parameter(param_attributes) + elsif passed_example.is_a?(Hash) && passed_example['$ref'] + param_attributes = { name: passed_example, in: :body, required: required, param_value: passed_example['$ref'], schema: schema } + parameter(param_attributes) + end end end end @@ -169,6 +177,45 @@ module Rswag metadata[:response][:examples] = example end + # checks the examples in the parameters should be able to add $ref and externalValue examples. + # This syntax would look something like this in the integration _spec.rb file + # + # request_body_json schema: { '$ref' => '#/components/schemas/blog' }, + # examples: [:blog, {name: :external_blog, + # externalValue: 'http://api.sample.org/myjson_example'}, + # {name: :another_example, + # '$ref' => '#/components/examples/flexible_blog_example'}] + # The first value :blog, points to a let param of the same name, and is used to make the request in the + # integration test (it is used to build the request payload) + # + # The second item in the array shows how to add an externalValue for the examples in the requestBody section + # The third item shows how to add a $ref item that points to the components/examples section of the swagger spec. + # + # NOTE: that the externalValue will produce valid example syntax in the swagger output, but swagger-ui + # will not show it yet + def merge_other_examples!(example_metadata) + # example.metadata[:operation][:requestBody][:content]['application/json'][:examples] + content_node = example_metadata[:operation][:requestBody][:content]['application/json'] + return unless content_node + + external_example = example_metadata[:operation]&.dig(:parameters)&.detect { |p| p[:in] == :body && p[:name].is_a?(Hash) && p[:name][:externalValue] } || {} + ref_example = example_metadata[:operation]&.dig(:parameters)&.detect { |p| p[:in] == :body && p[:name].is_a?(Hash) && p[:name]['$ref'] } || {} + examples_node = content_node[:examples] ||= {} + + nodes_to_add = [] + nodes_to_add << external_example unless external_example.empty? + nodes_to_add << ref_example unless ref_example.empty? + + nodes_to_add.each do |node| + json_request_examples = examples_node ||= {} + other_name = node[:name][:name] + other_key = node[:name][:externalValue] ? :externalValue : '$ref' + if other_name + json_request_examples.merge!(other_name => {other_key => node[:param_value]}) + end + end + end + def run_test!(&block) # NOTE: rspec 2.x support if RSPEC_VERSION < 3 @@ -202,9 +249,13 @@ module Rswag example.metadata[:operation][:requestBody][:content]['application/json'] = { examples: {} } unless example.metadata[:operation][:requestBody][:content]['application/json'][:examples] json_request_examples = example.metadata[:operation][:requestBody][:content]['application/json'][:examples] json_request_examples[body_parameter[:name]] = { value: send(body_parameter[:name]) } + example.metadata[:operation][:requestBody][:content]['application/json'][:examples] = json_request_examples end end + + self.class.merge_other_examples!(example.metadata) if example.metadata[:operation][:requestBody] + end end end diff --git a/rswag-specs/lib/rswag/specs/request_factory.rb b/rswag-specs/lib/rswag/specs/request_factory.rb index adc673c..9fc20b3 100644 --- a/rswag-specs/lib/rswag/specs/request_factory.rb +++ b/rswag-specs/lib/rswag/specs/request_factory.rb @@ -152,12 +152,9 @@ module Rswag end def build_json_payload(parameters, example) - body_param = parameters.select { |p| p[:in] == :body }.first + body_param = parameters.select { |p| p[:in] == :body && p[:name].is_a?(Symbol) }.first return nil unless body_param - # p "example is #{example.send(body_param[:name]).to_json} ** AND body param is #{body_param}" if body_param - # body_param ? example.send(body_param[:name]).to_json : nil - source_body_param = example.send(body_param[:name]) if body_param[:name] && example.respond_to?(body_param[:name]) source_body_param ||= body_param[:param_value] source_body_param ? source_body_param.to_json : nil diff --git a/test-app/app/controllers/blogs_controller.rb b/test-app/app/controllers/blogs_controller.rb index 2719750..75c5ca7 100644 --- a/test-app/app/controllers/blogs_controller.rb +++ b/test-app/app/controllers/blogs_controller.rb @@ -19,6 +19,14 @@ class BlogsController < ApplicationController respond_with @blog end + # POST /blogs/alternate + def alternate_create + + # contrived example to show different :examples in the requestBody section + @blog = Blog.create(params.require(:blog).permit(:title, :content)) + respond_with @blog + end + # Put /blogs/1 def upload @blog = Blog.find_by_id(params[:id]) diff --git a/test-app/config/routes.rb b/test-app/config/routes.rb index 83b9e90..e1fb12d 100644 --- a/test-app/config/routes.rb +++ b/test-app/config/routes.rb @@ -1,6 +1,7 @@ TestApp::Application.routes.draw do post '/blogs/flexible', to: 'blogs#flexible_create' + post '/blogs/alternate', to: 'blogs#alternate_create' resources :blogs put '/blogs/:id/upload', to: 'blogs#upload' diff --git a/test-app/spec/integration/blogs_spec.rb b/test-app/spec/integration/blogs_spec.rb index a3ec90c..8e3bccd 100644 --- a/test-app/spec/integration/blogs_spec.rb +++ b/test-app/spec/integration/blogs_spec.rb @@ -80,6 +80,32 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do end end + path '/blogs/alternate' do + post 'Creates a blog - different :examples in requestBody' do + tags 'Blogs' + description 'Creates a new blog from provided data' + operationId 'createAlternateBlog' + consumes 'application/json' + produces 'application/json' + + # NOTE: the externalValue: http://... is valid 3.0 spec, but swagger-UI does NOT support it yet + # https://github.com/swagger-api/swagger-ui/issues/5433 + request_body_json schema: { '$ref' => '#/components/schemas/blog' }, + examples: [:blog, {name: :external_blog, + externalValue: 'http://api.sample.org/myjson_example'}, + {name: :another_example, + '$ref' => '#/components/examples/flexible_blog_example'}] + + let(:blog) { { blog: { title: 'alt title', content: 'alt bar' } } } + + response '201', 'blog created' do + schema '$ref' => '#/components/schemas/blog' + run_test! + end + end + end + + path '/blogs/{id}' do diff --git a/test-app/spec/swagger_helper.rb b/test-app/spec/swagger_helper.rb index 4c1e7b8..06f597b 100644 --- a/test-app/spec/swagger_helper.rb +++ b/test-app/spec/swagger_helper.rb @@ -69,6 +69,16 @@ RSpec.configure do |config| required: %w[id headline] } }, + examples: { + flexible_blog_example: { + summary: 'Sample example of a flexible blog', + value: { + id: 1, + headline: 'This is a headline', + text: 'Some sample text' + } + } + }, securitySchemes: { basic_auth: { type: :http, diff --git a/test-app/swagger/v1/swagger.json b/test-app/swagger/v1/swagger.json index be26582..ed7938c 100644 --- a/test-app/swagger/v1/swagger.json +++ b/test-app/swagger/v1/swagger.json @@ -258,6 +258,63 @@ } } }, + "/blogs/alternate": { + "post": { + "summary": "Creates a blog - different :examples in requestBody", + "tags": [ + "Blogs" + ], + "description": "Creates a new blog from provided data", + "operationId": "createAlternateBlog", + "requestBody": { + "required": true, + "content": { + "application/json": { + "examples": { + "blog": { + "value": { + "blog": { + "title": "alt title", + "content": "alt bar" + } + } + }, + "external_blog": { + "externalValue": "http://api.sample.org/myjson_example" + }, + "another_example": { + "$ref": "#/components/examples/flexible_blog_example" + } + }, + "schema": { + "$ref": "#/components/schemas/blog" + } + } + } + }, + "parameters": [ + + ], + "responses": { + "201": { + "description": "blog created", + "content": { + "application/json": { + "example": { + "id": 1, + "title": "alt title", + "content": "alt bar", + "thumbnail": null + }, + "schema": { + "$ref": "#/components/schemas/blog" + } + } + } + } + } + } + }, "/blogs/{id}": { "get": { "summary": "Retrieves a blog", @@ -435,6 +492,16 @@ ] } }, + "examples": { + "flexible_blog_example": { + "summary": "Sample example of a flexible blog", + "value": { + "id": 1, + "headline": "This is a headline", + "text": "Some sample text" + } + } + }, "securitySchemes": { "basic_auth": { "type": "http",