rspec-openapi 0.2.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb2aae5a8366967c1ce6a904bb990372af7f5d3b09d4c81fad3866e590c168c3
4
- data.tar.gz: 0665f9348a14cb0eb77c37368f69d3952d6eb71e919d8864931cdfbbbc3055dc
3
+ metadata.gz: b483a73ef32d771b4da3e66f3c63d1cd0a1cfbb839752546d5465cda43ff8444
4
+ data.tar.gz: cf8ec362c29193e2434db5433170ec655008cd5f636a8f2341f9abf7e7f3d454
5
5
  SHA512:
6
- metadata.gz: 69ec6408d396d9e4c75bc4ba3ef1c71f82ea2a7bbd67def082262b275c20b9769db851fdb2c1bea971a4ede35c10b6f325fbc35a73be8591f12a986e94a01637
7
- data.tar.gz: b71662890f9710012a329db3f380f831e10a8d37671fd9db2a9e3b84d237c699c0c40892020e4f20c781b36ab68563205208bc51607f73f5b2f002c80b5c48de
6
+ metadata.gz: de935612cc1afdd9c8ccca7d69711beca33dea36ec97555293978544c9c1a978c04cdc196c42a39888408d7575e40d0d6dff76adf1d629b27512c569feb9b29a
7
+ data.tar.gz: 423d62a92e8a9b143fae9304af57d893e079dbc9cb956e17320ea45a3bf30d8084f88395fdeaa835cd8689c3b72ecba2b40812bd9c81a0f044ddf91754169f9c
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
+ --exclude-pattern spec/requests/**/*_spec.rb
1
2
  --format documentation
3
+ --require rspec/openapi
2
4
  --color
