diff --git a/.gitignore b/.gitignore index a0190963..473211c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +doc tmp .rvmrc .ruby-version diff --git a/Gemfile.lock b/Gemfile.lock index 73364087..8f0f6944 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) @@ -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 @@ -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 diff --git a/lib/rspec_api_documentation/configuration.rb b/lib/rspec_api_documentation/configuration.rb index c4721674..24845653 100644 --- a/lib/rspec_api_documentation/configuration.rb +++ b/lib/rspec_api_documentation/configuration.rb @@ -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 diff --git a/lib/rspec_api_documentation/dsl/endpoint/params.rb b/lib/rspec_api_documentation/dsl/endpoint/params.rb index 037788b8..c2501b20 100644 --- a/lib/rspec_api_documentation/dsl/endpoint/params.rb +++ b/lib/rspec_api_documentation/dsl/endpoint/params.rb @@ -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 diff --git a/lib/rspec_api_documentation/dsl/resource.rb b/lib/rspec_api_documentation/dsl/resource.rb index 1e45d4e2..c6569e2e 100644 --- a/lib/rspec_api_documentation/dsl/resource.rb +++ b/lib/rspec_api_documentation/dsl/resource.rb @@ -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 @@ -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 @@ -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 diff --git a/lib/rspec_api_documentation/views/api_blueprint_index.rb b/lib/rspec_api_documentation/views/api_blueprint_index.rb index 75e19449..57edaba1 100644 --- a/lib/rspec_api_documentation/views/api_blueprint_index.rb +++ b/lib/rspec_api_documentation/views/api_blueprint_index.rb @@ -12,19 +12,25 @@ 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: format_route(examples[0]), route_name: examples[0][:route_name], + explanation: examples[0][:route_explanation], attributes: attrs, parameters: params, http_methods: methods @@ -32,7 +38,7 @@ def sections end section.merge({ - routes: routes + routes: @configuration.sort_routes ? routes.sort_by { |r| r[:route_name] } : routes }) end end @@ -60,11 +66,10 @@ def format_route(example) # 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 @@ -74,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 diff --git a/rspec_api_documentation.gemspec b/rspec_api_documentation.gemspec index cc975926..8e1ba9ac 100644 --- a/rspec_api_documentation.gemspec +++ b/rspec_api_documentation.gemspec @@ -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" diff --git a/spec/dsl_spec.rb b/spec/dsl_spec.rb index 94253414..cd841aad 100644 --- a/spec/dsl_spec.rb +++ b/spec/dsl_spec.rb @@ -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 } @@ -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) diff --git a/spec/http_test_client_spec.rb b/spec/http_test_client_spec.rb index e2282371..9f73bb78 100644 --- a/spec/http_test_client_spec.rb +++ b/spec/http_test_client_spec.rb @@ -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 @@ -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 @@ -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 } diff --git a/spec/views/api_blueprint_index_spec.rb b/spec/views/api_blueprint_index_spec.rb index 862016c2..039a4833 100644 --- a/spec/views/api_blueprint_index_spec.rb +++ b/spec/views/api_blueprint_index_spec.rb @@ -63,7 +63,10 @@ let(:rspec_example_comments) do comment_group.route "/comments", "Comments Collection" do + explanation "Route explanation" + get("/comments") do + explanation "Method explanation" example_request 'Get all comments' do end end @@ -98,6 +101,8 @@ post_route = sections[1][:routes][1] post_route_with_optionals = sections[1][:routes][2] + expect(comments_route[:explanation]).to eq "Route explanation" + comments_examples = comments_route[:http_methods].map { |http_method| http_method[:examples] }.flatten expect(comments_examples.size).to eq 1 expect(comments_route[:route]).to eq "/comments" @@ -118,7 +123,7 @@ example: "1", name: "id", description: "The id", - properties_description: "required, string" + properties_description: "string, required" }] expect(post_route[:has_attributes?]).to eq true expect(post_route[:attributes]).to eq [{ @@ -139,11 +144,11 @@ example: "1", name: "id", description: "The id", - properties_description: "required, string" + properties_description: "string, required" }, { name: "option", description: nil, - properties_description: nil + properties_description: "optional" }] expect(post_route_with_optionals[:has_attributes?]).to eq false expect(post_route_with_optionals[:attributes]).to eq [] @@ -159,7 +164,7 @@ required: false, name: "description", description: nil, - properties_description: nil + properties_description: "optional" }] end end diff --git a/templates/rspec_api_documentation/api_blueprint_index.mustache b/templates/rspec_api_documentation/api_blueprint_index.mustache index 865f24a3..cbdae894 100644 --- a/templates/rspec_api_documentation/api_blueprint_index.mustache +++ b/templates/rspec_api_documentation/api_blueprint_index.mustache @@ -1,5 +1,8 @@ FORMAT: 1A + # {{ api_name }} +{{{ api_explanation }}} + {{# sections }} # Group {{ resource_name }} @@ -16,34 +19,57 @@ FORMAT: 1A ## {{ route_name }} [{{ route }}] {{# description }} -description: {{ description }} +{{ description }} {{/ description }} {{# explanation }} -explanation: {{ explanation }} +{{{ explanation }}} {{/ explanation }} {{# has_parameters? }} + Parameters + {{# parameters }} - + {{ name }}{{# example }}: {{ example }}{{/ example }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }}{{# description }} - {{ description }}{{/ description }} + + {{ name }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }} +{{# description }} + + {{ description }} +{{/ description }} {{/ parameters }} {{/ has_parameters? }} -{{# has_attributes? }} - -+ Attributes (object) -{{# attributes }} - + {{ name }}{{# example }}: {{ example }}{{/ example }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }}{{# description }} - {{ description }}{{/ description }} -{{/ attributes }} -{{/ has_attributes? }} {{# http_methods }} ### {{ description }} [{{ http_method }}] +{{# explanation }} + +{{{ explanation }}} +{{/ explanation }} + {{# examples }} {{# requests }} +{{# has_attributes? }} + ++ Parameters + +{{# attributes }} + + {{ name }}{{# value }}: {{ value }}{{/ value }}{{# properties_description }} ({{ properties_description }}){{/ properties_description }} +{{# description }} + + {{ description }} +{{/ description }} +{{# default }} + + + Default: {{ default }} +{{/ default }} +{{/ attributes }} +{{/ has_attributes? }} {{# has_request? }} + Request {{ description }}{{# request_content_type }} ({{ request_content_type }}){{/ request_content_type }} + + {{# explanation }} + {{{ explanation }}} + {{/ explanation }} {{/ has_request? }} {{# request_headers_text }}