diff --git a/features/api_blueprint_documentation.feature b/features/api_blueprint_documentation.feature index 2ac78462..00867429 100644 --- a/features/api_blueprint_documentation.feature +++ b/features/api_blueprint_documentation.feature @@ -248,7 +248,8 @@ Feature: Generate API Blueprint documentation from test examples Scenario: Index file should look like we expect Then the file "doc/api/index.apib" should contain exactly: """ - FORMAT: A1 + FORMAT: 1A + # Example API # Group Instructions @@ -262,18 +263,17 @@ Feature: Generate API Blueprint documentation from test examples + Headers - Host: example.org + Host: example.org + Response 200 (text/html;charset=utf-8) + Headers - Content-Type: text/html;charset=utf-8 - Content-Length: 57 + Content-Length: 57 + Body - {"data":{"id":"1","type":"instructions","attributes":{}}} + {"data":{"id":"1","type":"instructions","attributes":{}}} # Group Orders @@ -287,38 +287,36 @@ Feature: Generate API Blueprint documentation from test examples + Headers - Content-Type: application/json - Host: example.org + Host: example.org + Body - { - "data": { - "type": "order", - "attributes": { - "name": "Order 1", - "amount": 100.0, - "description": "A description" + { + "data": { + "type": "order", + "attributes": { + "name": "Order 1", + "amount": 100.0, + "description": "A description" + } + } } - } - } + Response 201 (application/json) + Headers - Content-Type: application/json - Content-Length: 73 + Content-Length: 73 + Body - { - "order": { - "name": "Order 1", - "amount": 100.0, - "description": "A great order" - } - } + { + "order": { + "name": "Order 1", + "amount": 100.0, + "description": "A great order" + } + } ### Return all orders [GET] @@ -326,34 +324,33 @@ Feature: Generate API Blueprint documentation from test examples + Headers - Host: example.org + Host: example.org + Response 200 (application/vnd.api+json) + Headers - Content-Type: application/vnd.api+json - Content-Length: 137 + Content-Length: 137 + Body - { - "page": 1, - "orders": [ - { - "name": "Order 1", - "amount": 9.99, - "description": null - }, { - "name": "Order 2", - "amount": 100.0, - "description": "A great order" + "page": 1, + "orders": [ + { + "name": "Order 1", + "amount": 9.99, + "description": null + }, + { + "name": "Order 2", + "amount": 100.0, + "description": "A great order" + } + ] } - ] - } - ## Single Order [/orders/:id{?optional=:optional}] + ## Single Order [/orders/{id}{?optional=:optional}] + Parameters + id: 1 (required, string) - Order id @@ -370,15 +367,13 @@ Feature: Generate API Blueprint documentation from test examples + Headers - Host: example.org - Content-Type: application/x-www-form-urlencoded + Host: example.org + Response 200 (text/html;charset=utf-8) + Headers - Content-Type: text/html;charset=utf-8 - Content-Length: 0 + Content-Length: 0 ### Returns a single order [GET] @@ -386,24 +381,23 @@ Feature: Generate API Blueprint documentation from test examples + Headers - Host: example.org + Host: example.org + Response 200 (application/json) + Headers - Content-Type: application/json - Content-Length: 73 + Content-Length: 73 + Body - { - "order": { - "name": "Order 1", - "amount": 100.0, - "description": "A great order" - } - } + { + "order": { + "name": "Order 1", + "amount": 100.0, + "description": "A great order" + } + } ### Updates a single order [PUT] @@ -411,55 +405,51 @@ Feature: Generate API Blueprint documentation from test examples + Headers - Content-Type: application/json; charset=utf-16 - Host: example.org + Host: example.org + Response 400 (application/json) + Headers - Content-Type: application/json - Content-Length: 0 + Content-Length: 0 + Request Update an order (application/json; charset=utf-16) + Headers - Content-Type: application/json; charset=utf-16 - Host: example.org + Host: example.org + Body - { - "data": { - "id": "1", - "type": "order", - "attributes": { - "name": "Order 1" + { + "data": { + "id": "1", + "type": "order", + "attributes": { + "name": "Order 1" + } + } } - } - } + Response 200 (application/json) + Headers - Content-Type: application/json - Content-Length: 111 + Content-Length: 111 + Body - { - "data": { - "id": "1", - "type": "order", - "attributes": { - "name": "Order 1", - "amount": 100.0, - "description": "A description" + { + "data": { + "id": "1", + "type": "order", + "attributes": { + "name": "Order 1", + "amount": 100.0, + "description": "A description" + } + } } - } - } """ Scenario: Example 'Deleting an order' file should not be created diff --git a/lib/rspec_api_documentation/dsl/endpoint/params.rb b/lib/rspec_api_documentation/dsl/endpoint/params.rb index 037788b8..d5d003d2 100644 --- a/lib/rspec_api_documentation/dsl/endpoint/params.rb +++ b/lib/rspec_api_documentation/dsl/endpoint/params.rb @@ -13,11 +13,14 @@ def initialize(example_group, example, extra_params) end def call - parameters = example.metadata.fetch(:parameters, {}).inject({}) do |hash, param| + set_param = -> hash, param { SetParam.new(self, hash, param).call - end - parameters.deep_merge!(extra_params) - parameters + } + + example.metadata.fetch(:parameters, {}).inject({}, &set_param) + .deep_merge( + example.metadata.fetch(:attributes, {}).inject({}, &set_param) + ).deep_merge(extra_params) end private diff --git a/lib/rspec_api_documentation/views/api_blueprint_example.rb b/lib/rspec_api_documentation/views/api_blueprint_example.rb index 45f815a9..35e8c810 100644 --- a/lib/rspec_api_documentation/views/api_blueprint_example.rb +++ b/lib/rspec_api_documentation/views/api_blueprint_example.rb @@ -1,7 +1,7 @@ module RspecApiDocumentation module Views class ApiBlueprintExample < MarkupExample - TOTAL_SPACES_INDENTATION = 8.freeze + TOTAL_SPACES_INDENTATION = 12.freeze def initialize(example, configuration) super @@ -20,14 +20,14 @@ def parameters def requests super.map do |request| - request[:request_headers_text] = remove_utf8_for_json(request[:request_headers_text]) + request[:request_headers_text] = remove_utf8_for_json(remove_content_type(request[:request_headers_text])) request[:request_headers_text] = indent(request[:request_headers_text]) request[:request_content_type] = content_type(request[:request_headers]) request[:request_content_type] = remove_utf8_for_json(request[:request_content_type]) request[:request_body] = body_to_json(request, :request) request[:request_body] = indent(request[:request_body]) - request[:response_headers_text] = remove_utf8_for_json(request[:response_headers_text]) + request[:response_headers_text] = remove_utf8_for_json(remove_content_type(request[:response_headers_text])) request[:response_headers_text] = indent(request[:response_headers_text]) request[:response_content_type] = content_type(request[:response_headers]) request[:response_content_type] = remove_utf8_for_json(request[:response_content_type]) @@ -46,6 +46,18 @@ def extension private + # `Content-Type` header is removed because the information would be duplicated + # since it's already present in `request[:request_content_type]`. + def remove_content_type(headers) + return unless headers + headers + .split("\n") + .reject { |header| + header.start_with?('Content-Type:') + } + .join("\n") + end + def has_request?(metadata) metadata.any? do |key, value| [:request_body, :request_headers, :request_content_type].include?(key) && value diff --git a/lib/rspec_api_documentation/views/api_blueprint_index.rb b/lib/rspec_api_documentation/views/api_blueprint_index.rb index aa7a4d50..ca6a05d9 100644 --- a/lib/rspec_api_documentation/views/api_blueprint_index.rb +++ b/lib/rspec_api_documentation/views/api_blueprint_index.rb @@ -23,7 +23,7 @@ def sections { "has_attributes?".to_sym => attrs.size > 0, "has_parameters?".to_sym => params.size > 0, - route: route, + route: format_route(examples[0]), route_name: examples[0][:route_name], attributes: attrs, parameters: params, @@ -45,6 +45,17 @@ def examples private + # APIB follows the RFC 6570 to format URI templates. + # According to it, simple string expansion (used to perform variable + # expansion) should be represented by `{var}` and not by `/:var` + # For example `/posts/:id` should become `/posts/{id}` + # cf. https://github.com/apiaryio/api-blueprint/blob/format-1A/API%20Blueprint%20Specification.md#431-resource-section + # cf. https://tools.ietf.org/html/rfc6570#section-3.2.6 + def format_route(example) + route_uri = example[:route_uri].gsub(/:(.*?)([.\/?{]|$)/, '{\1}\2') + "#{route_uri}#{example[:route_optionals]}" + end + # APIB has both `parameters` and `attributes`. This generates a hash # with all of its properties, like name, description, required. # { diff --git a/spec/views/api_blueprint_example_spec.rb b/spec/views/api_blueprint_example_spec.rb index 427ef7d0..fb70a5da 100644 --- a/spec/views/api_blueprint_example_spec.rb +++ b/spec/views/api_blueprint_example_spec.rb @@ -57,17 +57,9 @@ describe 'request_headers_text' do subject { view.requests[0][:request_headers_text] } - context 'when charset=utf-8 is present' do - it "just strips that because it's the default for json" do - expect(subject).to eq "Content-Type: application/json\n Another: header; charset=utf-8" - end - end - - context 'when charset=utf-16 is present' do - let(:content_type) { "application/json; charset=utf-16" } - - it "keeps that because it's NOT the default for json" do - expect(subject).to eq "Content-Type: application/json; charset=utf-16\n Another: header; charset=utf-8" + context 'when Content-Type is present' do + it "removes it" do + expect(subject).to eq "Another: header; charset=utf-8" end end end @@ -93,17 +85,9 @@ describe 'response_headers_text' do subject { view.requests[0][:response_headers_text] } - context 'when charset=utf-8 is present' do - it "just strips that because it's the default for json" do - expect(subject).to eq "Content-Type: application/json\n Another: header; charset=utf-8" - end - end - - context 'when charset=utf-16 is present' do - let(:content_type) { "application/json; charset=utf-16" } - - it "keeps that because it's NOT the default for json" do - expect(subject).to eq "Content-Type: application/json; charset=utf-16\n Another: header; charset=utf-8" + context 'when Content-Type is present' do + it "removes it" do + expect(subject).to eq "Another: header; charset=utf-8" end end end diff --git a/spec/views/api_blueprint_index_spec.rb b/spec/views/api_blueprint_index_spec.rb index 92b4e21c..862016c2 100644 --- a/spec/views/api_blueprint_index_spec.rb +++ b/spec/views/api_blueprint_index_spec.rb @@ -109,7 +109,7 @@ post_examples = post_route[:http_methods].map { |http_method| http_method[:examples] }.flatten expect(post_examples.size).to eq 2 - expect(post_route[:route]).to eq "/posts/:id" + expect(post_route[:route]).to eq "/posts/{id}" expect(post_route[:route_name]).to eq "Single Post" expect(post_route[:has_parameters?]).to eq true expect(post_route[:parameters]).to eq [{ @@ -130,7 +130,7 @@ post_w_optionals_examples = post_route_with_optionals[:http_methods].map { |http_method| http_method[:examples] }.flatten expect(post_w_optionals_examples.size).to eq 1 - expect(post_route_with_optionals[:route]).to eq "/posts/:id{?option=:option}" + expect(post_route_with_optionals[:route]).to eq "/posts/{id}{?option=:option}" expect(post_route_with_optionals[:route_name]).to eq "Single Post" expect(post_route_with_optionals[:has_parameters?]).to eq true expect(post_route_with_optionals[:parameters]).to eq [{ diff --git a/templates/rspec_api_documentation/api_blueprint_index.mustache b/templates/rspec_api_documentation/api_blueprint_index.mustache index 2955a22d..865f24a3 100644 --- a/templates/rspec_api_documentation/api_blueprint_index.mustache +++ b/templates/rspec_api_documentation/api_blueprint_index.mustache @@ -1,4 +1,5 @@ -FORMAT: A1 +FORMAT: 1A +# {{ api_name }} {{# sections }} # Group {{ resource_name }} @@ -48,13 +49,13 @@ explanation: {{ explanation }} + Headers - {{{ request_headers_text }}} + {{{ request_headers_text }}} {{/ request_headers_text }} {{# request_body }} + Body - {{{ request_body }}} + {{{ request_body }}} {{/ request_body }} {{# has_response? }} @@ -64,13 +65,13 @@ explanation: {{ explanation }} + Headers - {{{ response_headers_text }}} + {{{ response_headers_text }}} {{/ response_headers_text }} {{# response_body }} + Body - {{{ response_body }}} + {{{ response_body }}} {{/ response_body }} {{/ requests }} {{/ examples }}