apiculture 0.1.2 → 0.1.7

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
- 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: []