apiculture 0.1.2 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 22ff31bb7b740ba2994dadcf1e76b10c5e47d5e4
4
- data.tar.gz: 7a0f4a80554d338542dd0eb9c1ecd39820218066
2
+ SHA256:
3
+ metadata.gz: 21e9fada9f8ba88c058f19789d597059bb30b055fe92c10e27953c5df6296758
4
+ data.tar.gz: 7b3c02a5587b6976a2a1a7b11656834824e32887b9dde3922542687fcbe63c71
5
5
  SHA512:
6
- metadata.gz: af3e86d8c2b3c1f8a6f42b03f96d4ff904afe5c7874a28d84ecdc41bec3bb0dea2984fa173ac111ba1e32343e7cebc6826af46dca93e17c61e2d94ba307168a3
7
- data.tar.gz: 21674f0cd0a3b0ca724724920184210661b7de00f40d834daf74d95c66371938c7408d56401580ded47955d9ba9e240ee95a4778963a5b3c128d7e9cd0eff819
6
+ metadata.gz: b0818c0fd6c04efa47f0cabcd0927c13516bd293d7486fea8750b7325d260208b3134432ebf43fbc9d7f0321a8ff7b8e5ea07e6f679e59b1df62e0f5f105f1b7
7
+ data.tar.gz: 853ca12aefe6005ae08432d29f902f6a1584252db87663c08c96a00f47d7b771108e2485f39e7400615e8f54243f68f6abf2e8406a963def5e3821c9ee62a6fc
data/.travis.yml CHANGED
@@ -2,12 +2,10 @@ gemfile:
2
2
  - gemfiles/Gemfile.rack-1.x
3
3
  - gemfiles/Gemfile.rack-2.x
4
4
  rvm:
5
- - 2.3.7
6
- - 2.4.4
7
- - 2.5.1
8
- - 2.6.0-preview1
5
+ - 2.3
6
+ - 2.4
7
+ - 2.5
8
+ - 2.6
9
+ - 2.7
9
10
  sudo: false
10
11
  cache: bundler
11
- matrix:
12
- allow_failures:
13
- - rvm: 2.6.0-preview1
data/apiculture.gemspec CHANGED
@@ -36,13 +36,13 @@ Gem::Specification.new do |s|
36
36
  s.add_runtime_dependency 'mustermann', '~> 1'
37
37
  s.add_runtime_dependency 'builder', '~> 3'
38
38
  s.add_runtime_dependency 'rdiscount', '~> 2'
39
- s.add_runtime_dependency 'github-markup', '~> 1'
39
+ s.add_runtime_dependency 'github-markup', '~> 3'
40
40
  s.add_runtime_dependency 'mustache', '~> 1'
41
41
 
42
42
  s.add_development_dependency 'rack-test'
43
- s.add_development_dependency "rspec", "~> 3.1", '< 3.2'
43
+ s.add_development_dependency "rspec", "~> 3"
44
44
  s.add_development_dependency "rdoc", "~> 6.0"
45
- s.add_development_dependency "rake", "~> 10"
45
+ s.add_development_dependency "rake"
46
46
  s.add_development_dependency "bundler", "~> 1.0"
47
47
  s.add_development_dependency "simplecov", ">= 0"
48
48
  end
@@ -12,6 +12,6 @@ group :development do
12
12
  gem "rspec", "~> 3.1", '< 3.2'
13
13
  gem "rdoc", "~> 6.0"
14
14
  gem "rake", "~> 10"
15
- gem "bundler", "~> 1.0"
16
15
  gem "simplecov", ">= 0"
16
+ gem "bundler"
17
17
  end
@@ -12,6 +12,6 @@ group :development do
12
12
  gem "rspec", "~> 3.1", '< 3.2'
13
13
  gem "rdoc", "~> 6.0"
14
14
  gem "rake", "~> 10"
15
- gem "bundler", "~> 1.0"
16
15
  gem "simplecov", ">= 0"
16
+ gem "bundler"
17
17
  end
data/lib/apiculture.rb CHANGED
@@ -9,6 +9,7 @@ module Apiculture
9
9
  require_relative 'apiculture/markdown_segment'
10
10
  require_relative 'apiculture/timestamp_promise'
