Skip to content

[WIP] Fix api blueprint format #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Apr 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
95fe538
Fix API blueprint documentation.
FabienChaynes Feb 7, 2018
aee3bc0
Fix API blueprint documentation.
FabienChaynes Feb 8, 2018
6a86bfe
Fix API blueprint documentation.
FabienChaynes Feb 8, 2018
b57259d
Fix API blueprint documentation.
FabienChaynes Feb 8, 2018
96fbd74
Fix API blueprint documentation.
FabienChaynes Feb 8, 2018
6534f28
Add api_explanation to the api-blueprint template
icidasset Feb 14, 2018
4a30554
Add route and method explanations
icidasset Feb 14, 2018
56873e7
Add explanations to template
icidasset Feb 14, 2018
b9d7801
Fix attributes
icidasset Feb 14, 2018
bb5546d
Further improve template
icidasset Feb 14, 2018
d7d85ef
Improve field properties
icidasset Feb 14, 2018
8048cd2
Clean up template
icidasset Feb 14, 2018
4758f6c
Fix specs
icidasset Feb 15, 2018
35c6a8b
Actually use the attributes in the request
icidasset Feb 15, 2018
24ff4f7
Handle multiple examples correctly
icidasset Feb 15, 2018
f221b66
Remove example from parameters and attributes
icidasset Feb 15, 2018
3429130
Add ability to define default values for parameters and attributes
icidasset Feb 15, 2018
4f8c44d
Move method explanation to the right place in the template
icidasset Feb 16, 2018
ec98981
Allow the use of action methods (eg. `get`) outside `route` blocks
icidasset Feb 19, 2018
13ea7de
Allow multiple http methods if they have different action names
icidasset Feb 21, 2018
454d0ab
Move the description of the parameters
icidasset Feb 27, 2018
b2ec981
Add ability to sort the routes alphabetically
icidasset Feb 28, 2018
2f56cb8
Merge pull request #2 from icidasset/fix-api-blueprint-format
Mar 22, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
doc
tmp
.rvmrc
.ruby-version
Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ GEM
multipart-post (>= 1.2, < 3)
ffi (1.9.10)
gherkin (3.2.0)
hashdiff (0.2.3)
hashdiff (0.3.7)
httpclient (2.7.1)
i18n (0.7.0)
inch (0.7.0)
Expand Down Expand Up @@ -129,7 +129,7 @@ GEM
tins (1.8.2)
tzinfo (1.2.2)
thread_safe (~> 0.1)
webmock (1.22.6)
webmock (3.3.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff
Expand All @@ -154,7 +154,7 @@ DEPENDENCIES
rspec_api_documentation!
sinatra (~> 1.4, >= 1.4.4)
thin (~> 1.6, >= 1.6.3)
webmock (~> 1.7)
webmock (~> 3.3)

BUNDLED WITH
1.16.1
3 changes: 3 additions & 0 deletions lib/rspec_api_documentation/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ def self.add_setting(name, opts = {})
add_setting :response_headers_to_include, :default => nil
add_setting :html_embedded_css_file, :default => nil

# sorting
add_setting :sort_routes, :default => false

# renamed to request_body_formatter. here for backwards compatibility
add_setting :post_body_formatter, :default => nil

Expand Down
16 changes: 12 additions & 4 deletions lib/rspec_api_documentation/dsl/endpoint/params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ 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
Expand Down
20 changes: 18 additions & 2 deletions lib/rspec_api_documentation/dsl/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,25 @@ def self.define_action(method)
define_method method do |*args, &block|
options = args.extract_options!
options[:method] = method

if metadata[:route_uri]
options[:route] = metadata[:route_uri]
options[:action_name] = args.first

else
options[:route] = args.first
options[:route_uri] = args[0].gsub(/\{.*\}/, "")
options[:route_optionals] = (optionals = args[0].match(/(\{.*\})/) and optionals[-1])
options[:route_name] = options[:route_name] || options[:route]
options[:action_name] = options[:action_name] || method.to_s.upcase

end

options[:api_doc_dsl] = :endpoint

args.push(options)
args[0] = "#{method.to_s.upcase} #{args[0]}"

context(*args, &block)
end
end
Expand All @@ -30,7 +40,7 @@ def self.define_action(method)

def callback(*args, &block)
begin
require 'webmock'
require 'webmock/rspec'
rescue LoadError
raise "Callbacks require webmock to be installed"
end
Expand Down Expand Up @@ -71,7 +81,13 @@ def header(name, value)
end

def explanation(text)
safe_metadata(:resource_explanation, text)
if metadata[:method].present?
safe_metadata(:method_explanation, text)
elsif metadata[:route_uri].present?
safe_metadata(:route_explanation, text)
else
safe_metadata(:resource_explanation, text)
end
end

private
Expand Down
18 changes: 15 additions & 3 deletions lib/rspec_api_documentation/views/api_blueprint_example.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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])
Expand Down Expand Up @@ -78,6 +78,18 @@ def body_to_json(http_call, message_direction)
body
end

