diff --git a/Gemfile.lock b/Gemfile.lock index ee975f9..c1e355f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - swagger_rails (0.0.1) + swagger_rails (1.0.0.pre.beta) rails (>= 3.1, < 5) GEM @@ -111,7 +111,7 @@ GEM treetop (1.4.15) polyglot polyglot (>= 0.3.1) - tzinfo (0.3.46) + tzinfo (0.3.49) PLATFORMS ruby @@ -119,6 +119,9 @@ PLATFORMS DEPENDENCIES generator_spec pry - rspec-rails + rspec-rails (~> 3.0) sqlite3 swagger_rails! + +BUNDLED WITH + 1.10.6 diff --git a/README.md b/README.md index e78a268..99df2a4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ swagger_rails ========= -Leverage your api/integration test specs to generate [swagger](http://swagger.io/) descriptions for Rails-based API's. Use the provided DSL to accurately test and describe API operations in your spec files. Then, you can easily generate corresponding swagger.json files and serve them up with an embedded version of [swagger-ui](https://github.com/swagger-api/swagger-ui). This means you can complement your API with a slick discovery UI to assist consumers with their integration efforts. Best of all, it requires minimal coding and maintenance, allowing you to focus on building an awesome API! +Generate API documentation, including a slick discovery UI and playground, directly from your rspec integration specs. Use the provided DSL to describe and test API operations in your spec files. Then, you can easily generate corresponding swagger.json files and serve them up with an embedded version of [swagger-ui](https://github.com/swagger-api/swagger-ui). Best of all, it requires minimal coding and maintenance, allowing you to focus on building an awesome API! And that's not all ... diff --git a/app/controllers/swagger_rails/swagger_ui_controller.rb b/app/controllers/swagger_rails/swagger_ui_controller.rb index d77e158..512150b 100644 --- a/app/controllers/swagger_rails/swagger_ui_controller.rb +++ b/app/controllers/swagger_rails/swagger_ui_controller.rb @@ -3,7 +3,7 @@ module SwaggerRails def index @discovery_paths = Hash[ - SwaggerRails.swagger_docs.map do |path, doc| + SwaggerRails.config.swagger_docs.map do |path, doc| [ "#{root_path}#{path}", doc[:info][:title] ] end ] diff --git a/lib/generators/swagger_rails/install/install_generator.rb b/lib/generators/swagger_rails/install/install_generator.rb index 1a3b8d9..8f5cf29 100644 --- a/lib/generators/swagger_rails/install/install_generator.rb +++ b/lib/generators/swagger_rails/install/install_generator.rb @@ -6,7 +6,7 @@ module SwaggerRails source_root File.expand_path('../templates', __FILE__) def add_swagger_dir - empty_directory('config/swagger/v1') + empty_directory('swagger/v1') end def add_initializer diff --git a/lib/generators/swagger_rails/install/templates/swagger.json b/lib/generators/swagger_rails/install/templates/swagger.json deleted file mode 100644 index e18a96f..0000000 --- a/lib/generators/swagger_rails/install/templates/swagger.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "version": "0.0.0", - "title": "[Enter a description for your API here]", - "description": "The docs below are powered by the default swagger.json that gets installed with swagger_rails. You'll need to update it to describe your API. See here for the complete swagger spec - https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md" - }, - "paths": { - "/a/sample/resource": { - "post": { - "tags": [ - "a/sample/resource" - ], - "description": "Create a new sample resource", - "parameters": [ - { - "name": "body", - "in": "body", - "schema": { - "$ref": "#/definitions/CreateSampleResource" - } - } - ], - "responses": { - "200": { - "description": "Ok" - } - } - } - } - }, - "definitions": { - "CreateSampleResource": { - "properties": { - "name": { - "type": "string" - }, - "date_time": { - "type": "string", - "format": "date-time" - } - } - } - } -} diff --git a/lib/generators/swagger_rails/install/templates/swagger_rails.rb b/lib/generators/swagger_rails/install/templates/swagger_rails.rb index cf3dcbd..6c2bc04 100644 --- a/lib/generators/swagger_rails/install/templates/swagger_rails.rb +++ b/lib/generators/swagger_rails/install/templates/swagger_rails.rb @@ -1,13 +1,16 @@ SwaggerRails.configure do |c| - # Define your swagger documents and provide global metadata - # Describe actual operations in your spec/test files - c.swagger_doc 'v1/swagger.json' do + # Define your swagger documents and provide any global metadata here + # (Individual operations are generated from your spec/test files) + c.swagger_doc 'v1/swagger.json', { + swagger: '2.0', info: { title: 'API V1', version: 'v1' } } - end + + # Specify a location to output generated swagger files + c.swagger_dir File.expand_path('../../../swagger', __FILE__) end diff --git a/lib/swagger_rails.rb b/lib/swagger_rails.rb index ff2ff45..d6c682e 100644 --- a/lib/swagger_rails.rb +++ b/lib/swagger_rails.rb @@ -2,23 +2,32 @@ require "swagger_rails/engine" module SwaggerRails - def self.configure - yield self + class Configuration + attr_reader :swagger_docs, :swagger_dir_string + + def initialize + @swagger_docs = {} + @swagger_dir_string = nil + end + + def swagger_doc(path, doc) + @swagger_docs[path] = doc + end + + def swagger_dir(dir_string) + @swagger_dir_string = dir_string + end end class << self - @@swagger_docs = {} + attr_reader :config - def swagger_doc(path, &block) - @@swagger_docs[path] = block + def configure + yield config end - def swagger_docs - Hash[ - @@swagger_docs.map do |path, factory| - [ path, factory.call.merge(swagger: '2.0') ] - end - ] + def config + @config ||= Configuration.new end end end diff --git a/lib/swagger_rails/engine.rb b/lib/swagger_rails/engine.rb index b5e698a..4b44f0d 100644 --- a/lib/swagger_rails/engine.rb +++ b/lib/swagger_rails/engine.rb @@ -6,7 +6,7 @@ module SwaggerRails isolate_namespace SwaggerRails initializer 'swagger_rails.initialize' do |app| - middleware.use SwaggerDocs, File.join(app.root, 'config', 'swagger') + middleware.use SwaggerDocs, SwaggerRails.config.swagger_dir_string middleware.use SwaggerUi, "#{root}/bower_components/swagger-ui/dist" end end diff --git a/lib/swagger_rails/rspec/dsl.rb b/lib/swagger_rails/rspec/dsl.rb index fc6561d..2cce961 100644 --- a/lib/swagger_rails/rspec/dsl.rb +++ b/lib/swagger_rails/rspec/dsl.rb @@ -49,12 +49,20 @@ module SwaggerRails end def run_test! + if metadata.has_key?(:swagger_doc) + swagger_doc = SwaggerRails.config.swagger_docs[metadata[:swagger_doc]] + else + swagger_doc = SwaggerRails.config.swagger_docs.values.first + end + + test_visitor = SwaggerRails::TestVisitor.new(swagger_doc) + before do |example| - SwaggerRails::TestVisitor.instance.submit_request!(self, example.metadata) + test_visitor.submit_request!(self, example.metadata) end it "returns a #{metadata[:response_code]} status" do |example| - SwaggerRails::TestVisitor.instance.assert_response!(self, example.metadata) + test_visitor.assert_response!(self, example.metadata) end end end diff --git a/lib/swagger_rails/rspec/formatter.rb b/lib/swagger_rails/rspec/formatter.rb index 6f4fe3e..4f5304a 100644 --- a/lib/swagger_rails/rspec/formatter.rb +++ b/lib/swagger_rails/rspec/formatter.rb @@ -9,9 +9,10 @@ module SwaggerRails :example_group_finished, :stop - def initialize(output) + def initialize(output, config=SwaggerRails.config) @output = output - @swagger_docs = SwaggerRails.swagger_docs + @swagger_docs = config.swagger_docs + @swagger_dir_string = config.swagger_dir_string @output.puts 'Generating Swagger Docs ...' end @@ -20,14 +21,14 @@ module SwaggerRails metadata = notification.group.metadata return unless metadata.has_key?(:response_code) + swagger_doc = @swagger_docs[metadata[:swagger_doc]] || @swagger_docs.values.first swagger_data = swagger_data_from(metadata) - swagger_doc = @swagger_docs[metadata[:docs_path]] || @swagger_docs.values.first swagger_doc.deep_merge!(swagger_data) end def stop(notification) @swagger_docs.each do |path, doc| - file_path = File.join(Rails.root, 'config/swagger', path) + file_path = File.join(@swagger_dir_string, path) File.open(file_path, 'w') do |file| file.write(JSON.pretty_generate(doc)) @@ -50,11 +51,19 @@ module SwaggerRails end def operation_from(metadata) - metadata.slice(:summary, :consumes, :produces, :parameters).tap do |operation| - operation[:responses] = { - metadata[:response_code] => metadata[:response] - } - end + { + tags: [ find_root_of(metadata)[:description] ] , + summary: metadata[:summary], + consumes: metadata[:consumes], + produces: metadata[:produces], + parameters: metadata[:parameters], + responses: { metadata[:response_code] => metadata[:response] } + } + end + + def find_root_of(metadata) + parent = metadata[:parent_example_group] + parent.nil? ? metadata : find_root_of(parent) end end end diff --git a/lib/swagger_rails/test_visitor.rb b/lib/swagger_rails/test_visitor.rb index fed430c..4cfd56d 100644 --- a/lib/swagger_rails/test_visitor.rb +++ b/lib/swagger_rails/test_visitor.rb @@ -1,8 +1,9 @@ -require 'singleton' - module SwaggerRails class TestVisitor - include Singleton + + def initialize(swagger_doc) + @swagger_doc = swagger_doc + end def submit_request!(test, metadata) params_data = params_data_for(test, metadata[:parameters]) @@ -35,6 +36,7 @@ module SwaggerRails path_params_data.each do |param_data| path.sub!("\{#{param_data[:name]}\}", param_data[:value].to_s) end + path.prepend(@swagger_doc[:basePath] || '') end end diff --git a/lib/tasks/swagger_rails_tasks.rake b/lib/tasks/swagger_rails_tasks.rake index c9e2f00..fb44b12 100644 --- a/lib/tasks/swagger_rails_tasks.rake +++ b/lib/tasks/swagger_rails_tasks.rake @@ -4,9 +4,10 @@ # end require 'rspec/core/rake_task' +require 'swagger_rails' desc 'Generate Swagger JSON files from integration specs' RSpec::Core::RakeTask.new('swaggerize') do |t| - t.pattern = 'spec/integration/**/*_spec.rb' + t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb' t.rspec_opts = [ '--format SwaggerRails::RSpec::Formatter', '--dry-run' ] end diff --git a/spec/dummy/config/initializers/swagger_rails.rb b/spec/dummy/config/initializers/swagger_rails.rb index cf3dcbd..6c2bc04 100644 --- a/spec/dummy/config/initializers/swagger_rails.rb +++ b/spec/dummy/config/initializers/swagger_rails.rb @@ -1,13 +1,16 @@ SwaggerRails.configure do |c| - # Define your swagger documents and provide global metadata - # Describe actual operations in your spec/test files - c.swagger_doc 'v1/swagger.json' do + # Define your swagger documents and provide any global metadata here + # (Individual operations are generated from your spec/test files) + c.swagger_doc 'v1/swagger.json', { + swagger: '2.0', info: { title: 'API V1', version: 'v1' } } - end + + # Specify a location to output generated swagger files + c.swagger_dir File.expand_path('../../../swagger', __FILE__) end diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb index 99850bd..5b0c02a 100644 --- a/spec/dummy/config/routes.rb +++ b/spec/dummy/config/routes.rb @@ -1,5 +1,5 @@ Rails.application.routes.draw do - resources :blogs, defaults: { :format => :json } - mount SwaggerRails::Engine => '/api-docs' + + resources :blogs, defaults: { :format => :json } end diff --git a/spec/dummy/spec/integration/blogs_spec.rb b/spec/dummy/spec/integration/blogs_spec.rb index ebeaab0..f864ae0 100644 --- a/spec/dummy/spec/integration/blogs_spec.rb +++ b/spec/dummy/spec/integration/blogs_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe 'Blogs API', swagger_doc: 'blogs/v1/swagger.json' do +describe 'Blogs API', swagger_doc: 'v1/swagger.json' do path '/blogs' do diff --git a/spec/dummy/swagger/v1/swagger.json b/spec/dummy/swagger/v1/swagger.json new file mode 100644 index 0000000..b13c2e9 --- /dev/null +++ b/spec/dummy/swagger/v1/swagger.json @@ -0,0 +1,90 @@ +{ + "swagger": "2.0", + "info": { + "title": "API V1", + "version": "v1" + }, + "paths": { + "/blogs": { + "post": { + "tags": [ + "Blogs API" + ], + "summary": "creates a new blog", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "blog", + "in": "body", + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "content": { + "type": "string" + } + } + } + } + ], + "responses": { + "201": { + "description": "valid request" + }, + "422": { + "description": "invalid request" + } + } + }, + "get": { + "tags": [ + "Blogs API" + ], + "summary": "searches existing blogs", + "consumes": null, + "produces": [ + "application/json" + ], + "parameters": [ + + ], + "responses": { + "200": { + "description": "valid request" + } + } + } + }, + "/blogs/{id}": { + "get": { + "tags": [ + "Blogs API" + ], + "summary": "retreives a specific blog", + "consumes": null, + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "type": "string" + } + ], + "responses": { + "200": { + "description": "blog found" + } + } + } + } + } +} \ No newline at end of file diff --git a/spec/generators/swagger_rails/install_generator_spec.rb b/spec/generators/swagger_rails/install_generator_spec.rb index b667edb..9ecdfba 100644 --- a/spec/generators/swagger_rails/install_generator_spec.rb +++ b/spec/generators/swagger_rails/install_generator_spec.rb @@ -14,7 +14,7 @@ describe SwaggerRails::InstallGenerator do end it 'creates a default swagger directory' do - assert_directory('config/swagger/v1') + assert_directory('swagger/v1') end it 'creates a swagger_rails initializer' do diff --git a/spec/test_visitor_spec.rb b/spec/test_visitor_spec.rb index 0923ac1..79b055b 100644 --- a/spec/test_visitor_spec.rb +++ b/spec/test_visitor_spec.rb @@ -5,7 +5,8 @@ module SwaggerRails describe TestVisitor do let(:test) { spy('test') } - subject { described_class.instance } + let(:swagger_doc) { {} } + subject { described_class.new(swagger_doc) } describe '#submit_request!' do before { subject.submit_request!(test, metadata) } @@ -93,6 +94,21 @@ module SwaggerRails ) end end + + context 'when a basePath is provided' do + let(:swagger_doc) { { basePath: '/api' } } + let(:metadata) do + { + path_template: '/resource', + http_verb: :get, + parameters: [] + } + end + + it 'prepends the basePath to the request path' do + expect(test).to have_received(:get).with('/api/resource', {}, {}) + end + end end describe '#assert_response' do