11
11
  require_relative 'apiculture/app_documentation'
12
+ require_relative 'apiculture/openapi_documentation'
12
13
 
13
14
  def self.extended(in_class)
14
15
  in_class.send(:include, SinatraInstanceMethods)
@@ -205,7 +206,7 @@ module Apiculture
205
206
  def api_documentation
206
207
  AppDocumentation.new(self, @apiculture_mounted_at.to_s, @apiculture_actions_and_docs || [])
207
208
  end
208
-
209
+
209
210
  # Define an API method. Under the hood will call the related methods in Sinatra
210
211
  # to define the route.
211
212
  def api_method(http_verb, path, options={}, &blk)
@@ -102,7 +102,7 @@ class Apiculture::App
102
102
  end
103
103
 
104
104
  def perform_action_block(&blk)
105
- # Execut the action in a Sinatra-like fashion - passing the route parameter values as
105
+ # Executes the action in a Sinatra-like fashion - passing the route parameter values as
106
106
  # arguments to the given block/callable. This is where in the future we should ditch
107
107
  # the Sinatra calling conventions - Sinatra mandates that the action accept the route parameters
108
108
  # as arguments and grab all the useful stuff from instance methods like `params` etc. whereas
@@ -25,6 +25,10 @@ class Apiculture::AppDocumentation
25
25
  # Generates a Markdown string that contains the entire API documentation
26
26
  def to_markdown
27
27
  (['## %s' % @app_title] + to_markdown_slices).join("\n\n")
28
+ end
29
+
30
+ def to_openapi
31
+ OpenApiDocumentation::Base.new(@app_title, @mountpoint, @chunks)
28
32
  end
29
33
 
30
34
  # Generates an HTML fragment string that can be included into another HTML document
@@ -96,8 +96,8 @@
96
96
  }
97
97
  </style>
98
98
  </head>
99
-
99
+
100
100
  <body>
101
101
  {{& html_fragment }}
102
102
  </body>
103
- </html>
103
+ </html>
@@ -1,4 +1,6 @@
1
1
  require 'builder'
2
+ require 'rdiscount'
3
+
2
4
  # Generates Markdown/HTML documentation about a single API action.
3
5
  #
4
6
  # Formats route parameters and request/QS parameters as a neat HTML
@@ -26,12 +28,15 @@ class Apiculture::MethodDocumentation
26
28
 
27
29
  # Compose an HTML string by converting the result of +to_markdown+
28
30
  def to_html_fragment
29
- require 'rdiscount'
30
- RDiscount.new(to_markdown).to_html
31
+ markdown_string_to_html(to_markdown)
31
32
  end
32
33
 
33
34
  private
34
35
 
36
+ def markdown_string_to_html(str)
37
+ RDiscount.new(str.to_s).to_html
38
+ end
39
+
35
40
  class StringBuf #:nodoc:
36
41
  def initialize; @blocks = []; end
37
42
  def <<(block); @blocks << block.to_s; self; end
@@ -59,7 +64,7 @@ class Apiculture::MethodDocumentation
59
64
  @definition.route_parameters.each do | param |
60
65
  html.tr do
61
66
  html.td { html.tt(':%s' % param.name) }
62
- html.td(param.description)
67
+ html.td { html << markdown_string_to_html(param.description) }
63
68
  end
64
69
  end
65
70
  end
@@ -98,7 +103,7 @@ class Apiculture::MethodDocumentation
98
103
  @definition.responses.each do | resp |
99
104
  html.tr do
100
105
  html.td { html.b(resp.http_status_code) }
101
- html.td resp.description
106
+ html.td { html << markdown_string_to_html(resp.description) }
102
107
  html.td { html.pre { html.code(body_example(resp)) }}
103
108
  end
104
109
  end
@@ -139,7 +144,7 @@ class Apiculture::MethodDocumentation
139
144
  html.td { html.tt(param.name.to_s) }
140
145
  html.td(param.required ? 'Yes' : 'No')
141
146
  html.td(param.matchable.inspect)
142
- html.td(param.description.to_s)
147
+ html.td { html << markdown_string_to_html(param.description) }
143
148
  end
144
149
  end
145
150
  end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+ require 'yaml'