# `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

# JSON requests should use UTF-8 by default according to
# http://www.ietf.org/rfc/rfc4627.txt, so we will remove `charset=utf-8`
# when we find it to remove noise.
Expand Down
42 changes: 30 additions & 12 deletions lib/rspec_api_documentation/views/api_blueprint_index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,33 @@ def sections
attrs = fields(:attributes, examples)
params = fields(:parameters, examples)

methods = examples.group_by(&:http_method).map do |http_method, examples|
{
http_method: http_method,
description: examples.first.respond_to?(:action_name) && examples.first.action_name,
examples: examples
}
end
methods = examples
.group_by { |e| "#{e.http_method} - #{e.action_name}" }
.map do |group, examples|
first_example = examples.first

{
http_method: first_example.try(:http_method),
description: first_example.try(:action_name),
explanation: first_example.try(:[], :metadata).try(:[], :method_explanation),
examples: examples
}
end

{
"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],
explanation: examples[0][:route_explanation],
attributes: attrs,
parameters: params,
http_methods: methods
}
end

section.merge({
routes: routes
routes: @configuration.sort_routes ? routes.sort_by { |r| r[:route_name] } : routes
})
end
end
Expand All @@ -45,15 +51,25 @@ 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.2
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.
# {
# required: true,
# example: "1",
# type: "string",
# name: "id",
# description: "The id",
# properties_description: "required, string"
# properties_description: "string, required"
# }
def fields(property_name, examples)
examples
Expand All @@ -63,8 +79,10 @@ def fields(property_name, examples)
.uniq { |property| property[:name] }
.map do |property|
properties = []
properties << "required" if property[:required]
properties << property[:type] if property[:type]
properties << "required" if property[:required] == true
properties << "optional" if property[:required].blank?

if properties.count > 0
property[:properties_description] = properties.join(", ")
else
Expand Down
2 changes: 1 addition & 1 deletion rspec_api_documentation.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Gem::Specification.new do |s|
s.add_development_dependency "rake", "~> 10.1"
s.add_development_dependency "rack-test", "~> 0.6.2"
s.add_development_dependency "rack-oauth2", "~> 1.2.2", ">= 1.0.7"
s.add_development_dependency "webmock", "~> 1.7"
s.add_development_dependency "webmock", "~> 3.3"
s.add_development_dependency "rspec-its", "~> 1.0"
s.add_development_dependency "faraday", "~> 0.9", ">= 0.9.0"
s.add_development_dependency "thin", "~> 1.6", ">= 1.6.3"
Expand Down
7 changes: 7 additions & 0 deletions spec/dsl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,12 @@
parameter :id, 'The ID of the resource.', :required => true, scope: :data
parameter :note, "Any additional notes about your order."

attribute :tip, "The amount you want to tip", :required => true

let(:type) { "coffee" }
let(:size) { "medium" }
let(:data_id) { 2 }
let(:tip) { 20 }

let(:id) { 1 }

Expand All @@ -157,6 +160,10 @@
expect(params['data']).to eq({'id' => 2})
end

it "should set attributes as well" do
expect(params["tip"]).to eq(tip())
end

it "should allow extra parameters to be passed in" do
expect(client).to receive(method).with(path, params.merge("extra" => true), nil)
do_request(:extra => true)
Expand Down
6 changes: 3 additions & 3 deletions spec/http_test_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'capybara'
require 'capybara/server'
require 'sinatra/base'
require 'webmock'
require 'webmock/rspec'
require 'support/stub_app'

describe RspecApiDocumentation::HttpTestClient do
Expand All @@ -16,7 +16,7 @@
Rack::Handler::Thin.run(app, :Port => port)
end

server = Capybara::Server.new(StubApp.new, 8888)
server = Capybara::Server.new(StubApp.new, 29876)
server.boot
end

Expand All @@ -25,7 +25,7 @@
end

let(:client_context) { |example| double(example: example, app_root: 'nowhere') }
let(:target_host) { 'http://localhost:8888' }
let(:target_host) { 'http://localhost:29876' }
let(:test_client) { RspecApiDocumentation::HttpTestClient.new(client_context, {host: target_host}) }

subject { test_client }
Expand Down
28 changes: 6 additions & 22 deletions spec/views/api_blueprint_example_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading