diff --git a/Gemfile.lock b/Gemfile.lock index 6e8db2a9..3794dbcf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - rspec_api_documentation (6.1.0) + rspec_api_documentation (7.0.0) activesupport (>= 3.0.0) mustache (~> 1.0, >= 0.99.4) rspec (~> 3.0) @@ -75,7 +75,7 @@ GEM multi_json (1.11.2) multi_test (0.1.2) multipart-post (2.0.0) - mustache (1.0.5) + mustache (1.1.1) nokogiri (1.8.4) mini_portile2 (~> 2.3.0) pry (0.11.3) @@ -157,4 +157,4 @@ DEPENDENCIES yard (>= 0.9.11) BUNDLED WITH - 1.16.4 + 1.17.2 diff --git a/README.md b/README.md index 6151e8ec..fa6feb5d 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ resource "Orders" do example "Listing orders" do do_request - status.should == 200 + expect(status).to eq 200 end end end @@ -258,6 +258,7 @@ RspecApiDocumentation.configure do |config| config.html_embedded_css_file = nil # Removes the DSL method `status`, this is required if you have a parameter named status + # In this case you can assert response status with `expect(response_status).to eq 200` config.disable_dsl_status! # Removes the DSL method `method`, this is required if you have a parameter named method @@ -360,7 +361,8 @@ This [format](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/ * Several new options on `parameter` helper. - - `with_example: true`. This option will adjust your description of the parameter with the passed value. + - `with_example: true`. This option will adjust your example of the parameter with the passed value. + - `example: `. Will provide a example value for the parameter. - `default: `. Will provide a default value for the parameter. - `minimum: `. Will setup upper limit for your parameter. - `maximum: `. Will setup lower limit for your parameter. @@ -515,18 +517,18 @@ resource "Account" do # default :document is :all example "Get a list of all accounts" do do_request - status.should == 200 + expect(status).to eq 200 end # Don't actually document this example, purely for testing purposes example "Get a list on page 2", :document => false do do_request(:page => 2) - status.should == 404 + expect(status).to eq 404 end # With example_request, you can't change the :document example_request "Get a list on page 3", :page => 3 do - status.should == 404 + expect(status).to eq 404 end end @@ -535,12 +537,12 @@ resource "Account" do example "Creating an account", :document => :private do do_request(:email => "eric@example.com") - status.should == 201 + expect(status).to eq 201 end example "Creating an account - errors", :document => [:private, :developers] do do_request - status.should == 422 + expect(status).to eq 422 end end end @@ -609,7 +611,7 @@ resource "Orders" do let(:id) { order.id } example "Get an order" do - path.should == "/orders/1" # `:id` is replaced with the value of `id` + expect(path).to eq "/orders/1" # `:id` is replaced with the value of `id` end end @@ -699,7 +701,7 @@ resource "Orders" do get "/orders" do example_request "Headers" do - headers.should == { "Accept" => "application/json", "X-Custom" => "dynamic" } + expect(headers).to eq { "Accept" => "application/json", "X-Custom" => "dynamic" } end end end @@ -738,7 +740,7 @@ resource "Orders" do # OR let(:order_item_item_id) { 1 } example "Creating an order" do - params.should eq({ + expect(params).to eq({ :order => { :name => "My Order", :item => { @@ -827,7 +829,7 @@ resource "Order" do do_request - status.should == 200 + expect(status).to eq 200 end end end @@ -847,7 +849,7 @@ resource "Order" do example "Listing orders" do do_request - status.should == 200 + expect(status).to eq 200 end end end @@ -868,7 +870,7 @@ resource "Order" do do_request - status.should == 200 + expect(status).to eq 200 end end end @@ -890,7 +892,7 @@ resource "Orders" do get "/orders" do example_request "Headers" do - headers.should == { "Accept" => "application/json" } + expect(headers).to eq { "Accept" => "application/json" } end end end @@ -910,7 +912,7 @@ resource "Order" do example "Listing orders" do do_request - response_body.should == [{ :name => "Order 1" }].to_json + expect(response_body).to eq [{ :name => "Order 1" }].to_json end end end @@ -926,7 +928,7 @@ resource "Order" do example "Listing orders" do do_request - response_headers["Content-Type"].should == "application/json" + expect(response_headers["Content-Type"]).to eq "application/json" end end end @@ -942,8 +944,8 @@ resource "Order" do example "Listing orders" do do_request - status.should == 200 - response_status.should == 200 + expect(status).to eq 200 + expect(response_status).to eq 200 end end end @@ -961,7 +963,7 @@ resource "Orders" do get "/orders" do example "List orders" do - query_string.should == "name=My+Orders" + expect(query_string).to eq "name=My+Orders" end end end diff --git a/features/api_blueprint_documentation.feature b/features/api_blueprint_documentation.feature index b78e19ef..59884c2e 100644 --- a/features/api_blueprint_documentation.feature +++ b/features/api_blueprint_documentation.feature @@ -115,6 +115,11 @@ Feature: Generate API Blueprint documentation from test examples attribute :name, 'The order name', required: true, :example => 'a name' attribute :amount, required: false attribute :description, 'The order description', type: 'string', required: false, example: "a description" + attribute :category, 'The order category', type: 'string', required: false, default: 'normal', enum: %w[normal priority] + attribute :metadata, 'The order metadata', type: 'json', required: false, annotation: <<-MARKDOWN + + instructions (optional, string) + + notes (optional, string) + MARKDOWN get 'Returns a single order' do explanation "This is used to return orders." @@ -250,6 +255,7 @@ Feature: Generate API Blueprint documentation from test examples """ FORMAT: 1A # Example API + Example API Description # Group Instructions @@ -360,6 +366,14 @@ Feature: Generate API Blueprint documentation from test examples + name: a name (required) - The order name + amount (optional) + description: a description (optional, string) - The order description + + category (optional, string) - The order category + + Default: `normal` + + Members + + `normal` + + `priority` + + metadata (optional, json) - The order metadata + + instructions (optional, string) + + notes (optional, string) ### Deletes a specific order [DELETE] diff --git a/features/open_api.feature b/features/open_api.feature index b7ba07dd..a24b7094 100644 --- a/features/open_api.feature +++ b/features/open_api.feature @@ -192,7 +192,7 @@ Feature: Generate Open API Specification from test examples parameter :name, 'The order name', required: true, scope: :data, with_example: true parameter :amount, required: false, scope: :data, with_example: true - parameter :description, 'The order description', required: false, scope: :data, with_example: true + parameter :description, 'The order description', required: true, scope: :data, with_example: true header "Content-Type", "application/json" @@ -342,19 +342,16 @@ Feature: Generate Open API Specification from test examples "200": { "description": "List all instructions", "schema": { - "description": "", "type": "object", "properties": { } }, "headers": { "Content-Type": { - "description": "", "type": "string", "x-example-value": "text/html;charset=utf-8" }, "Content-Length": { - "description": "", "type": "string", "x-example-value": "57" } @@ -424,17 +421,21 @@ Feature: Generate Open API Specification from test examples { "name": "one_level_arr", "in": "query", - "description": " one level arr\nEg, `[\"value1\", \"value2\"]`", + "description": " one level arr", "required": false, "type": "array", "items": { "type": "string" - } + }, + "example": [ + "value1", + "value2" + ] }, { "name": "two_level_arr", "in": "query", - "description": " two level arr\nEg, `[[5.1, 3.0], [1.0, 4.5]]`", + "description": " two level arr", "required": false, "type": "array", "items": { @@ -442,26 +443,33 @@ Feature: Generate Open API Specification from test examples "items": { "type": "number" } - } + }, + "example": [ + [ + 5.1, + 3.0 + ], + [ + 1.0, + 4.5 + ] + ] } ], "responses": { "200": { "description": "Getting a list of orders", "schema": { - "description": "", "type": "object", "properties": { } }, "headers": { "Content-Type": { - "description": "", "type": "string", "x-example-value": "application/vnd.api+json" }, "Content-Length": { - "description": "", "type": "string", "x-example-value": "137" } @@ -509,7 +517,6 @@ Feature: Generate Open API Specification from test examples "description": "", "required": false, "schema": { - "description": "", "type": "object", "properties": { "data": { @@ -560,19 +567,16 @@ Feature: Generate Open API Specification from test examples "201": { "description": "Creating an order", "schema": { - "description": "", "type": "object", "properties": { } }, "headers": { "Content-Type": { - "description": "", "type": "string", "x-example-value": "application/json" }, "Content-Length": { - "description": "", "type": "string", "x-example-value": "73" } @@ -611,7 +615,6 @@ Feature: Generate Open API Specification from test examples { "name": "id", "in": "path", - "description": "", "required": true, "type": "integer" } @@ -620,19 +623,16 @@ Feature: Generate Open API Specification from test examples "200": { "description": "Getting a specific order", "schema": { - "description": "", "type": "object", "properties": { } }, "headers": { "Content-Type": { - "description": "", "type": "string", "x-example-value": "application/json" }, "Content-Length": { - "description": "", "type": "string", "x-example-value": "73" } @@ -669,7 +669,6 @@ Feature: Generate Open API Specification from test examples { "name": "id", "in": "path", - "description": "", "required": true, "type": "integer" }, @@ -679,7 +678,6 @@ Feature: Generate Open API Specification from test examples "description": "", "required": false, "schema": { - "description": "", "type": "object", "properties": { "data": { @@ -702,7 +700,8 @@ Feature: Generate Open API Specification from test examples } }, "required": [ - "name" + "name", + "description" ] } } @@ -713,19 +712,16 @@ Feature: Generate Open API Specification from test examples "200": { "description": "Update an order", "schema": { - "description": "", "type": "object", "properties": { } }, "headers": { "Content-Type": { - "description": "", "type": "string", "x-example-value": "application/json" }, "Content-Length": { - "description": "", "type": "string", "x-example-value": "63" } @@ -736,19 +732,16 @@ Feature: Generate Open API Specification from test examples "400": { "description": "Invalid request", "schema": { - "description": "", "type": "object", "properties": { } }, "headers": { "Content-Type": { - "description": "", "type": "string", "x-example-value": "application/json" }, "Content-Length": { - "description": "", "type": "string", "x-example-value": "0" } @@ -778,7 +771,6 @@ Feature: Generate Open API Specification from test examples { "name": "id", "in": "path", - "description": "", "required": true, "type": "integer" } @@ -787,19 +779,16 @@ Feature: Generate Open API Specification from test examples "200": { "description": "Deleting an order", "schema": { - "description": "", "type": "object", "properties": { } }, "headers": { "Content-Type": { - "description": "", "type": "string", "x-example-value": "text/html;charset=utf-8" }, "Content-Length": { - "description": "", "type": "string", "x-example-value": "0" } diff --git a/lib/rspec_api_documentation.rb b/lib/rspec_api_documentation.rb index 5986aadb..c5fe5983 100644 --- a/lib/rspec_api_documentation.rb +++ b/lib/rspec_api_documentation.rb @@ -52,25 +52,28 @@ module Writers module OpenApi extend ActiveSupport::Autoload + autoload :Components + autoload :Contact + autoload :ExternalDocs + autoload :Flow + autoload :Header autoload :Helper - autoload :Node - autoload :Root autoload :Info - autoload :Contact autoload :License - autoload :Paths - autoload :Path - autoload :Tag + autoload :Media + autoload :Node autoload :Operation autoload :Parameter - autoload :Responses + autoload :Path + autoload :RequestBody autoload :Response - autoload :Example - autoload :Headers - autoload :Header + autoload :Root autoload :Schema - autoload :SecurityDefinitions - autoload :SecuritySchema + autoload :SecurityScheme + autoload :Server + autoload :Tag + autoload :Variable + autoload :Reference end module Views diff --git a/lib/rspec_api_documentation/client_base.rb b/lib/rspec_api_documentation/client_base.rb index d234391b..a3ba2efc 100644 --- a/lib/rspec_api_documentation/client_base.rb +++ b/lib/rspec_api_documentation/client_base.rb @@ -63,6 +63,7 @@ def document_example(method, path) request_metadata[:request_method] = method request_metadata[:request_path] = path request_metadata[:request_body] = request_body.empty? ? nil : request_body.force_encoding("UTF-8") + request_metadata[:request_body_hash] = request_body.empty? ? nil : last_request.env["rack.request.form_hash"] request_metadata[:request_headers] = request_headers request_metadata[:request_query_parameters] = query_hash request_metadata[:request_content_type] = request_content_type @@ -87,6 +88,11 @@ def headers(method, path, params, request_headers) def record_response_body(response_content_type, response_body) return nil if response_body.empty? + + # even when the response is has charset UTF8 still the encoding is Encoding::ASCII_8BIT + # forcing the response to UTF-8 + response_body.force_encoding("UTF-8") if response_body.is_utf8? + if response_body.encoding == Encoding::ASCII_8BIT "[binary data]" else diff --git a/lib/rspec_api_documentation/dsl/endpoint.rb b/lib/rspec_api_documentation/dsl/endpoint.rb index 1b221914..2764a07a 100644 --- a/lib/rspec_api_documentation/dsl/endpoint.rb +++ b/lib/rspec_api_documentation/dsl/endpoint.rb @@ -43,21 +43,26 @@ def do_request(extra_params = {}) if http_method == :get && !query_string.blank? path_or_query += "?#{query_string}" + elsif [:put, :post, :patch].include?(http_method) && example.metadata[:request_body] + generated_body = generate_request_body(example.metadata[:request_body][:schema]) + params_or_body = if example.metadata[:request_body][:type] == 'application/json' + generated_body.to_json + else + generated_body + end + elsif respond_to?(:raw_post) + params_or_body = raw_post else - if respond_to?(:raw_post) - params_or_body = raw_post + formatter = RspecApiDocumentation.configuration.request_body_formatter + case formatter + when :json + params_or_body = params.empty? ? nil : params.to_json + when :xml + params_or_body = params.to_xml + when Proc + params_or_body = formatter.call(params) else - formatter = RspecApiDocumentation.configuration.request_body_formatter - case formatter - when :json - params_or_body = params.empty? ? nil : params.to_json - when :xml - params_or_body = params.to_xml - when Proc - params_or_body = formatter.call(params) - else - params_or_body = params - end + params_or_body = params end end @@ -68,6 +73,39 @@ def query_string build_nested_query(params || {}) end + def generate_request_body(request_body_schema) + # schema: { + # type: 'object', + # required: ['name'], + # properties: { + # "name": { + # "type": 'string' + # }, + # "description": { + # "type": 'string' + # }, + # 'address':{ + # 'type': 'object', + # 'properties': { + # 'street': 'string' + # } + # } + # } + # } + request_body = {} + request_body_schema[:properties].each_pair do |key, value| + case value[:type] + when 'string', 'number', 'integer', 'boolean', 'array' + request_body[key] = respond_to?(key) ? send(key) : nil + when 'object' + request_body[key] = respond_to?(key) ? send(key) : generate_request_body(value) + else + raise 'unknown type' + end + end + request_body + end + def params Params.new(self, example, extra_params).call end diff --git a/lib/rspec_api_documentation/dsl/resource.rb b/lib/rspec_api_documentation/dsl/resource.rb index f03a610b..024d722c 100644 --- a/lib/rspec_api_documentation/dsl/resource.rb +++ b/lib/rspec_api_documentation/dsl/resource.rb @@ -54,6 +54,15 @@ def route(*args, &block) context(*args, &block) end + def request_body(body = nil) + safe_metadata(:request_body, body) + end + + def response_schema(schema_ref = nil) + # not doing safe_metadata as the schema can be different for errors responses + metadata[:response_schema] = schema_ref + end + def parameter(name, *args) parameters.push(field_specification(name, *args)) end @@ -73,6 +82,7 @@ def header(name, value) def authentication(type, value, opts = {}) name, new_opts = case type + when :bearer then [opts[:name], opts.merge(type: type)] when :basic then ['Authorization', opts.merge(type: type)] when :apiKey then [opts[:name], opts.merge(type: type, in: :header)] else raise 'Not supported type for authentication' diff --git a/lib/rspec_api_documentation/example.rb b/lib/rspec_api_documentation/example.rb index ba8f0ad7..35402fba 100644 --- a/lib/rspec_api_documentation/example.rb +++ b/lib/rspec_api_documentation/example.rb @@ -67,9 +67,13 @@ def filter_headers(requests) end def remap_headers(requests, key, headers_to_include) + # TODO: sometimes the requests is nil for some rspec + return unless requests return requests unless headers_to_include + requests.each.with_index do |request_hash, index| next unless request_hash.key?(key) + headers = request_hash[key] request_hash[key] = headers.select{ |key, _| headers_to_include.map(&:downcase).include?(key.downcase) } requests[index] = request_hash diff --git a/lib/rspec_api_documentation/open_api/components.rb b/lib/rspec_api_documentation/open_api/components.rb new file mode 100644 index 00000000..104fa52f --- /dev/null +++ b/lib/rspec_api_documentation/open_api/components.rb @@ -0,0 +1,13 @@ +module RspecApiDocumentation + module OpenApi + class Components < Node + add_setting :schemas, :schema => { '' => Schema } + add_setting :responses, :schema => { '' => Response } + add_setting :parameters, :schema => { '' => Parameter } + add_setting :headers, :schema => { '' => Header } + add_setting :securitySchemes, :schema => { '' => SecurityScheme } + add_setting :links + add_setting :callbacks + end + end +end diff --git a/lib/rspec_api_documentation/open_api/contact.rb b/lib/rspec_api_documentation/open_api/contact.rb index b6bc3c02..9bd942bb 100644 --- a/lib/rspec_api_documentation/open_api/contact.rb +++ b/lib/rspec_api_documentation/open_api/contact.rb @@ -1,9 +1,9 @@ module RspecApiDocumentation module OpenApi class Contact < Node - add_setting :name, :default => 'API Support' - add_setting :url, :default => 'http://www.open-api.io/support' - add_setting :email, :default => 'support@open-api.io' + add_setting :name + add_setting :url + add_setting :email end end end diff --git a/lib/rspec_api_documentation/open_api/example.rb b/lib/rspec_api_documentation/open_api/example.rb deleted file mode 100644 index c641b191..00000000 --- a/lib/rspec_api_documentation/open_api/example.rb +++ /dev/null @@ -1,7 +0,0 @@ -module RspecApiDocumentation - module OpenApi - class Example < Node - CHILD_CLASS = true - end - end -end diff --git a/lib/rspec_api_documentation/open_api/external_docs.rb b/lib/rspec_api_documentation/open_api/external_docs.rb new file mode 100644 index 00000000..e13a8285 --- /dev/null +++ b/lib/rspec_api_documentation/open_api/external_docs.rb @@ -0,0 +1,8 @@ +module RspecApiDocumentation + module OpenApi + class ExternalDocs < Node + add_setting :description + add_setting :url, :required => true + end + end +end diff --git a/lib/rspec_api_documentation/open_api/flow.rb b/lib/rspec_api_documentation/open_api/flow.rb new file mode 100644 index 00000000..83c2b055 --- /dev/null +++ b/lib/rspec_api_documentation/open_api/flow.rb @@ -0,0 +1,10 @@ +module RspecApiDocumentation + module OpenApi + class Flow < Node + add_setting :authorizationUrl, :required => true + add_setting :tokenUrl, :required => true + add_setting :refreshUrl + add_setting :scopes, :required => true + end + end +end diff --git a/lib/rspec_api_documentation/open_api/header.rb b/lib/rspec_api_documentation/open_api/header.rb index 222e2694..e61cb9c4 100644 --- a/lib/rspec_api_documentation/open_api/header.rb +++ b/lib/rspec_api_documentation/open_api/header.rb @@ -1,12 +1,17 @@ module RspecApiDocumentation module OpenApi class Header < Node - add_setting :description, :default => '' - add_setting :type, :required => true, :default => lambda { |header| - Helper.extract_type(header.public_send('x-example-value')) - } - add_setting :format - add_setting 'x-example-value' + add_setting :description + add_setting :required + add_setting :deprecated + # add_setting :allowEmptyValue + # add_setting :style + # add_setting :explode + # add_setting :allowReserved + add_setting :schema, :schema => Schema + add_setting :example + # add_setting :examples, :schema => { '' => Example } + # add_setting :content, :schema => { '' => Media } end end end diff --git a/lib/rspec_api_documentation/open_api/headers.rb b/lib/rspec_api_documentation/open_api/headers.rb deleted file mode 100644 index 6a073a14..00000000 --- a/lib/rspec_api_documentation/open_api/headers.rb +++ /dev/null @@ -1,7 +0,0 @@ -module RspecApiDocumentation - module OpenApi - class Headers < Node - CHILD_CLASS = Header - end - end -end diff --git a/lib/rspec_api_documentation/open_api/helper.rb b/lib/rspec_api_documentation/open_api/helper.rb index 0e25ad65..83a06a90 100644 --- a/lib/rspec_api_documentation/open_api/helper.rb +++ b/lib/rspec_api_documentation/open_api/helper.rb @@ -16,7 +16,7 @@ def extract_type(value) end def extract_items(value, opts = {}) - result = {type: extract_type(value)} + result = { type: extract_type(value) } if result[:type] == :array result[:items] = extract_items(value[0], opts) else diff --git a/lib/rspec_api_documentation/open_api/info.rb b/lib/rspec_api_documentation/open_api/info.rb index ff4c934a..4c295d65 100644 --- a/lib/rspec_api_documentation/open_api/info.rb +++ b/lib/rspec_api_documentation/open_api/info.rb @@ -2,10 +2,10 @@ module RspecApiDocumentation module OpenApi class Info < Node add_setting :title, :default => 'OpenAPI Specification', :required => true - add_setting :description, :default => 'This is a sample server Petstore server.' - add_setting :termsOfService, :default => 'http://open-api.io/terms/' - add_setting :contact, :default => Contact.new, :schema => Contact - add_setting :license, :default => License.new, :schema => License + add_setting :description + add_setting :termsOfService + add_setting :contact, :schema => Contact + add_setting :license, :schema => License add_setting :version, :default => '1.0.0', :required => true end end diff --git a/lib/rspec_api_documentation/open_api/media.rb b/lib/rspec_api_documentation/open_api/media.rb new file mode 100644 index 00000000..6b7c5feb --- /dev/null +++ b/lib/rspec_api_documentation/open_api/media.rb @@ -0,0 +1,10 @@ +module RspecApiDocumentation + module OpenApi + class Media < Node + add_setting :schema, :schema => Schema + add_setting :example + # add_setting :examples, :schema => { '' => Example } + # add_setting :encoding, :schema => { '' => Encoding } + end + end +end diff --git a/lib/rspec_api_documentation/open_api/node.rb b/lib/rspec_api_documentation/open_api/node.rb index 2f102c88..fa5b2565 100644 --- a/lib/rspec_api_documentation/open_api/node.rb +++ b/lib/rspec_api_documentation/open_api/node.rb @@ -1,12 +1,6 @@ module RspecApiDocumentation module OpenApi class Node - # this is used to define class of incoming option attribute - # If +false+ then do not create new setting - # If +true+ then create new setting with raw passed value - # If RspecApiDocumentation::OpenApi::Node then create new setting and wrap it in this class - CHILD_CLASS = false - # This attribute allow us to hide some of children through configuration file attr_accessor :hide @@ -36,14 +30,18 @@ def initialize(opts = {}) opts.each do |name, value| if name.to_s == 'hide' self.hide = value - elsif self.class::CHILD_CLASS - add_setting name, :value => self.class::CHILD_CLASS === true ? value : self.class::CHILD_CLASS.new(value) elsif setting_exist?(name.to_sym) schema = setting_schema(name) converted = - case - when schema.is_a?(Array) && schema[0] <= Node then value.map { |v| v.is_a?(schema[0]) ? v : schema[0].new(v) } - when schema <= Node then value.is_a?(schema) ? value : schema.new(value) + # Type of converted value is encoded into how the schema is defined for a node + # if node schema is defined as as '' => Schema, it is expected that converted value has to be Hash + # if node schema is defined as as [Schema], it is expected that converted value has to be Array + if schema.is_a?(Hash) && schema.values[0] <= Node + Hash[value.map { |k, v| [k, v.is_a?(schema.values[0]) ? v : map_value_to_schema(v, schema.values[0])] }] + elsif schema.is_a?(Array) && schema[0] <= Node + value.map { |v| v.is_a?(schema[0]) ? v : map_value_to_schema(v, schema[0]) } + elsif schema <= Node + value.is_a?(schema) ? value : map_value_to_schema(value, schema) else value end @@ -54,6 +52,13 @@ def initialize(opts = {}) end end + def map_value_to_schema(value, schema) + return value if value.is_a?(Reference) + return Reference.new(value) if value.respond_to?(:has_key?) && value.has_key?("$ref") + + schema.new(value) + end + def assign_setting(name, value); public_send("#{name}=", value) unless value.nil? end def safe_assign_setting(name, value); assign_setting(name, value) unless settings.has_key?(name) end def setting(name); public_send(name) end @@ -69,7 +74,13 @@ def add_setting(name, opts = {}) settings[name] = opts[:value] if opts[:value] define_singleton_method("#{name}_schema") { opts[:schema] || NilClass } - define_singleton_method("#{name}=") { |value| settings[name] = value } + define_singleton_method("#{name}=") do |value| + if setting[name].is_a?(Hash) && value.is_a?(Hash) + value.each { |k, v| setting[name][k] = setting[name][k] ? setting[name][k].merge(v) : v } + else + settings[name] = value + end + end define_singleton_method("#{name}") do if settings.has_key?(name) settings[name] @@ -94,6 +105,8 @@ def as_json when value.is_a?(Array) && value[0].is_a?(Node) tmp = value.select { |v| !v.hide }.map { |v| v.as_json } hash[name] = tmp unless tmp.empty? + when value.is_a?(Hash) && value.values[0].is_a?(Node) + hash[name] = Hash[value.select { |k, v| !v.hide }.map { |k, v| [k, v.as_json] }] else hash[name] = value end unless value.nil? diff --git a/lib/rspec_api_documentation/open_api/operation.rb b/lib/rspec_api_documentation/open_api/operation.rb index 85db7c1b..b7713e0f 100644 --- a/lib/rspec_api_documentation/open_api/operation.rb +++ b/lib/rspec_api_documentation/open_api/operation.rb @@ -3,16 +3,15 @@ module OpenApi class Operation < Node add_setting :tags, :default => [] add_setting :summary - add_setting :description, :default => '' - add_setting :externalDocs - add_setting :operationId - add_setting :consumes - add_setting :produces + add_setting :description + add_setting :externalDocs, :schema => ExternalDocs + # add_setting :operationId add_setting :parameters, :default => [], :schema => [Parameter] - add_setting :responses, :required => true, :schema => Responses - add_setting :schemes + add_setting :requestBody, :schema => RequestBody + add_setting :responses, :required => true, :schema => { '' => Response } add_setting :deprecated, :default => false add_setting :security + # add_setting :servers, :schema => [Server] end end end diff --git a/lib/rspec_api_documentation/open_api/parameter.rb b/lib/rspec_api_documentation/open_api/parameter.rb index d16c5473..e8c1c370 100644 --- a/lib/rspec_api_documentation/open_api/parameter.rb +++ b/lib/rspec_api_documentation/open_api/parameter.rb @@ -1,33 +1,19 @@ module RspecApiDocumentation module OpenApi class Parameter < Node - # Required to write example values to description of parameter when option `with_example: true` is provided - attr_accessor :value - attr_accessor :with_example - add_setting :name, :required => true add_setting :in, :required => true - add_setting :description, :default => '' - add_setting :required, :default => lambda { |parameter| parameter.in.to_s == 'path' ? true : false } - add_setting :schema - add_setting :type - add_setting :items - add_setting :default - add_setting :minimum - add_setting :maximum - add_setting :enum - - def description_with_example - str = description_without_example.dup || '' - if with_example && value - str << "\n" unless str.empty? - str << "Eg, `#{value}`" - end - str - end - - alias_method :description_without_example, :description - alias_method :description, :description_with_example + add_setting :description + add_setting :required, :default => lambda { |parameter| parameter.in.to_s == 'path' } + add_setting :deprecated + # add_setting :allowEmptyValue + # add_setting :style + # add_setting :explode + # add_setting :allowReserved + add_setting :schema, :schema => Schema + add_setting :example + # add_setting :examples, :schema => { '' => Example } + # add_setting :content, :schema => { '' => Media } end end end diff --git a/lib/rspec_api_documentation/open_api/path.rb b/lib/rspec_api_documentation/open_api/path.rb index 241bba8c..158ec616 100644 --- a/lib/rspec_api_documentation/open_api/path.rb +++ b/lib/rspec_api_documentation/open_api/path.rb @@ -8,6 +8,7 @@ class Path < Node add_setting :options, :schema => Operation add_setting :head, :schema => Operation add_setting :patch, :schema => Operation + add_setting :trace, :schema => Operation end end end diff --git a/lib/rspec_api_documentation/open_api/paths.rb b/lib/rspec_api_documentation/open_api/paths.rb deleted file mode 100644 index b3a9efb1..00000000 --- a/lib/rspec_api_documentation/open_api/paths.rb +++ /dev/null @@ -1,7 +0,0 @@ -module RspecApiDocumentation - module OpenApi - class Paths < Node - CHILD_CLASS = Path - end - end -end diff --git a/lib/rspec_api_documentation/open_api/reference.rb b/lib/rspec_api_documentation/open_api/reference.rb new file mode 100644 index 00000000..bb8ea128 --- /dev/null +++ b/lib/rspec_api_documentation/open_api/reference.rb @@ -0,0 +1,7 @@ +module RspecApiDocumentation + module OpenApi + class Reference < Node + add_setting :$ref, required: true + end + end +end diff --git a/lib/rspec_api_documentation/open_api/request_body.rb b/lib/rspec_api_documentation/open_api/request_body.rb new file mode 100644 index 00000000..00fc63c0 --- /dev/null +++ b/lib/rspec_api_documentation/open_api/request_body.rb @@ -0,0 +1,9 @@ +module RspecApiDocumentation + module OpenApi + class RequestBody < Node + add_setting :description, :default => 'body' + add_setting :content, :required => true, :schema => { '' => Media } + add_setting :required, :default => false + end + end +end diff --git a/lib/rspec_api_documentation/open_api/response.rb b/lib/rspec_api_documentation/open_api/response.rb index 6584db6f..04887a46 100644 --- a/lib/rspec_api_documentation/open_api/response.rb +++ b/lib/rspec_api_documentation/open_api/response.rb @@ -2,9 +2,9 @@ module RspecApiDocumentation module OpenApi class Response < Node add_setting :description, :required => true, :default => 'Successful operation' - add_setting :schema, :schema => Schema - add_setting :headers, :schema => Headers - add_setting :examples, :schema => Example + add_setting :headers, :schema => { '' => Header } + add_setting :content, :schema => { '' => Media } + # add_setting :links end end end diff --git a/lib/rspec_api_documentation/open_api/responses.rb b/lib/rspec_api_documentation/open_api/responses.rb deleted file mode 100644 index 4b8c7025..00000000 --- a/lib/rspec_api_documentation/open_api/responses.rb +++ /dev/null @@ -1,9 +0,0 @@ -module RspecApiDocumentation - module OpenApi - class Responses < Node - CHILD_CLASS = Response - - add_setting :default, :default => lambda { |responses| responses.existing_settings.size > 1 ? nil : Response.new } - end - end -end diff --git a/lib/rspec_api_documentation/open_api/root.rb b/lib/rspec_api_documentation/open_api/root.rb index edaeae96..b02f61e0 100644 --- a/lib/rspec_api_documentation/open_api/root.rb +++ b/lib/rspec_api_documentation/open_api/root.rb @@ -1,21 +1,14 @@ module RspecApiDocumentation module OpenApi class Root < Node - add_setting :swagger, :default => '2.0', :required => true + add_setting :openapi, :default => '3.0.0', :required => true add_setting :info, :default => Info.new, :required => true, :schema => Info - add_setting :host, :default => 'localhost:3000' - add_setting :basePath - add_setting :schemes, :default => %w(http https) - add_setting :consumes, :default => %w(application/json application/xml) - add_setting :produces, :default => %w(application/json application/xml) - add_setting :paths, :default => Paths.new, :required => true, :schema => Paths - add_setting :definitions - add_setting :parameters - add_setting :responses - add_setting :securityDefinitions, :schema => SecurityDefinitions + add_setting :servers, :schema => [Server] + add_setting :paths, :default => { '/' => Path.new }, :required => true, :schema => { '' => Path } + add_setting :components, :schema => Components add_setting :security - add_setting :tags, :default => [], :schema => [Tag] - add_setting :externalDocs + add_setting :tags, :schema => [Tag] + add_setting :externalDocs, :schema => ExternalDocs end end end diff --git a/lib/rspec_api_documentation/open_api/schema.rb b/lib/rspec_api_documentation/open_api/schema.rb index c632c12f..7f3d186c 100644 --- a/lib/rspec_api_documentation/open_api/schema.rb +++ b/lib/rspec_api_documentation/open_api/schema.rb @@ -1,15 +1,36 @@ module RspecApiDocumentation module OpenApi class Schema < Node - add_setting :format add_setting :title - add_setting :description, :default => '' + add_setting :multipleOf + add_setting :maximum + add_setting :exclusiveMaximum + add_setting :minimum + add_setting :exclusiveMinimum + add_setting :maxLength + add_setting :minLength + add_setting :pattern + add_setting :maxItems + add_setting :minItems + add_setting :uniqueItems + add_setting :maxProperties + add_setting :minProperties add_setting :required add_setting :enum add_setting :type + add_setting :allOf, :schema => [Schema] + add_setting :oneOf, :schema => [Schema] + add_setting :anyOf, :schema => [Schema] + add_setting :not, :schema => [Schema] add_setting :items - add_setting :properties + add_setting :properties, :schema => { '' => Schema } + add_setting :description + add_setting :format add_setting :example + add_setting :externalDocs, :schema => ExternalDocs + add_setting :nullable + add_setting :deprecated + add_setting :discriminator end end end diff --git a/lib/rspec_api_documentation/open_api/security_definitions.rb b/lib/rspec_api_documentation/open_api/security_definitions.rb deleted file mode 100644 index e1ddc136..00000000 --- a/lib/rspec_api_documentation/open_api/security_definitions.rb +++ /dev/null @@ -1,7 +0,0 @@ -module RspecApiDocumentation - module OpenApi - class SecurityDefinitions < Node - CHILD_CLASS = SecuritySchema - end - end -end diff --git a/lib/rspec_api_documentation/open_api/security_schema.rb b/lib/rspec_api_documentation/open_api/security_schema.rb index a1ba5f05..25218498 100644 --- a/lib/rspec_api_documentation/open_api/security_schema.rb +++ b/lib/rspec_api_documentation/open_api/security_schema.rb @@ -2,7 +2,7 @@ module RspecApiDocumentation module OpenApi class SecuritySchema < Node add_setting :type, :required => true - add_setting :description, :default => '' + add_setting :description add_setting :name add_setting :in add_setting :flow diff --git a/lib/rspec_api_documentation/open_api/security_scheme.rb b/lib/rspec_api_documentation/open_api/security_scheme.rb new file mode 100644 index 00000000..aae46019 --- /dev/null +++ b/lib/rspec_api_documentation/open_api/security_scheme.rb @@ -0,0 +1,14 @@ +module RspecApiDocumentation + module OpenApi + class SecurityScheme < Node + add_setting :type, :required => true + add_setting :description + add_setting :name + add_setting :in + add_setting :scheme + add_setting :bearerFormat + add_setting :flows, :schema => { '' => Flow } + add_setting :openIdConnectUrl + end + end +end diff --git a/lib/rspec_api_documentation/open_api/server.rb b/lib/rspec_api_documentation/open_api/server.rb new file mode 100644 index 00000000..11ffe8a7 --- /dev/null +++ b/lib/rspec_api_documentation/open_api/server.rb @@ -0,0 +1,9 @@ +module RspecApiDocumentation + module OpenApi + class Server < Node + add_setting :url, :required => true + add_setting :description + add_setting :variables, :schema => { '' => Variable } + end + end +end diff --git a/lib/rspec_api_documentation/open_api/tag.rb b/lib/rspec_api_documentation/open_api/tag.rb index 6c8a82d8..5a1e8422 100644 --- a/lib/rspec_api_documentation/open_api/tag.rb +++ b/lib/rspec_api_documentation/open_api/tag.rb @@ -2,8 +2,8 @@ module RspecApiDocumentation module OpenApi class Tag < Node add_setting :name, :required => true - add_setting :description, :default => '' - add_setting :externalDocs + add_setting :description + # add_setting :externalDocs, :schema => ExternalDocs end end end diff --git a/lib/rspec_api_documentation/open_api/variable.rb b/lib/rspec_api_documentation/open_api/variable.rb new file mode 100644 index 00000000..7f7947bf --- /dev/null +++ b/lib/rspec_api_documentation/open_api/variable.rb @@ -0,0 +1,9 @@ +module RspecApiDocumentation + module OpenApi + class Variable < Node + add_setting :enum + add_setting :default, :required => true + add_setting :description + end + end +end diff --git a/lib/rspec_api_documentation/views/api_blueprint_index.rb b/lib/rspec_api_documentation/views/api_blueprint_index.rb index 3c36082a..ef42c1fa 100644 --- a/lib/rspec_api_documentation/views/api_blueprint_index.rb +++ b/lib/rspec_api_documentation/views/api_blueprint_index.rb @@ -86,6 +86,11 @@ def fields(property_name, examples) property[:properties_description] = nil end + property[:has_default?] = true if property[:default] + property[:has_enum?] = true if property[:enum] + + property[:annotations] = property[:annotation].lines.map(&:chomp) if property[:annotation] + property[:description] = nil if description_blank?(property) property end diff --git a/lib/rspec_api_documentation/writers/append_json_writer.rb b/lib/rspec_api_documentation/writers/append_json_writer.rb index 5eae1f7b..69f943e7 100644 --- a/lib/rspec_api_documentation/writers/append_json_writer.rb +++ b/lib/rspec_api_documentation/writers/append_json_writer.rb @@ -5,7 +5,7 @@ module Writers class AppendJsonWriter < JsonWriter def write index_file = docs_dir.join("index.json") - if File.exists?(index_file) && (output = File.read(index_file)).length >= 2 + if File.exist?(index_file) && (output = File.read(index_file)).length >= 2 existing_index_hash = JSON.parse(output) end File.open(index_file, "w+") do |f| diff --git a/lib/rspec_api_documentation/writers/open_api_writer.rb b/lib/rspec_api_documentation/writers/open_api_writer.rb index ce45080c..e44de3f5 100644 --- a/lib/rspec_api_documentation/writers/open_api_writer.rb +++ b/lib/rspec_api_documentation/writers/open_api_writer.rb @@ -33,9 +33,10 @@ def initialize(index, configuration, init_config) def as_json @specs = OpenApi::Root.new(init_config) + add_components! add_tags! add_paths! - add_security_definitions! + add_security_schemes! specs.as_json end @@ -47,8 +48,12 @@ def examples index.examples.map { |example| OpenApiExample.new(example) } end - def add_security_definitions! - security_definitions = OpenApi::SecurityDefinitions.new + def add_components! + specs.safe_assign_setting(:components, OpenApi::Components.new) + end + + def add_security_schemes! + security_schemes = {} arr = examples.map do |example| example.respond_to?(:authentications) ? example.authentications : nil @@ -56,16 +61,16 @@ def add_security_definitions! arr.each do |securities| securities.each do |security, opts| - schema = OpenApi::SecuritySchema.new( + schema = OpenApi::SecurityScheme.new( name: opts[:name], description: opts[:description], type: opts[:type], in: opts[:in] ) - security_definitions.add_setting security, :value => schema + security_schemes[security.to_s] = schema end end - specs.securityDefinitions = security_definitions unless arr.empty? + specs.components.safe_assign_setting(:securitySchemes, security_schemes) unless arr.empty? end def add_tags! @@ -80,68 +85,97 @@ def add_tags! end def add_paths! - specs.safe_assign_setting(:paths, OpenApi::Paths.new) + specs.safe_assign_setting(:paths, {}) examples.each do |example| - specs.paths.add_setting example.route, :value => OpenApi::Path.new + route = example.route.to_s + specs.paths[route] ||= OpenApi::Path.new - operation = specs.paths.setting(example.route).setting(example.http_method) || OpenApi::Operation.new + operation = specs.paths[route].setting(example.http_method) || OpenApi::Operation.new operation.safe_assign_setting(:tags, [example.resource_name]) - operation.safe_assign_setting(:summary, example.respond_to?(:route_summary) ? example.route_summary : '') - operation.safe_assign_setting(:description, example.respond_to?(:route_description) ? example.route_description : '') - operation.safe_assign_setting(:responses, OpenApi::Responses.new) + operation.safe_assign_setting(:summary, example.route_summary) if example.respond_to?(:route_summary) + operation.safe_assign_setting(:description, example.route_description) if example.respond_to?(:route_description) + operation.safe_assign_setting(:externalDocs, OpenApi::ExternalDocs.new(url: example.external_docs)) if example.respond_to?(:external_docs) + operation.safe_assign_setting(:responses, {}) + operation.safe_assign_setting(:deprecated, example.deprecated) if example.respond_to?(:deprecated) operation.safe_assign_setting(:parameters, extract_parameters(example)) - operation.safe_assign_setting(:consumes, example.requests.map { |request| request[:request_content_type] }.compact.map { |q| q[/[^;]+/] }) - operation.safe_assign_setting(:produces, example.requests.map { |request| request[:response_content_type] }.compact.map { |q| q[/[^;]+/] }) + operation.safe_assign_setting(:requestBody, extract_request_body(example)) operation.safe_assign_setting(:security, example.respond_to?(:authentications) ? example.authentications.map { |(k, _)| {k => []} } : []) process_responses(operation.responses, example) - specs.paths.setting(example.route).assign_setting(example.http_method, operation) + specs.paths[route].assign_setting(example.http_method, operation) end end def process_responses(responses, example) - schema = extract_schema(example.respond_to?(:response_fields) ? example.response_fields : []) + schema = build_schema_for_response(example) example.requests.each do |request| response = OpenApi::Response.new( - description: example.description, - schema: schema + description: example.description ) if request[:response_headers] - response.safe_assign_setting(:headers, OpenApi::Headers.new) + response.safe_assign_setting(:headers, {}) request[:response_headers].each do |header, value| - response.headers.add_setting header, :value => OpenApi::Header.new('x-example-value' => value) + response.headers[header.to_s] = OpenApi::Header.new(schema: OpenApi::Schema.new(type: 'string'), example: value) end end + response_body = JSON.parse(request[:response_body]) rescue nil if /\A(?[^;]+)/ =~ request[:response_content_type] - response.safe_assign_setting(:examples, OpenApi::Example.new) - response_body = JSON.parse(request[:response_body]) rescue nil - response.examples.add_setting response_content_type, :value => response_body + content_type = response_content_type.to_s + else + content_type = 'application/json' + end + + if response_body + response.safe_assign_setting(:content, { + content_type => OpenApi::Media.new(schema: schema || get_schema(response_body), example: response_body) + }) end - responses.add_setting "#{request[:response_status]}", :value => response + + responses[request[:response_status].to_s] = response end end + def build_schema_for_response(example) + return create_schema_ref(example.response_schema) if example.respond_to?(:response_schema) + + extract_schema(example.response_fields) if example.respond_to?(:response_fields) + end + + def create_schema_ref(schema_name) + return unless schema_name + + OpenApi::Reference.new("$ref": "#/components/schemas/#{schema_name}") + end + def extract_schema(fields) - schema = {type: 'object', properties: {}} + return nil if fields.nil? + + schema = { type: 'object', properties: {} } fields.each do |field| current = schema if field[:scope] [*field[:scope]].each do |scope| - current[:properties][scope] ||= {type: 'object', properties: {}} + current[:properties][scope] ||= { type: 'object', properties: {} } current = current[:properties][scope] end end - current[:properties][field[:name]] = {type: field[:type] || OpenApi::Helper.extract_type(field[:value])} + current[:properties][field[:name]] = { type: field[:type] || OpenApi::Helper.extract_type(field[:value]) } current[:properties][field[:name]][:example] = field[:value] if field[:value] && field[:with_example] - current[:properties][field[:name]][:default] = field[:default] if field[:default] - current[:properties][field[:name]][:description] = field[:description] if field[:description] - opts = {enum: field[:enum], minimum: field[:minimum], maximum: field[:maximum]} + opts = { + description: field[:description], + default: field[:default], + enum: field[:enum], + minimum: field[:minimum], + maximum: field[:maximum], + required: field[:required], + nullable: field[:nullable] || field[:value].nil? || nil + } if current[:properties][field[:name]][:type] == :array current[:properties][field[:name]][:items] = field[:items] || OpenApi::Helper.extract_items(field[:value][0], opts) @@ -149,15 +183,57 @@ def extract_schema(fields) opts.each { |k, v| current[:properties][field[:name]][k] = v if v } end - current[:required] ||= [] << field[:name] if field[:required] + if field[:required] + current[:required] ||= [] + current[:required] << field[:name] + end end OpenApi::Schema.new(schema) end + def get_schema(field) + type = OpenApi::Helper.extract_type(field).to_s + case type + when 'object' + OpenApi::Schema.new(type: type, properties: Hash[field.map { |k, v| [k, get_schema(v)] }]) + when 'array' + OpenApi::Schema.new(type: type, items: get_schema(field[0])) + else + OpenApi::Schema.new(type: type, example: field, nullable: field.nil? || nil) + end + end + def extract_parameters(example) - extract_known_parameters(example.extended_parameters.select { |p| !p[:in].nil? }) + - extract_unknown_parameters(example, example.extended_parameters.select { |p| p[:in].nil? }) + known_parameters = extract_known_parameters(example.extended_parameters.reject { |p| p[:in].nil? }) + known_param_names = known_parameters.map { |p| p.name } + unknown_parameters = extract_unknown_parameters(example).reject { |p| known_param_names.include?(p.name) } + known_parameters + unknown_parameters + end + + def extract_request_body(example) + if example.respond_to?(:request_body) + request_body_from_last_request = !example.requests.empty? && example.requests.last[:request_body_hash] + OpenApi::RequestBody.new( + description: example.request_body[:description], + required: example.request_body[:required], + content: { + example.request_body[:type] || 'application/json' => OpenApi::Media.new( + schema: OpenApi::Schema.new(example.request_body[:schema]), + example: example.request_body[:example] || request_body_from_last_request + ) + } + ) + else + body = example.requests.map { |req| JSON.parse(req[:request_body]) rescue nil }.compact.reduce({}, :merge) + return nil if body.empty? + + OpenApi::RequestBody.new( + content: { + 'application/json' => OpenApi::Media.new(schema: get_schema(body), example: body) + } + ) + end end def extract_parameter(opts) @@ -166,52 +242,38 @@ def extract_parameter(opts) in: opts[:in], description: opts[:description], required: opts[:required], - type: opts[:type] || OpenApi::Helper.extract_type(opts[:value]), - value: opts[:value], - with_example: opts[:with_example], - default: opts[:default], - ).tap do |elem| - if elem.type == :array - elem.items = opts[:items] || OpenApi::Helper.extract_items(opts[:value][0], { minimum: opts[:minimum], maximum: opts[:maximum], enum: opts[:enum] }) - else - elem.minimum = opts[:minimum] - elem.maximum = opts[:maximum] - elem.enum = opts[:enum] - end - end + deprecated: opts[:deprecated], + schema: opts[:schema] || get_schema(opts[:value]), + example: opts[:value] + ) end - def extract_unknown_parameters(example, parameters) - if example.http_method == :get - parameters.map { |parameter| extract_parameter(parameter.merge(in: :query)) } - elsif parameters.any? { |parameter| !parameter[:scope].nil? } - [OpenApi::Parameter.new( - name: :body, - in: :body, - description: '', - schema: extract_schema(parameters) - )] - else - parameters.map { |parameter| extract_parameter(parameter.merge(in: :formData)) } + def extract_unknown_parameters(example) + parameters = [] + example.requests.each do |req| + req[:request_query_parameters].each do |name, value| + parameters.push(OpenApi::Parameter.new( + name: name, + in: :query, + schema: get_schema(value), + example: value + )) + end + req[:request_headers].each do |name, value| + parameters.push(OpenApi::Parameter.new( + name: name, + in: :header, + schema: get_schema(value), + example: value + )) + end end + parameters end def extract_known_parameters(parameters) - result = parameters.select { |parameter| %w(query path header formData).include?(parameter[:in].to_s) } - .map { |parameter| extract_parameter(parameter) } - - body = parameters.select { |parameter| %w(body).include?(parameter[:in].to_s) } - - result.unshift( - OpenApi::Parameter.new( - name: :body, - in: :body, - description: '', - schema: extract_schema(body) - ) - ) unless body.empty? - - result + parameters.select { |parameter| %w(query path header cookie).include?(parameter[:in].to_s) } + .map { |parameter| extract_parameter(parameter) } end end diff --git a/lib/rspec_api_documentation/writers/writer.rb b/lib/rspec_api_documentation/writers/writer.rb index d70b1f2a..a00c1715 100644 --- a/lib/rspec_api_documentation/writers/writer.rb +++ b/lib/rspec_api_documentation/writers/writer.rb @@ -14,7 +14,7 @@ def self.write(index, configuration) end def self.clear_docs(docs_dir) - if File.exists?(docs_dir) + if File.exist?(docs_dir) FileUtils.rm_rf(docs_dir, :secure => true) end FileUtils.mkdir_p(docs_dir) diff --git a/rspec_api_documentation.gemspec b/rspec_api_documentation.gemspec index 9590586a..baa01dbf 100644 --- a/rspec_api_documentation.gemspec +++ b/rspec_api_documentation.gemspec @@ -3,7 +3,7 @@ $:.unshift lib unless $:.include?(lib) Gem::Specification.new do |s| s.name = "rspec_api_documentation" - s.version = "6.1.0" + s.version = "7.0.0" s.platform = Gem::Platform::RUBY s.authors = ["Chris Cahoon", "Sam Goldman", "Eric Oestrich"] s.email = ["chris@smartlogicsolutions.com", "sam@smartlogicsolutions.com", "eric@smartlogicsolutions.com"] diff --git a/spec/api_documentation_spec.rb b/spec/api_documentation_spec.rb index 39930cc4..dc295567 100644 --- a/spec/api_documentation_spec.rb +++ b/spec/api_documentation_spec.rb @@ -19,7 +19,7 @@ subject.clear_docs expect(File.directory?(configuration.docs_dir)).to be_truthy - expect(File.exists?(test_file)).to be_falsey + expect(File.exist?(test_file)).to be_falsey end end diff --git a/spec/open_api/contact_spec.rb b/spec/open_api/contact_spec.rb deleted file mode 100644 index 8de23ae0..00000000 --- a/spec/open_api/contact_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'spec_helper' - -describe RspecApiDocumentation::OpenApi::Contact do - let(:node) { RspecApiDocumentation::OpenApi::Contact.new } - subject { node } - - describe "default settings" do - its(:name) { should == 'API Support' } - its(:url) { should == 'http://www.open-api.io/support' } - its(:email) { should == 'support@open-api.io' } - end -end diff --git a/spec/open_api/info_spec.rb b/spec/open_api/info_spec.rb index e54993c2..38360fb2 100644 --- a/spec/open_api/info_spec.rb +++ b/spec/open_api/info_spec.rb @@ -5,14 +5,7 @@ subject { node } describe "default settings" do - class RspecApiDocumentation::OpenApi::Contact; end - class RspecApiDocumentation::OpenApi::License; end - its(:title) { should == 'OpenAPI Specification' } - its(:description) { should == 'This is a sample server Petstore server.' } - its(:termsOfService) { should == 'http://open-api.io/terms/' } - its(:contact) { should be_a(RspecApiDocumentation::OpenApi::Contact) } - its(:license) { should be_a(RspecApiDocumentation::OpenApi::License) } its(:version) { should == '1.0.0' } end end diff --git a/spec/open_api/root_spec.rb b/spec/open_api/root_spec.rb index 52debbbf..1c2c91e0 100644 --- a/spec/open_api/root_spec.rb +++ b/spec/open_api/root_spec.rb @@ -8,7 +8,6 @@ describe "default settings" do class RspecApiDocumentation::OpenApi::Info; end - class RspecApiDocumentation::OpenApi::Paths; end its(:swagger) { should == '2.0' } its(:info) { should be_a(RspecApiDocumentation::OpenApi::Info) } @@ -17,10 +16,10 @@ class RspecApiDocumentation::OpenApi::Paths; end its(:schemes) { should == %w(http https) } its(:consumes) { should == %w(application/json application/xml) } its(:produces) { should == %w(application/json application/xml) } - its(:paths) { should be_a(RspecApiDocumentation::OpenApi::Paths) } its(:definitions) { should be_nil } its(:parameters) { should be_nil } its(:responses) { should be_nil } + its(:paths) { should == {} } its(:securityDefinitions) { should be_nil } its(:security) { should be_nil } its(:tags) { should == [] } diff --git a/spec/writers/html_writer_spec.rb b/spec/writers/html_writer_spec.rb index 72dc5615..76db414e 100644 --- a/spec/writers/html_writer_spec.rb +++ b/spec/writers/html_writer_spec.rb @@ -27,7 +27,7 @@ writer.write index_file = File.join(configuration.docs_dir, "index.html") - expect(File.exists?(index_file)).to be_truthy + expect(File.exist?(index_file)).to be_truthy end end end diff --git a/spec/writers/json_iodocs_writer_spec.rb b/spec/writers/json_iodocs_writer_spec.rb index bfee639c..116ccab1 100644 --- a/spec/writers/json_iodocs_writer_spec.rb +++ b/spec/writers/json_iodocs_writer_spec.rb @@ -25,7 +25,7 @@ it "should write the index" do writer.write index_file = File.join(configuration.docs_dir, "apiconfig.json") - expect(File.exists?(index_file)).to be_truthy + expect(File.exist?(index_file)).to be_truthy end end end diff --git a/spec/writers/json_writer_spec.rb b/spec/writers/json_writer_spec.rb index 9e5e6b8d..973b52b1 100644 --- a/spec/writers/json_writer_spec.rb +++ b/spec/writers/json_writer_spec.rb @@ -24,7 +24,7 @@ it "should write the index" do writer.write index_file = File.join(configuration.docs_dir, "index.json") - expect(File.exists?(index_file)).to be_truthy + expect(File.exist?(index_file)).to be_truthy end end end diff --git a/spec/writers/markdown_writer_spec.rb b/spec/writers/markdown_writer_spec.rb index 799336ee..313a51d9 100644 --- a/spec/writers/markdown_writer_spec.rb +++ b/spec/writers/markdown_writer_spec.rb @@ -27,7 +27,7 @@ writer.write index_file = File.join(configuration.docs_dir, "index.md") - expect(File.exists?(index_file)).to be_truthy + expect(File.exist?(index_file)).to be_truthy end end end diff --git a/spec/writers/slate_writer_spec.rb b/spec/writers/slate_writer_spec.rb index 9c1398da..acd91047 100644 --- a/spec/writers/slate_writer_spec.rb +++ b/spec/writers/slate_writer_spec.rb @@ -27,7 +27,7 @@ writer.write index_file = File.join(configuration.docs_dir, "index.html.md") - expect(File.exists?(index_file)).to be_truthy + expect(File.exist?(index_file)).to be_truthy end end end diff --git a/spec/writers/textile_writer_spec.rb b/spec/writers/textile_writer_spec.rb index 1531f7ad..2e10cb7d 100644 --- a/spec/writers/textile_writer_spec.rb +++ b/spec/writers/textile_writer_spec.rb @@ -27,7 +27,7 @@ writer.write index_file = File.join(configuration.docs_dir, "index.textile") - expect(File.exists?(index_file)).to be_truthy + expect(File.exist?(index_file)).to be_truthy end end end diff --git a/templates/rspec_api_documentation/api_blueprint_index.mustache b/templates/rspec_api_documentation/api_blueprint_index.mustache index 865f24a3..8100200e 100644 --- a/templates/rspec_api_documentation/api_blueprint_index.mustache +++ b/templates/rspec_api_documentation/api_blueprint_index.mustache @@ -1,5 +1,6 @@ FORMAT: 1A # {{ api_name }} +{{ api_explanation }} {{# sections }} # Group {{ resource_name }} @@ -27,6 +28,18 @@ explanation: {{ explanation }} + Parameters {{# parameters }} + {{ name }}{{# example }}: {{ example }}{{/ example }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }}{{# description }} - {{ description }}{{/ description }} + {{# has_default?}} + + Default: `{{default}}` + {{/ has_default?}} + {{# has_enum?}} + + Members + {{# enum}} + + `{{.}}` + {{/ enum}} + {{/ has_enum?}} + {{# annotations }} + {{ . }} + {{/ annotations }} {{/ parameters }} {{/ has_parameters? }} {{# has_attributes? }} @@ -34,6 +47,18 @@ explanation: {{ explanation }} + Attributes (object) {{# attributes }} + {{ name }}{{# example }}: {{ example }}{{/ example }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }}{{# description }} - {{ description }}{{/ description }} + {{# has_default?}} + + Default: `{{default}}` + {{/ has_default?}} + {{# has_enum?}} + + Members + {{# enum}} + + `{{.}}` + {{/ enum}} + {{/ has_enum?}} + {{# annotations }} + {{ . }} + {{/ annotations }} {{/ attributes }} {{/ has_attributes? }} {{# http_methods }}