3
+ require 'base64'
4
+ module OpenApiDocumentation
5
+ class Base
6
+ def initialize(app, prefix, chunks)
7
+ @app, @prefix = app, prefix
8
+ @paths = chunks.select { |chunk| chunk.respond_to?(:http_verb) }
9
+ @data = {
10
+ openapi: '3.0.0',
11
+ info: {
12
+ title: @app.to_s,
13
+ version: '0.0.1',
14
+ description: @app.to_s + " " + chunks.select { |chunk| chunk.respond_to?(:to_markdown) }.map(&:to_markdown).join("\n")
15
+ },
16
+ tags: []
17
+ }
18
+ @data[:paths] = build_paths
19
+ end
20
+
21
+ def to_yaml
22
+ JSON.load(@data.to_json).to_yaml # trickery to get string based yaml
23
+ end
24
+
25
+ def paths
26
+ @data[:paths]
27
+ end
28
+
29
+ def spec
30
+ @data
31
+ end
32
+
33
+ private
34
+
35
+ def build_paths
36
+ paths = {}
37
+ @paths.each do |path|
38
+ path = Path.new(path, @prefix, @app)
39
+ paths = merge_paths(paths, path)
40
+ end
41
+ paths
42
+ end
43
+
44
+ # We don't have deep_merge here so this is the poor man's alternative
45
+ def merge_paths(paths, path)
46
+ if paths.key?(path.name)
47
+ paths[path.name].merge!(path.build[path.name])
48
+ else
49
+ paths.merge!(path.build)
50
+ end
51
+ paths
52
+ end
53
+
54
+ end
55
+
56
+ class Path
57
+ VERBS_WITHOUT_BODY = %w(get head delete options)
58
+
59
+ def initialize(path, prefix, app)
60
+ @path, @prefix, @app = path, prefix, app
61
+ end
62
+
63
+ def build
64
+ request_body = build_request_body unless VERBS_WITHOUT_BODY.include?(@path.http_verb)
65
+ {
66
+ name =>
67
+ {
68
+ @path.http_verb.to_sym => {
69
+ summary: @path.description,
70
+ description: @path.description,
71
+ tags: [ @app.to_s ],
72
+ parameters: build_parameters,
73
+ requestBody: request_body,
74
+ responses: build_responses,
75
+ operationId: operation_id
76
+ }.delete_if { |_k, v| v.nil? || v.empty? }
77
+ }
78
+ }
79
+ end
80
+
81
+ def name
82
+ full_path = @path.path.to_s
83
+ @path.route_parameters.each do |parameter|
84
+ # This is a bit confusing but naming is a little different between
85
+ # apiculture and openapi
86
+ full_path.gsub!(":#{parameter.name}", "\{#{parameter.name}\}")
87
+ end
88
+ Util.clean_path("#{@prefix}#{full_path}")
89
+ end
90
+
91
+ private
92
+
93
+ def operation_id
94
+ # base64 encoding to make sure these ids are safe to use in an url
95
+ Base64.urlsafe_encode64("#{@path.http_verb}#{@prefix}#{@path.path}")
96
+ end
97
+
98
+ def build_parameters
99
+ if VERBS_WITHOUT_BODY.include?(@path.http_verb)
100
+ build_route_parameters + build_query_parameters
101
+ else
102
+ build_route_parameters
103
+ end
104
+ end
105
+
106
+ def build_route_parameters
107
+ route_params = @path.route_parameters.map do |parameter|
108
+ {
109
+ name: parameter.name,
110
+ description: parameter.description,
111
+ required: true,
112
+ in: :path,
113
+ schema: {
114
+ type: Util.map_type(parameter.matchable),
115
+ example: Util.map_example(parameter.matchable)
116
+ }
117
+ }
118
+ end
119
+ route_params
120
+ end
121
+
122
+ def build_query_parameters
123
+ params = @path.parameters.map do |parameter|
124
+ {
125
+ name: parameter.name,
126
+ description: parameter.description,
127
+ required: true,
128
+ in: :query,
129
+ schema: {
130
+ type: Util.map_type(parameter.matchable),
131
+ example: parameter.matchable
132
+ }
133
+ }
134
+ end
135
+ params
136
+ end
137
+
138
+ def build_request_body
139
+ return nil if VERBS_WITHOUT_BODY.include?(@path.http_verb)
140
+
141
+ body_params = Hash[ @path.parameters.collect do |parameter|
142
+ [parameter.name, {
143
+ type: Util.map_type(parameter.matchable),
144
+ description: parameter.description
145
+ }]
146
+ end ]
147
+
148
+ return nil if body_params.count == 0
149
+
150
+ schema = {
151
+ type: :object,
152
+ properties: body_params
153
+ }
154
+
155
+ schema[:required] = @path.parameters.select(&:required).map(&:name) if @path.parameters.select(&:required).map(&:name).count > 0
156
+ {
157
+ content: {
158
+ "application/json": {
159
+ schema: schema
160
+ }
161
+ }
162
+ }
163
+ end
164
+
165
+ def build_responses
166
+ responses = Hash[@path.responses.collect do |response|
167
+ _response = {
168
+ description: response.description
169
+ }
170
+
171
+ unless response.jsonable_object_example.nil? || response.jsonable_object_example.empty?
172
+ _response[:content] = {
173
+ 'application/json': {
174
+ schema:
175
+ { type: 'object',
176
+ properties: Util.response_to_schema(response.jsonable_object_example) }
177
+ }
178
+ }
179
+ end
180
+
181
+ [response.http_status_code.to_s, _response]
182
+ end ]
183
+ responses
184
+ end
185
+ end
186
+
187
+ class Util
188
+ TYPES = {
189
+ String => 'string',
190
+ Integer => 'integer',
191
+ TrueClass => 'boolean'
192
+ }.freeze
193
+
194
+ EXAMPLES = {
195
+ String => 'string',
196
+ Integer => 1234,
197
+ TrueClass => true
198
+ }.freeze
199
+
200
+ def self.response_to_schema(response)
201
+ schema = {}
202
+ return nil if response.nil? || response.empty? || response.class == String
203
+ response.each do |key, value|
204
+ schema[key] = {
205
+ type: 'string',
206
+ example: value.to_s
207
+ }
208
+ end
209
+ schema
210
+ end
211
+
212
+ def self.map_type(type)
213
+ TYPES.fetch(type, 'string')
214
+ end
215
+
216
+ def self.map_example(type)
217
+ EXAMPLES.fetch(type, 'string')
218
+ end
219
+
220
+ def self.clean_path(path)
221
+ path.gsub(/\/\?\*\?$/, '')
222
+ end
223
+ end
224
+ end
@@ -1,3 +1,3 @@
1
1
  module Apiculture
