Skip to content

Commit 74e731b

Browse files
author
yld
authored
Merge pull request #1 from FinalCAD/fix-api-blueprint-format
[WIP] Fix api blueprint format
2 parents d7a6961 + 2f56cb8 commit 74e731b

13 files changed

+152
-71
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
doc
12
tmp
23
.rvmrc
34
.ruby-version

Gemfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ GEM
5757
multipart-post (>= 1.2, < 3)
5858
ffi (1.9.10)
5959
gherkin (3.2.0)
60-
hashdiff (0.2.3)
60+
hashdiff (0.3.7)
6161
httpclient (2.7.1)
6262
i18n (0.7.0)
6363
inch (0.7.0)
@@ -129,7 +129,7 @@ GEM
129129
tins (1.8.2)
130130
tzinfo (1.2.2)
131131
thread_safe (~> 0.1)
132-
webmock (1.22.6)
132+
webmock (3.3.0)
133133
addressable (>= 2.3.6)
134134
crack (>= 0.3.2)
135135
hashdiff
@@ -154,7 +154,7 @@ DEPENDENCIES
154154
rspec_api_documentation!
155155
sinatra (~> 1.4, >= 1.4.4)
156156
thin (~> 1.6, >= 1.6.3)
157-
webmock (~> 1.7)
157+
webmock (~> 3.3)
158158

159159
BUNDLED WITH
160160
1.16.1

lib/rspec_api_documentation/configuration.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ def self.add_setting(name, opts = {})
8181
add_setting :response_headers_to_include, :default => nil
8282
add_setting :html_embedded_css_file, :default => nil
8383

84+
# sorting
85+
add_setting :sort_routes, :default => false
86+
8487
# renamed to request_body_formatter. here for backwards compatibility
8588
add_setting :post_body_formatter, :default => nil
8689

lib/rspec_api_documentation/dsl/endpoint/params.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,19 @@ def initialize(example_group, example, extra_params)
1313
end
1414

1515
def call
16-
parameters = example.metadata.fetch(:parameters, {}).inject({}) do |hash, param|
16+
set_param = -> hash, param {
1717
SetParam.new(self, hash, param).call
18-
end
19-
parameters.deep_merge!(extra_params)
20-
parameters
18+
}
19+
20+
example.metadata
21+
.fetch(:parameters, {})
22+
.inject({}, &set_param)
23+
.deep_merge(
24+
example.metadata
25+
.fetch(:attributes, {})
26+
.inject({}, &set_param)
27+
)
28+
.deep_merge(extra_params)
2129
end
2230

2331
private

lib/rspec_api_documentation/dsl/resource.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,25 @@ def self.define_action(method)
88
define_method method do |*args, &block|
99
options = args.extract_options!
1010
options[:method] = method
11+
1112
if metadata[:route_uri]
1213
options[:route] = metadata[:route_uri]
1314
options[:action_name] = args.first
15+
1416
else
1517
options[:route] = args.first
18+
options[:route_uri] = args[0].gsub(/\{.*\}/, "")
19+
options[:route_optionals] = (optionals = args[0].match(/(\{.*\})/) and optionals[-1])
20+
options[:route_name] = options[:route_name] || options[:route]
21+
options[:action_name] = options[:action_name] || method.to_s.upcase
22+
1623
end
24+
1725
options[:api_doc_dsl] = :endpoint
26+
1827
args.push(options)
1928
args[0] = "#{method.to_s.upcase} #{args[0]}"
29+
2030
context(*args, &block)
2131
end
2232
end
@@ -30,7 +40,7 @@ def self.define_action(method)
3040

3141
def callback(*args, &block)
3242
begin
33-
require 'webmock'
43+
require 'webmock/rspec'
3444
rescue LoadError
3545
raise "Callbacks require webmock to be installed"
3646
end
@@ -71,7 +81,13 @@ def header(name, value)
7181
end
7282

7383
def explanation(text)
74-
safe_metadata(:resource_explanation, text)
84+
if metadata[:method].present?
85+
safe_metadata(:method_explanation, text)
86+
elsif metadata[:route_uri].present?
87+
safe_metadata(:route_explanation, text)
88+
else
89+
safe_metadata(:resource_explanation, text)
90+
end
7591
end
7692

7793
private

lib/rspec_api_documentation/views/api_blueprint_example.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module RspecApiDocumentation
22
module Views
33
class ApiBlueprintExample < MarkupExample
4-
TOTAL_SPACES_INDENTATION = 8.freeze
4+
TOTAL_SPACES_INDENTATION = 12.freeze
55

66
def initialize(example, configuration)
77
super
@@ -20,14 +20,14 @@ def parameters
2020

2121
def requests
2222
super.map do |request|
23-
request[:request_headers_text] = remove_utf8_for_json(request[:request_headers_text])
23+
request[:request_headers_text] = remove_utf8_for_json(remove_content_type(request[:request_headers_text]))
2424
request[:request_headers_text] = indent(request[:request_headers_text])
2525
request[:request_content_type] = content_type(request[:request_headers])
2626
request[:request_content_type] = remove_utf8_for_json(request[:request_content_type])
2727
request[:request_body] = body_to_json(request, :request)
2828
request[:request_body] = indent(request[:request_body])
2929

30-
request[:response_headers_text] = remove_utf8_for_json(request[:response_headers_text])
30+
request[:response_headers_text] = remove_utf8_for_json(remove_content_type(request[:response_headers_text]))
3131
request[:response_headers_text] = indent(request[:response_headers_text])
3232
request[:response_content_type] = content_type(request[:response_headers])
3333
request[:response_content_type] = remove_utf8_for_json(request[:response_content_type])
@@ -78,6 +78,18 @@ def body_to_json(http_call, message_direction)
7878
body
7979
end
8080

81+
# `Content-Type` header is removed because the information would be duplicated
82+
# since it's already present in `request[:request_content_type]`.
83+
def remove_content_type(headers)
84+
return unless headers
85+
headers
86+
.split("\n")
87+
.reject { |header|
88+
header.start_with?('Content-Type:')
89+
}
90+
.join("\n")
91+
end
92+
8193
# JSON requests should use UTF-8 by default according to
8294
# http://www.ietf.org/rfc/rfc4627.txt, so we will remove `charset=utf-8`
8395
# when we find it to remove noise.

lib/rspec_api_documentation/views/api_blueprint_index.rb

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,33 @@ def sections
1212
attrs = fields(:attributes, examples)
1313
params = fields(:parameters, examples)
1414

15-
methods = examples.group_by(&:http_method).map do |http_method, examples|
16-
{
17-
http_method: http_method,
18-
description: examples.first.respond_to?(:action_name) && examples.first.action_name,
19-
examples: examples
20-
}
21-
end
15+
methods = examples
16+
.group_by { |e| "#{e.http_method} - #{e.action_name}" }
17+
.map do |group, examples|
18+
first_example = examples.first
19+
20+
{
21+
http_method: first_example.try(:http_method),
22+
description: first_example.try(:action_name),
23+
explanation: first_example.try(:[], :metadata).try(:[], :method_explanation),
24+
examples: examples
25+
}
26+
end
2227

2328
{
2429
"has_attributes?".to_sym => attrs.size > 0,
2530
"has_parameters?".to_sym => params.size > 0,
26-
route: route,
31+
route: format_route(examples[0]),
2732
route_name: examples[0][:route_name],
33+
explanation: examples[0][:route_explanation],
2834
attributes: attrs,
2935
parameters: params,
3036
http_methods: methods
3137
}
3238
end
3339

3440
section.merge({
35-
routes: routes
41+
routes: @configuration.sort_routes ? routes.sort_by { |r| r[:route_name] } : routes
3642
})
3743
end
3844
end
@@ -45,15 +51,25 @@ def examples
4551

4652
private
4753

54+
# APIB follows the RFC 6570 to format URI templates.
55+
# According to it, simple string expansion (used to perform variable
56+
# expansion) should be represented by `{var}` and not by `:var`
57+
# For example `/posts/:id` should become `/posts/{id}`
58+
# cf. https://github.com/apiaryio/api-blueprint/blob/format-1A/API%20Blueprint%20Specification.md#431-resource-section
59+
# cf. https://tools.ietf.org/html/rfc6570#section-3.2.2
60+
def format_route(example)
61+
route_uri = example[:route_uri].gsub(/:(.*?)([.\/?{]|$)/, '{\1}\2')
62+
"#{route_uri}#{example[:route_optionals]}"
63+
end
64+
4865
# APIB has both `parameters` and `attributes`. This generates a hash
4966
# with all of its properties, like name, description, required.
5067
# {
5168
# required: true,
52-
# example: "1",
5369
# type: "string",
5470
# name: "id",
5571
# description: "The id",
56-
# properties_description: "required, string"
72+
# properties_description: "string, required"
5773
# }
5874
def fields(property_name, examples)
5975
examples
@@ -63,8 +79,10 @@ def fields(property_name, examples)
6379
.uniq { |property| property[:name] }
6480
.map do |property|
6581
properties = []
66-
properties << "required" if property[:required]
6782
properties << property[:type] if property[:type]
83+
properties << "required" if property[:required] == true
84+
properties << "optional" if property[:required].blank?
85+
6886
if properties.count > 0
6987
property[:properties_description] = properties.join(", ")
7088
else

rspec_api_documentation.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
2626
s.add_development_dependency "rake", "~> 10.1"
2727
s.add_development_dependency "rack-test", "~> 0.6.2"
2828
s.add_development_dependency "rack-oauth2", "~> 1.2.2", ">= 1.0.7"
29-
s.add_development_dependency "webmock", "~> 1.7"
29+
s.add_development_dependency "webmock", "~> 3.3"
3030
s.add_development_dependency "rspec-its", "~> 1.0"
3131
s.add_development_dependency "faraday", "~> 0.9", ">= 0.9.0"
3232
s.add_development_dependency "thin", "~> 1.6", ">= 1.6.3"

spec/dsl_spec.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,12 @@
130130
parameter :id, 'The ID of the resource.', :required => true, scope: :data
131131
parameter :note, "Any additional notes about your order."
132132

133+
attribute :tip, "The amount you want to tip", :required => true
134+
133135
let(:type) { "coffee" }
134136
let(:size) { "medium" }
135137
let(:data_id) { 2 }
138+
let(:tip) { 20 }
136139

137140
let(:id) { 1 }
138141

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

163+
it "should set attributes as well" do
164+
expect(params["tip"]).to eq(tip())
165+
end
166+
160167
it "should allow extra parameters to be passed in" do
161168
expect(client).to receive(method).with(path, params.merge("extra" => true), nil)
162169
do_request(:extra => true)

spec/http_test_client_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require 'capybara'
44
require 'capybara/server'
55
require 'sinatra/base'
6-
require 'webmock'
6+
require 'webmock/rspec'
77
require 'support/stub_app'
88

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

19-
server = Capybara::Server.new(StubApp.new, 8888)
19+
server = Capybara::Server.new(StubApp.new, 29876)
2020
server.boot
2121
end
2222

@@ -25,7 +25,7 @@
2525
end
2626

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

3131
subject { test_client }

spec/views/api_blueprint_example_spec.rb

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,9 @@
5757
describe 'request_headers_text' do
5858
subject { view.requests[0][:request_headers_text] }
5959

60-
context 'when charset=utf-8 is present' do
61-
it "just strips that because it's the default for json" do
62-
expect(subject).to eq "Content-Type: application/json\n Another: header; charset=utf-8"
63-
end
64-
end
65-
66-
context 'when charset=utf-16 is present' do
67-
let(:content_type) { "application/json; charset=utf-16" }
68-
69-
it "keeps that because it's NOT the default for json" do
70-
expect(subject).to eq "Content-Type: application/json; charset=utf-16\n Another: header; charset=utf-8"
60+
context 'when Content-Type is present' do
61+
it "removes it" do
62+
expect(subject).to eq "Another: header; charset=utf-8"
7163
end
7264
end
7365
end
@@ -93,17 +85,9 @@
9385
describe 'response_headers_text' do
9486
subject { view.requests[0][:response_headers_text] }
9587

96-
context 'when charset=utf-8 is present' do
97-
it "just strips that because it's the default for json" do
98-
expect(subject).to eq "Content-Type: application/json\n Another: header; charset=utf-8"
99-
end
100-
end
101-
102-
context 'when charset=utf-16 is present' do
103-
let(:content_type) { "application/json; charset=utf-16" }
104-
105-
it "keeps that because it's NOT the default for json" do
106-
expect(subject).to eq "Content-Type: application/json; charset=utf-16\n Another: header; charset=utf-8"
88+
context 'when Content-Type is present' do
89+
it "removes it" do
90+
expect(subject).to eq "Another: header; charset=utf-8"
10791
end
10892
end
10993
end

0 commit comments

Comments
 (0)