3
- --require spec_helper
@@ -1,3 +1,26 @@
1
+ ## v0.3.3
2
+
3
+ * Avoid `JSON::ParserError` when a response body is no content
4
+ [#9](https://github.com/k0kubun/rspec-openapi/issues/9)
5
+
6
+ ## v0.3.2
7
+
8
+ * Stop generating format as path parameters in Rails
9
+ [#8](https://github.com/k0kubun/rspec-openapi/issues/8)
10
+
11
+ ## v0.3.1
12
+
13
+ * Add `RSpec::OpenAPI.description_builder` config to dynamically generate a description [experimental]
14
+ [#6](https://github.com/k0kubun/rspec-openapi/issues/6)
15
+
16
+ ## v0.3.0
17
+
18
+ * Initial support of rack-test and non-Rails apps [#5](https://github.com/k0kubun/rspec-openapi/issues/5)
19
+
20
+ ## v0.2.2
21
+
22
+ * Allow disabling `example` by `RSpec::OpenAPI.enable_example = false`
23
+
1
24
  ## v0.2.1
2
25
 
3
26
  * Generate `example` of request body and path / query params
data/Gemfile CHANGED
@@ -4,5 +4,9 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'rails', '6.0.3.2'
7
+ gem 'roda'
7
8
  gem 'rspec-rails'
8
- gem 'pry'
9
+
10
+ group :development do
11
+ gem 'pry'
12
+ end
data/README.md CHANGED
@@ -29,7 +29,7 @@ $ OPENAPI=1 rspec
29
29
 
30
30
  ### Example
31
31
 
32
- Let's say you have [a request spec](./spec/requests/tables_spec.rb) like this:
32
+ Let's say you have [a request spec](./spec/requests/rails/tables_spec.rb) like this:
33
33
 
34
34
  ```rb
35
35
  RSpec.describe 'Tables', type: :request do
@@ -55,7 +55,7 @@ If you run the spec with `OPENAPI=1`,
55
55
  OPENAPI=1 rspec spec/requests/tables_spec.rb
56
56
  ```
57
57
 
58
- It will generate [`doc/openapi.yaml` file](./spec/railsapp/doc/openapi.yaml) like:
58
+ It will generate [`doc/openapi.yaml` file](./spec/rails/doc/openapi.yaml) like:
59
59
 
60
60
  ```yml
61
61
  openapi: 3.0.3
@@ -93,7 +93,7 @@ paths:
93
93
 
94
94
  and the schema file can be used as an input of [Swagger UI](https://github.com/swagger-api/swagger-ui) or [Redoc](https://github.com/Redocly/redoc).
95
95
 
96
- ![Redoc example](./spec/railsapp/doc/screenshot.png)
96
+ ![Redoc example](./spec/rails/doc/screenshot.png)
97
97
 
98
98
 
99
99
  ### Configuration
@@ -104,6 +104,9 @@ The following configurations are optional.
104
104
  # Change the path to generate schema from `doc/openapi.yaml`
105
105
  RSpec::OpenAPI.path = 'doc/schema.yaml'
106
106
 
107
+ # Disable generating `example`
108
+ RSpec::OpenAPI.enable_example = false
109
+
107
110
  # Generate a comment on top of a schema file
108
111
  RSpec::OpenAPI.comment = <<~EOS
109
112
  This file is auto-generated by rspec-openapi https://github.com/k0kubun/rspec-openapi
@@ -111,6 +114,9 @@ RSpec::OpenAPI.comment = <<~EOS
111
114
  When you write a spec in spec/requests, running the spec with `OPENAPI=1 rspec` will
112
115
  update this file automatically. You can also manually edit this file.
113
116
  EOS
117
+
118
+ # Generate a custom description, given an RSpec example
119
+ RSpec::OpenAPI.description_builder = -> (example) { example.description }
114
120
  ```
115
121
 
116
122
  ### How can I add information which can't be generated from RSpec?
@@ -138,15 +144,13 @@ end
138
144
 
139
145
  ## Project status
140
146
 
141
- PoC / Experimental
147
+ Beta
142
148
 
143
- This worked for some of my Rails apps, but this may raise a basic error for your app.
149
+ Basic features are there, and some people are already using this.
144
150
 
145
- ### Current limitations
151
+ ### Current limitation
146
152
 
147
153
  * Generating a JSON file is not supported yet
148
- * This only works for RSpec request specs
149
- * Only Rails is supported for looking up a request route
150
154
 
151
155
  ### Other missing features with notes
152
156
 
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.pattern = 'spec/rspec/openapi/**/*_spec.rb'
6
+ end
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -3,8 +3,11 @@ require 'rspec/openapi/hooks' if ENV['OPENAPI']
3
3
 
4
4
  module RSpec::OpenAPI
5
5
  @path = 'doc/openapi.yaml'
6
+ @comment = nil
7
+ @enable_example = true
8
+ @description_builder = -> (example) { example.description }
6
9
 
7
10
  class << self
8
- attr_accessor :path, :comment
11
+ attr_accessor :path, :comment, :enable_example, :description_builder
9
12
  end
10
13
  end
@@ -8,8 +8,9 @@ require 'rspec/openapi/schema_merger'
8
8
  records = []
9
9
 
10
10
  RSpec.configuration.after(:each) do |example|
11
- if example.metadata[:type] == :request && example.metadata[:openapi] != false && request && response
12
- records << RSpec::OpenAPI::RecordBuilder.build(self, example: example)
11
+ if example.metadata[:type] == :request && example.metadata[:openapi] != false
12
+ record = RSpec::OpenAPI::RecordBuilder.build(self, example: example)
13
+ records << record if record
13
14
  end
14
15
  end
15
16
 
@@ -5,8 +5,7 @@ RSpec::OpenAPI::Record = Struct.new(
5
5
  :query_params, # @param [Hash] - {:query=>"string"}
6
6
  :request_params, # @param [Hash] - {:request=>"body"}
7
7
  :request_content_type, # @param [String] - "application/json"
8
- :controller, # @param [String] - "v1/statuses"
9
- :action, # @param [String] - "show"
8
+ :summary, # @param [String] - "v1/statuses #show"
10
9
  :description, # @param [String] - "returns a status"
11
10
  :status, # @param [Integer] - 200
12
11
  :response_body, # @param [Object] - {"status" => "ok"}
@@ -1,41 +1,80 @@
1
+ require 'action_dispatch'
1
2
  require 'rspec/openapi/record'
2
3
 
3
4
  class << RSpec::OpenAPI::RecordBuilder = Object.new
4
5
  # @param [RSpec::ExampleGroups::*] context
5
6
  # @param [RSpec::Core::Example] example
6
- # @return [RSpec::OpenAPI::Record]
7
+ # @return [RSpec::OpenAPI::Record,nil]
7
8
  def build(context, example:)
8
- # TODO: Support Non-Rails frameworks
9
- request = context.request
10
- response = context.response
11
- route = find_route(request)
9
+ if rack_test?(context)
10
+ request = ActionDispatch::Request.new(context.last_request.env)
11
+ response = ActionDispatch::TestResponse.new(*context.last_response.to_a)
12
+ else
13
+ request = context.request
14
+ response = context.response
15
+ end
16
+
17
+ # Generate `path` and `summary` in a framework-friendly manner when possible
18
+ if rails?
19
+ route = find_rails_route(request)
20
+ path = route.path.spec.to_s.delete_suffix('(.:format)')
21
+ summary = "#{route.requirements[:controller]} ##{route.requirements[:action]}"
22
+ else
23
+ path = request.path
24
+ summary = "#{request.method} #{request.path}"
25
+ end
26
+
27
+ response_body =
28
+ begin
29
+ response.parsed_body
30
+ rescue JSON::ParserError
31
+ nil
32
+ end
12
33
 
13
34
  RSpec::OpenAPI::Record.new(
14
35
  method: request.request_method,
15
- path: route.path.spec.to_s.delete_suffix('(.:format)'),
16
- path_params: request.path_parameters,
36
+ path: path,
37
+ path_params: raw_path_params(request),
17
38
  query_params: request.query_parameters,
18
39
  request_params: raw_request_params(request),
19
40
  request_content_type: request.content_type,
20
- controller: route.requirements[:controller],
21
- action: route.requirements[:action],
22
- description: example.description,
41
+ summary: summary,
42
+ description: RSpec::OpenAPI.description_builder.call(example),
23
43
  status: response.status,
24
- response_body: response.parsed_body,
44
+ response_body: response_body,
25
45
  response_content_type: response.content_type,
26
46
  ).freeze
27
47
  end
28
48
 
29
49
  private
30
50
 
51
+ def rails?
52
+ defined?(Rails) && Rails.application
53
+ end
54
+
55
+ def rack_test?(context)
56
+ defined?(Rack::Test::Methods) && context.class < Rack::Test::Methods
57
+ end
58
+
31
59
  # @param [ActionDispatch::Request] request
32
- def find_route(request)
60
+ def find_rails_route(request)
33
61
  Rails.application.routes.router.recognize(request) do |route|
34
62
  return route
35
63
  end
36
64
  raise "No route matched for #{request.request_method} #{request.path_info}"
37
65
  end
38
66
 
67
+ # :controller and :action always exist. :format is added when routes is configured as such.
68
+ def raw_path_params(request)
69
+ if rails?
70
+ request.path_parameters.reject do |key, _value|
71
+ %i[controller action format].include?(key)
72
+ end
73
+ else
74
+ request.path_parameters
75
+ end
76
+ end
77
+
39
78
  # workaround to get real request parameters
40
79
  # because ActionController::ParamsWrapper overwrites request_parameters
41
80
  def raw_request_params(request)
@@ -2,23 +2,28 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
2
2
  # @param [RSpec::OpenAPI::Record] record
3
3
  # @return [Hash]
4
4
  def build(record)
5
+ response = {
6
+ description: record.description,
7
+ }
8
+
9
+ if record.response_body
10
+ response[:content] = {
11
+ normalize_content_type(record.response_content_type) => {
12
+ schema: build_property(record.response_body),
13
+ example: (record.response_body if example_enabled?),
14
+ }.compact,
15
+ }
16
+ end
17
+
5
18
  {
6
19
  paths: {
7
20
  normalize_path(record.path) => {
8
21
  record.method.downcase => {
9
- summary: "#{record.controller} ##{record.action}",
22
+ summary: record.summary,
10
23
  parameters: build_parameters(record),
11
24
  requestBody: build_request_body(record),
12
25
  responses: {
13
- record.status.to_s => {
14
- description: record.description,
15
- content: {
16
- normalize_content_type(record.response_content_type) => {
17
- schema: build_property(record.response_body),
18
- example: record.response_body,
19
- },
20
- },
21
- },
26
+ record.status.to_s => response,
22
27
  },
23
28
  }.compact,
24
29
  },
@@ -28,18 +33,21 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
28
33
 
29
34
  private
30
35
 
36
+ def example_enabled?
37
+ RSpec::OpenAPI.enable_example
38
+ end
39
+
31
40
  def build_parameters(record)
32
41
  parameters = []
33
42
 
34
43
  record.path_params.each do |key, value|
35
- next if %i[controller action].include?(key)
36
44
  parameters << {
37
45
  name: key.to_s,
38
46
  in: 'path',
39
47
  required: true,
40
48
  schema: build_property(try_cast(value)),
41
- example: try_cast(value),
42
- }
49
+ example: (try_cast(value) if example_enabled?),
50
+ }.compact
43
51
  end
44
52
 
45
53
  record.query_params.each do |key, value|
@@ -47,8 +55,8 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
47
55
  name: key.to_s,
48
56
  in: 'query',
49
57
  schema: build_property(try_cast(value)),
50
- example: try_cast(value),
51
- }
58
+ example: (try_cast(value) if example_enabled?),
59
+ }.compact
52
60
  end
53
61
 
54
62
  return nil if parameters.empty?
@@ -63,8 +71,8 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
63
71
  content: {
64
72
  normalize_content_type(record.request_content_type) => {
65
73
  schema: build_property(record.request_params),
66
- example: record.request_params,
67
- }
74
+ example: (record.request_params if example_enabled?),
75
+ }.compact
68
76
  }
69
77
  }
70
78
  end
@@ -1,5 +1,5 @@
1
1
  module RSpec
2
2
  module OpenAPI
3
- VERSION = '0.2.1'
3
+ VERSION = '0.3.3'
4
4
  end
5
5
  end
@@ -23,5 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ['lib']
25
25
 
26
+ spec.add_dependency 'actionpack'
26
27
  spec.add_dependency 'rspec'
27
28
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-openapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takashi Kokubun
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-25 00:00:00.000000000 Z
11
+ date: 2020-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rspec
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -74,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
88
  - !ruby/object:Gem::Version
75
89
  version: '0'
76
90
  requirements: []
77
- rubygems_version: 3.2.0.pre1
91
+ rubygems_version: 3.1.2
78
92
  signing_key:
79
93
  specification_version: 4
80
94
  summary: Generate OpenAPI schema from RSpec request specs