2
- VERSION = '0.1.2'
2
+ VERSION = '0.1.7'
3
3
  end
@@ -1,28 +1,35 @@
1
1
  require_relative '../spec_helper'
2
2
 
3
3
  describe "Apiculture.api_documentation" do
4
- let(:app) {
4
+ let(:app) do
5
5
  Class.new(Apiculture::App) do
6
6
  extend Apiculture
7
-
7
+
8
8
  markdown_string 'This API is very important. Because it has to do with pancakes.'
9
-
9
+
10
10
  documentation_build_time!
11
-
11
+
12
12
  desc 'Order a pancake'
13
- required_param :diameter, "Diameter of the pancake", Integer
13
+ required_param :diameter, "Diameter of the pancake. The pancake will be **bold**", Integer
14
14
  param :topping, 'Type of topping', String
15
- responds_with 200, 'When the pancake is created succesfully', {id: 'abdef..c21'}
15
+ pancake_response_info = <<~EOS
16
+ When the pancake has been baked successfully
17
+ The pancake will have the following properties:
18
+
19
+ * It is going to be round
20
+ * It is going to be delicious
21
+ EOS
22
+ responds_with 200, pancake_response_info, { id: 'abdef..c21' }
16
23
  api_method :post, '/pancakes' do
17
24
  end
18
-
25
+
19
26
  desc 'Check the pancake status'
20
27
  route_param :id, 'Pancake ID to check status on'
21
- responds_with 200, 'When the pancake is found', {status: 'Baking'}
22
- responds_with 404, 'When no such pancake exists', {status: 'No such pancake'}
28
+ responds_with 200, 'When the pancake is found', { status: 'Baking' }
29
+ responds_with 404, 'When no such pancake exists', { status: 'No such pancake' }
23
30
  api_method :get, '/pancake/:id' do
24
31
  end
25
-
32
+
26
33
  desc 'Throw away the pancake'
27
34
  route_param :id, 'Pancake ID to delete'
28
35
  api_method :delete, '/pancake/:id' do
@@ -33,21 +40,21 @@ describe "Apiculture.api_documentation" do
33
40
  api_method :get, '/pancake/with/:topping_id' do |topping_id|
34
41
  end
35
42
  end
36
- }
37
-
43
+ end
44
+
38
45
  it 'generates app documentation as HTML without the body element' do
39
46
  docco = app.api_documentation
40
47
  generated_html = docco.to_html_fragment
41
-
48
+
42
49
  expect(generated_html).not_to include('<body')
43
50
  expect(generated_html).to include('Pancake ID to check status on')
44
51
  expect(generated_html).to include('Pancake ID to delete')
45
52
  end
46
-
53
+
47
54
  it 'generates app documentation in HTML' do
48
55
  docco = app.api_documentation
49
56
  generated_html = docco.to_html
50
-
57
+
51
58
  if ENV['SHOW_TEST_DOC']
52
59
  File.open('t.html', 'w') do |f|
53
60
  f.write(generated_html)
@@ -55,21 +62,22 @@ describe "Apiculture.api_documentation" do
55
62
  `open #{f.path}`
56
63
  end
57
64
  end
58
-
65
+
59
66
  expect(generated_html).to include('<body')
60
67
  expect(generated_html).to include('Pancake ID to check status on')
61
- expect(generated_html).to include('When the pancake is created succesfully')
68
+ expect(generated_html).to include('When the pancake has been baked successfully')
62
69
  expect(generated_html).to include('"id": "abdef..c21"')
70
+ expect(generated_html).to end_with("\n")
63
71
  end
64
-
72
+
65
73
  it 'generates app documentation in Markdown' do
66
74
  docco = app.api_documentation
67
75
  generated_markdown = docco.to_markdown
68
-
76
+
69
77
  expect(generated_markdown).not_to include('<body')
70
78
  expect(generated_markdown).to include('## POST /pancakes')
71
79
  end
72
-
80
+
73
81
  it 'generates app documentation honoring the mount point' do
74
82
  overridden = Class.new(Apiculture::App) do
75
83
  extend Apiculture
@@ -77,11 +85,11 @@ describe "Apiculture.api_documentation" do
77
85
  api_method :get, '/pancakes' do
78
86
  end
79
87
  end
80
-
88
+
81
89
  generated_markdown = overridden.api_documentation.to_markdown
82
90
  expect(generated_markdown).to include('## GET /api/v2/pancakes')
83
91
  end
84
-
92
+
85
93
  it 'generates app documentation injecting the inline Markdown strings' do
86
94
  app_class = Class.new(Apiculture::App) do
87
95
  extend Apiculture
@@ -91,16 +99,16 @@ describe "Apiculture.api_documentation" do
91
99
  markdown_string '# This describes even more important stuff'
92
100
  markdown_string 'This is a paragraph'
93
101
  end
94
-
102
+
95
103
  generated_html = app_class.api_documentation.to_html
96
104
  expect(generated_html).to include('<h2>GET /pancakes</h2>')
97
105
  expect(generated_html).to include('<h1>This describes even more important stuff')
98
106
  expect(generated_html).to include('<h1>This describes important stuff')
99
107
  expect(generated_html).to include('<p>This is a paragraph')
100
108
  end
101
-
109
+
102
110
  context 'with a file containing Markdown that has to be spliced into the docs' do
103
- before(:each) { File.open('./TEST.md', 'w') {|f| f << "# This is an important header"} }
111
+ before(:each) { File.open('./TEST.md', 'w') { |f| f << "# This is an important header" } }
104
112
  after(:each) { File.unlink('./TEST.md') }
105
113
  it 'splices the contents of the file using markdown_file' do
106
114
  app_class = Class.new(Apiculture::App) do
@@ -109,7 +117,7 @@ describe "Apiculture.api_documentation" do
109
117
  api_method :get, '/pancakes' do
110
118
  end
111
119
  end
112
-
120
+
113
121
  generated_html = app_class.api_documentation.to_html
114
122
  expect(generated_html).to include('<h2>GET /pancakes</h2>')
115
123
  expect(generated_html).to include('<h1>This is an important header')
@@ -7,7 +7,7 @@ describe Apiculture::MethodDocumentation do
7
7
 
8
8
  definition.description = "This action bakes pancakes"
9
9
  definition.parameters << Apiculture::Parameter.new(:name, 'Pancake name', true, String, :to_s)
10
- definition.parameters << Apiculture::Parameter.new(:thickness, 'Pancake thickness', false, Float, :to_f)
10
+ definition.parameters << Apiculture::Parameter.new(:thickness, 'Pancake **thick**ness', false, Float, :to_f)
11
11
  definition.parameters << Apiculture::Parameter.new(:diameter, 'Pancake diameter', false, Integer, :to_i)
12
12
 
13
13
  definition.route_parameters << Apiculture::RouteParameter.new(:pan_id, 'ID of the pancake frying pan')
@@ -28,9 +28,9 @@ describe Apiculture::MethodDocumentation do
28
28
  expect(generated_html).to include('<h3>URL parameters</h3>')
29
29
  expect(generated_html).to include('ID of the pancake frying pan')
30
30
  expect(generated_html).to include('<h3>Request parameters</h3>')
31
- expect(generated_html).to include('<td>Pancake name</td>')
32
- expect(generated_html).to include('<td>Pancake has been baked</td>')
33
- expect(generated_html).to include('<td>Frying pan too cold</td>')
31
+ expect(generated_html).to include('<p>Pancake name</p>')
32
+ expect(generated_html).to include('<p>Pancake has been baked</p>')
33
+ expect(generated_html).to include('<p>Frying pan too cold</p>')
34
34
  end
35
35
 
36
36
  it 'generates HTML from an ActionDefinition without route params' do
@@ -38,7 +38,7 @@ describe Apiculture::MethodDocumentation do
38
38
 
39
39
  definition.description = "This action bakes pancakes"
40
40
  definition.parameters << Apiculture::Parameter.new(:name, 'Pancake name', true, String, :to_s)
41
- definition.parameters << Apiculture::Parameter.new(:thickness, 'Pancake thickness', false, Float, :to_f)
41
+ definition.parameters << Apiculture::Parameter.new(:thickness, 'Pancake **thick**ness', false, Float, :to_f)
42
42
  definition.parameters << Apiculture::Parameter.new(:diameter, 'Pancake diameter', false, Integer, :to_i)
43
43
 
44
44
  definition.http_verb = 'get'
@@ -0,0 +1,197 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe 'Apiculture.api_documentation' do
4
+ let!(:test_class) do
5
+ class PancakeApi < Apiculture::App
6
+ extend Apiculture
7
+
8
+ documentation_build_time!
9
+
10
+ desc 'Order a pancake'
11
+ required_param :diameter, 'Diameter of the pancake. The pancake will be **bold**', Integer
12
+ param :topping, 'Type of topping', String
13
+ pancake_response_info = <<~EOS
14
+ When the pancake has been baked successfully
15
+ The pancake will have the following properties:
16
+
17
+ * It is going to be round
18
+ * It is going to be delicious
19
+ EOS
20
+ responds_with 200, pancake_response_info, { id: 'abdef..c21' }
21
+ api_method :post, '/pancakes' do
22
+ end
23
+
24
+ desc 'Check the pancake status'
25
+ route_param :id, 'Pancake ID to check status on'
26
+ responds_with 200, 'When the pancake is found', { status: 'Baking' }
27
+ responds_with 404, 'When no such pancake exists', { status: 'No such pancake' }
28
+ api_method :get, '/pancake/:id' do
29
+ end
30
+
31
+ desc 'Throw away the pancake'
32
+ route_param :id, 'Pancake ID to delete'
33
+ api_method :delete, '/pancake/:id' do
34
+ end
35
+
36
+ desc 'Pancake ingredients are in the URL'
37
+ route_param :topping_id, 'Pancake topping ID', Integer, cast: :to_i
38
+ api_method :get, '/pancake/with/:topping_id' do |topping_id|
39
+ end
40
+ end
41
+ end
42
+
43
+ let(:documentation) { PancakeApi.api_documentation }
44
+ let(:open_api_map) { documentation.to_openapi.spec }
45
+
46
+ describe '.to_openapi' do
47
+ it 'will have openapi version' do
48
+ expect(open_api_map).to include(openapi: '3.0.0')
49
+ end
50
+
51
+ describe 'info' do
52
+ let(:info) { open_api_map.fetch(:info) }
53
+
54
+ it 'will not to be empty' do
55
+ expect(info).not_to be_empty
56
+ end
57
+
58
+ it 'will have title' do
59
+ expect(info).to include(title: 'PancakeApi')
60
+ end
61
+
62
+ it 'will have version' do
63
+ expect(info).to include(version: '0.0.1')
64
+ end
65
+
66
+ it 'will have description' do
67
+ expect(info).to include(:description)
68
+ expect(info[:description]).to include('PancakeApi Documentation built on')
69
+ end
70
+ end
71
+
72
+ describe 'paths' do
73
+ let(:paths) { open_api_map.fetch(:paths) }
74
+
75
+ it 'will have paths' do
76
+ expect(paths).not_to be_empty
77
+ end
78
+
79
+ it 'will have 4 paths' do
80
+ expect(paths.size).to eq(3)
81
+ end
82
+
83
+ context 'POST /pancakes' do
84
+ let(:post_pancakes) { paths.dig('/pancakes', :post) }
85
+
86
+ it 'will have route' do
87
+ expect(post_pancakes).not_to be_empty
88
+ end
89
+
90
+ describe 'request body content' do
91
+ let(:request_content) { post_pancakes.dig(:requestBody, :content) }
92
+ it 'will have correct JSON content type' do
93
+ expect(request_content).to have_key(:'application/json')
94
+ end
95
+
96
+ describe 'schema' do
97
+ let(:schema) { request_content.dig(:'application/json', :schema) }
98
+ it 'will have type' do
99
+ expect(schema).to include(type: :object)
100
+ end
101
+
102
+ it 'will have required parameters' do
103
+ expect(schema).to include(required: [:diameter])
104
+ end
105
+
106
+ describe 'properties' do
107
+ let(:properties) { schema.fetch(:properties) }
108
+ it 'will have all properties' do
109
+ expect(properties).to have_key :diameter
110
+ expect(properties).to have_key :topping
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ context 'GET /pancake/:id' do
118
+ let(:get_pancake_by_id_path) { paths.dig('/pancake/{id}', :get) }
119
+ it 'will have route' do
120
+ expect(get_pancake_by_id_path).not_to be_empty
121
+ end
122
+
123
+ it 'will have summary' do
124
+ expect(get_pancake_by_id_path).to include(summary: 'Check the pancake status')
125
+ end
126
+
127
+ it 'will have a description' do
128
+ expect(get_pancake_by_id_path).to include(description: 'Check the pancake status')
129
+ end
130
+
131
+ it 'will have operationId' do
132
+ expect(get_pancake_by_id_path).to have_key :operationId
133
+ end
134
+
135
+ describe 'parameters' do
136
+ let(:parameter) { get_pancake_by_id_path.fetch(:parameters).first }
137
+
138
+ it 'will contain parameter' do
139
+ expect(parameter).not_to be_empty
140
+ end
141
+
142
+ it 'will have required propertie' do
143
+ expect(parameter).to include(required: true)
144
+ end
145
+
146
+ it 'will indicate a path parameter' do
147
+ expect(parameter).to include(in: :path)
148
+ end
149
+
150
+ describe 'schema' do
151
+ let(:schema) { parameter.fetch(:schema) }
152
+ it 'will have object type' do
153
+ expect(schema).to include(type: 'string')
154
+ end
155
+
156
+ it 'will have an example' do
157
+ expect(schema).to include(example: 'string')
158
+ end
159
+ end
160
+ end
161
+
162
+ describe 'responses' do
163
+ let(:responses) { get_pancake_by_id_path.fetch(:responses) }
164
+
165
+ it 'will contain all responses by response code' do
166
+ expect(responses).to have_key('200')
167
+ expect(responses).to have_key('404')
168
+ end
169
+
170
+ describe 'response 200' do
171
+ let(:response_200) { responses.fetch('200') }
172
+
173
+ it 'will have description' do
174
+ expect(response_200).to include(description: 'When the pancake is found')
175
+ end
176
+
177
+ it 'will have correct content type' do
178
+ expect(response_200.fetch(:content)).to have_key(:'application/json')
179
+ end
180
+
181
+ describe 'schema' do
182
+ let(:schema) { response_200.dig(:content, :'application/json', :schema) }
183
+
184
+ it 'will have object type' do
185
+ expect(schema).to include(type: 'object')
186
+ end
187
+
188
+ it 'will have properties' do
189
+ expect(schema).to have_key :properties
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apiculture
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
8
  - WeTransfer
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-02-15 00:00:00.000000000 Z
12
+ date: 2021-06-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mustermann
@@ -59,14 +59,14 @@ dependencies:
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: '1'
62
+ version: '3'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '1'
69
+ version: '3'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: mustache
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -101,20 +101,14 @@ dependencies:
101
101
  requirements:
102
102
  - - "~>"
103
103
  - !ruby/object:Gem::Version
104
- version: '3.1'
105
- - - "<"
106
- - !ruby/object:Gem::Version
107
- version: '3.2'
104
+ version: '3'
108
105
  type: :development
109
106
  prerelease: false
110
107
  version_requirements: !ruby/object:Gem::Requirement
111
108
  requirements:
112
109
  - - "~>"
113
110
  - !ruby/object:Gem::Version
114
- version: '3.1'
115
- - - "<"
116
- - !ruby/object:Gem::Version
117
- version: '3.2'
111
+ version: '3'
118
112
  - !ruby/object:Gem::Dependency
119
113
  name: rdoc
120
114
  requirement: !ruby/object:Gem::Requirement
@@ -133,16 +127,16 @@ dependencies:
133
127
  name: rake
134
128
  requirement: !ruby/object:Gem::Requirement
135
129
  requirements:
136
- - - "~>"
130
+ - - ">="
137
131
  - !ruby/object:Gem::Version
138
- version: '10'
132
+ version: '0'
139
133
  type: :development
140
134
  prerelease: false
141
135
  version_requirements: !ruby/object:Gem::Requirement
142
136
  requirements:
143
- - - "~>"
137
+ - - ">="
144
138
  - !ruby/object:Gem::Version
145
- version: '10'
139
+ version: '0'
146
140
  - !ruby/object:Gem::Dependency
147
141
  name: bundler
148
142
  requirement: !ruby/object:Gem::Requirement
@@ -197,12 +191,14 @@ files:
197
191
  - lib/apiculture/indifferent_hash.rb
198
192
  - lib/apiculture/markdown_segment.rb
199
193
  - lib/apiculture/method_documentation.rb
194
+ - lib/apiculture/openapi_documentation.rb
200
195
  - lib/apiculture/sinatra_instance_methods.rb
201
196
  - lib/apiculture/timestamp_promise.rb
202
197
  - lib/apiculture/version.rb
203
198
  - spec/apiculture/action_spec.rb
204
199
  - spec/apiculture/app_documentation_spec.rb
205
200
  - spec/apiculture/method_documentation_spec.rb
201
+ - spec/apiculture/openapi_documentation_spec.rb
206
202
  - spec/apiculture_spec.rb
207
203
  - spec/spec_helper.rb
208
204
  homepage: https://github.com/WeTransfer/apiculture
@@ -210,7 +206,7 @@ licenses:
210
206
  - MIT
211
207
  metadata:
212
208
  allowed_push_host: https://rubygems.org
213
- post_install_message:
209
+ post_install_message:
214
210
  rdoc_options: []
215
211
  require_paths:
216
212
  - lib
@@ -225,9 +221,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
225
221
  - !ruby/object:Gem::Version
226
222
  version: '0'
227
223
  requirements: []
228
- rubyforge_project:
229
- rubygems_version: 2.6.11
230
- signing_key:
224
+ rubygems_version: 3.0.9
225
+ signing_key:
231
226
  specification_version: 4
232
227
  summary: Sweet API sauce on top of Rack
233
228
  test_files: []