apiculture 0.1.6 → 0.2.0
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 +4 -4
- data/.gitignore +3 -5
- data/.travis.yml +2 -0
- data/CHANGELOG.md +3 -0
- data/apiculture.gemspec +19 -20
- data/gemfiles/Gemfile.rack-1.x +2 -3
- data/gemfiles/Gemfile.rack-2.x +2 -3
- data/lib/apiculture/app.rb +4 -4
- data/lib/apiculture/app_documentation.rb +4 -0
- data/lib/apiculture/openapi_documentation.rb +224 -0
- data/lib/apiculture/sinatra_instance_methods.rb +5 -5
- data/lib/apiculture/version.rb +1 -1
- data/lib/apiculture.rb +41 -40
- metadata +42 -45
- data/spec/apiculture/action_spec.rb +0 -45
- data/spec/apiculture/app_documentation_spec.rb +0 -126
- data/spec/apiculture/method_documentation_spec.rb +0 -102
- data/spec/apiculture_spec.rb +0 -461
- data/spec/spec_helper.rb +0 -15
data/lib/apiculture.rb
CHANGED
@@ -9,40 +9,41 @@ module Apiculture
|
|
9
9
|
require_relative 'apiculture/markdown_segment'
|
10
10
|
require_relative 'apiculture/timestamp_promise'
|
11
11
|
require_relative 'apiculture/app_documentation'
|
12
|
-
|
12
|
+
require_relative 'apiculture/openapi_documentation'
|
13
|
+
|
13
14
|
def self.extended(in_class)
|
14
15
|
in_class.send(:include, SinatraInstanceMethods)
|
15
16
|
super
|
16
17
|
end
|
17
|
-
|
18
|
+
|
18
19
|
IDENTITY_PROC = ->(arg) { arg }
|
19
|
-
|
20
|
+
|
20
21
|
AC_APPLY_TYPECAST_PROC = ->(cast_proc_or_method, v) {
|
21
22
|
cast_proc_or_method.is_a?(Symbol) ? v.public_send(cast_proc_or_method) : cast_proc_or_method.call(v)
|
22
23
|
}
|
23
|
-
|
24
|
+
|
24
25
|
AC_CHECK_PRESENCE_PROC = ->(name_as_string, params) {
|
25
26
|
params.has_key?(name_as_string) or raise MissingParameter.new(name_as_string)
|
26
27
|
}
|
27
|
-
|
28
|
+
|
28
29
|
AC_CHECK_TYPE_PROC = ->(param, value) {
|
29
30
|
param.matchable === value or raise ParameterTypeMismatch.new(param, value.class)
|
30
31
|
}
|
31
|
-
|
32
|
+
|
32
33
|
class Parameter < Struct.new(:name, :description, :required, :matchable, :cast_proc_or_method)
|
33
34
|
# Return Strings since Sinatra prefers string keys for params{}
|
34
35
|
def name_as_string; name.to_s; end
|
35
36
|
end
|
36
|
-
|
37
|
+
|
37
38
|
class RouteParameter < Parameter
|
38
39
|
end
|
39
|
-
|
40
|
+
|
40
41
|
class PossibleResponse < Struct.new(:http_status_code, :description, :jsonable_object_example)
|
41
42
|
def no_body?
|
42
43
|
jsonable_object_example.nil?
|
43
44
|
end
|
44
45
|
end
|
45
|
-
|
46
|
+
|
46
47
|
# Indicates where this API will be mounted. This is only used
|
47
48
|
# for the generated documentation. In general, this should match
|
48
49
|
# the SCRIPT_NAME of the Sinatra application when it will be called.
|
@@ -60,13 +61,13 @@ module Apiculture
|
|
60
61
|
def mounted_at(path)
|
61
62
|
@apiculture_mounted_at = path.to_s.gsub(/\/$/, '')
|
62
63
|
end
|
63
|
-
|
64
|
+
|
64
65
|
# Inserts the generation timestamp into the documentation at this point.
|
65
66
|
# The timestamp will be not very precise (to the minute) and in UTC time
|
66
67
|
def documentation_build_time!
|
67
68
|
apiculture_stack << Apiculture::TimestampPromise
|
68
69
|
end
|
69
|
-
|
70
|
+
|
70
71
|
# Inserts a literal Markdown string into the documentation at this point.
|
71
72
|
# For instance, if used after an API method declaration, it will insert
|
72
73
|
# the header between the API methods in the doc.
|
@@ -81,7 +82,7 @@ module Apiculture
|
|
81
82
|
def markdown_string(str)
|
82
83
|
apiculture_stack << MarkdownSegment.new(str)
|
83
84
|
end
|
84
|
-
|
85
|
+
|
85
86
|
# Inserts the contents of the file at +path+ into the documentation, using +markdown_string+.
|
86
87
|
# For instance, if used after an API method declaration, it will insert
|
87
88
|
# the header between the API methods in the doc.
|
@@ -94,25 +95,25 @@ module Apiculture
|
|
94
95
|
md = File.read(path_to_markdown).encode(Encoding::UTF_8)
|
95
96
|
markdown_string(md)
|
96
97
|
end
|
97
|
-
|
98
|
+
|
98
99
|
# Describe the API method that is going to be defined
|
99
100
|
def desc(action_description)
|
100
101
|
@apiculture_action_definition ||= ActionDefinition.new
|
101
102
|
@apiculture_action_definition.description = action_description.to_s
|
102
103
|
end
|
103
|
-
|
104
|
+
|
104
105
|
# Add an optional parameter for the API call
|
105
106
|
def param(name, description, matchable, cast: IDENTITY_PROC)
|
106
107
|
@apiculture_action_definition ||= ActionDefinition.new
|
107
108
|
@apiculture_action_definition.parameters << Parameter.new(name, description, required=false, matchable, cast)
|
108
109
|
end
|
109
|
-
|
110
|
+
|
110
111
|
# Add a requred parameter for the API call
|
111
112
|
def required_param(name, description, matchable, cast: IDENTITY_PROC)
|
112
113
|
@apiculture_action_definition ||= ActionDefinition.new
|
113
114
|
@apiculture_action_definition.parameters << Parameter.new(name, description, required=true, matchable, cast)
|
114
115
|
end
|
115
|
-
|
116
|
+
|
116
117
|
# Describe a parameter that has to be included in the URL of the API call.
|
117
118
|
# Route parameters are always required, and all the parameters specified
|
118
119
|
# using +route_param+ should also be included in the path given for the route
|
@@ -121,28 +122,28 @@ module Apiculture
|
|
121
122
|
@apiculture_action_definition ||= ActionDefinition.new
|
122
123
|
@apiculture_action_definition.route_parameters << RouteParameter.new(name, description, required=false, matchable, cast)
|
123
124
|
end
|
124
|
-
|
125
|
+
|
125
126
|
# Add a possible response, specifying the code and the JSON Response by example.
|
126
127
|
# Multiple response packages can be specified.
|
127
128
|
def responds_with(http_status, description, example_jsonable_object = nil)
|
128
129
|
@apiculture_action_definition ||= ActionDefinition.new
|
129
130
|
@apiculture_action_definition.responses << PossibleResponse.new(http_status, description, example_jsonable_object)
|
130
131
|
end
|
131
|
-
|
132
|
+
|
132
133
|
DefinitionError = Class.new(StandardError)
|
133
134
|
ValidationError = Class.new(StandardError)
|
134
|
-
|
135
|
+
|
135
136
|
class RouteParameterNotInPath < DefinitionError; end
|
136
137
|
class ReservedParameter < DefinitionError; end
|
137
138
|
class ConflictingParameter < DefinitionError; end
|
138
|
-
|
139
|
+
|
139
140
|
# Gets raised when a parameter is missing
|
140
141
|
class MissingParameter < ValidationError
|
141
142
|
def initialize(parameter_name)
|
142
143
|
super "Missing parameter :#{parameter_name}"
|
143
144
|
end
|
144
145
|
end
|
145
|
-
|
146
|
+
|
146
147
|
# Gets raised when a parameter is supplied and has a wrong type
|
147
148
|
class ParameterTypeMismatch < ValidationError
|
148
149
|
def initialize(ac_parameter, received_ruby_type)
|
@@ -151,7 +152,7 @@ module Apiculture
|
|
151
152
|
super "Received #{received_type}, expected #{expected_type.inspect} for :#{parameter_name}"
|
152
153
|
end
|
153
154
|
end
|
154
|
-
|
155
|
+
|
155
156
|
# Returns a Proc that calls the strong parameters to check the presence/types
|
156
157
|
def parametric_validator_proc_from(parametric_validators, implicitly_defined_route_parameter_names)
|
157
158
|
required_params = parametric_validators.select{|e| e.required }
|
@@ -163,23 +164,23 @@ module Apiculture
|
|
163
164
|
parametric_validators.each do |param|
|
164
165
|
param_name = param.name_as_string
|
165
166
|
next unless params.has_key?(param_name) # this is checked via required_params
|
166
|
-
|
167
|
+
|
167
168
|
# Apply the type cast and save it (since using our override we can mutate the params)
|
168
169
|
value_after_type_cast = AC_APPLY_TYPECAST_PROC.call(param.cast_proc_or_method, params[param_name])
|
169
170
|
params[param_name] = value_after_type_cast
|
170
|
-
|
171
|
+
|
171
172
|
# Ensure the typecast value adheres to the enforced Ruby type
|
172
173
|
AC_CHECK_TYPE_PROC.call(param, params[param_name])
|
173
174
|
end
|
174
|
-
|
175
|
-
# The following only applies if the app does not use strong_parameters -
|
175
|
+
|
176
|
+
# The following only applies if the app does not use strong_parameters -
|
176
177
|
# this makes use of parameter mutability again to kill the parameters that are not permitted
|
177
178
|
# or mentioned in the API specification. We need to keep the params which are specified in the
|
178
179
|
# route but not documented via Apiculture though
|
179
180
|
unexpected_parameters = Set.new(params.keys.map(&:to_s)) -
|
180
181
|
Set.new(parametric_validators.map(&:name).map(&:to_s)) -
|
181
182
|
Set.new(implicitly_defined_route_parameter_names.map(&:to_s))
|
182
|
-
|
183
|
+
|
183
184
|
unexpected_parameters.each do | parameter_to_discard |
|
184
185
|
# TODO: raise or record a warning
|
185
186
|
if env['rack.logger'].respond_to?(:warn)
|
@@ -189,7 +190,7 @@ module Apiculture
|
|
189
190
|
end
|
190
191
|
}
|
191
192
|
end
|
192
|
-
|
193
|
+
|
193
194
|
# Serve the documentation for the API at the given URL
|
194
195
|
def serve_api_documentation_at(url)
|
195
196
|
get(url) do
|
@@ -197,7 +198,7 @@ module Apiculture
|
|
197
198
|
self.class.api_documentation.to_html
|
198
199
|
end
|
199
200
|
end
|
200
|
-
|
201
|
+
|
201
202
|
# Returns an +AppDocumentation+ object for all actions defined so far.
|
202
203
|
#
|
203
204
|
# MyApi.api_documentation.to_markdown #=> "..."
|
@@ -205,14 +206,14 @@ 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)
|
212
213
|
action_def = (@apiculture_action_definition || ActionDefinition.new)
|
213
214
|
action_def.http_verb = http_verb
|
214
215
|
action_def.path = path
|
215
|
-
|
216
|
+
|
216
217
|
# Ensure no reserved Sinatra parameters are used
|
217
218
|
all_parameter_names = action_def.all_parameter_names_as_strings
|
218
219
|
%w( splat captures ).each do | reserved_param |
|
@@ -220,34 +221,34 @@ module Apiculture
|
|
220
221
|
raise ReservedParameter.new(":#{reserved_param} is a reserved magic parameter name in Sinatra")
|
221
222
|
end
|
222
223
|
end
|
223
|
-
|
224
|
+
|
224
225
|
# Ensure no conflations between route/req params
|
225
226
|
seen_params = {}
|
226
|
-
all_parameter_names.each do |e|
|
227
|
+
all_parameter_names.each do |e|
|
227
228
|
if seen_params[e]
|
228
|
-
raise ConflictingParameter.new(":#{e} mentioned twice as a possible parameter. Note that URL" +
|
229
|
+
raise ConflictingParameter.new(":#{e} mentioned twice as a possible parameter. Note that URL" +
|
229
230
|
" parameters and request parameters share a namespace.")
|
230
231
|
else
|
231
232
|
seen_params[e] = true
|
232
233
|
end
|
233
234
|
end
|
234
|
-
|
235
|
+
|
235
236
|
# Ensure the path has the route parameters that were predeclared
|
236
237
|
action_def.route_parameters.map(&:name).each do | route_parameter_key |
|
237
238
|
unless path.include?(':%s' % route_parameter_key)
|
238
239
|
raise RouteParameterNotInPath.new("Parameter :#{route_parameter_key} not present in path #{path.inspect}")
|
239
240
|
end
|
240
241
|
end
|
241
|
-
|
242
|
+
|
242
243
|
# TODO: ensure all route parameters are documented
|
243
|
-
|
244
|
+
|
244
245
|
# Pick out all the defined parameters and set up a block that can validate them
|
245
246
|
# when the action is called. With that, set up the actual Sinatra method that will
|
246
247
|
# respond to the request. We take care to preserve all the params that have NOT been documented
|
247
248
|
# using Apiculture but _were_ in fact specified in the actual path.
|
248
249
|
route_parameter_names = path.scan(/:([^:\/]+)/).flatten.map(&:to_sym)
|
249
250
|
parametric_checker_proc = parametric_validator_proc_from(action_def.parameters + action_def.route_parameters, route_parameter_names)
|
250
|
-
public_send(http_verb, path, options) do |*matched_sinatra_route_params|
|
251
|
+
public_send(http_verb, path, **options) do |*matched_sinatra_route_params|
|
251
252
|
# Extract all the parameter names from the route path as given to the method
|
252
253
|
route_parameters = Hash[route_parameter_names.zip(matched_sinatra_route_params)]
|
253
254
|
|
@@ -268,13 +269,13 @@ module Apiculture
|
|
268
269
|
# Execute the original action via instance_exec, passing along the route args
|
269
270
|
instance_exec(*route_parameters.values, &blk)
|
270
271
|
end
|
271
|
-
|
272
|
+
|
272
273
|
# Reset for the subsequent action definition
|
273
274
|
@apiculture_action_definition = ActionDefinition.new
|
274
275
|
# and store the just defined action for future use
|
275
276
|
apiculture_stack << action_def
|
276
277
|
end
|
277
|
-
|
278
|
+
|
278
279
|
def apiculture_stack
|
279
280
|
@apiculture_actions_and_docs ||= []
|
280
281
|
@apiculture_actions_and_docs
|
metadata
CHANGED
@@ -1,32 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apiculture
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
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:
|
12
|
+
date: 2023-02-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: builder
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
20
|
+
version: '3'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '
|
27
|
+
version: '3'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
29
|
+
name: github-markup
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
@@ -40,21 +40,21 @@ dependencies:
|
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '3'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: mustache
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '
|
48
|
+
version: '1'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
55
|
+
version: '1'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
57
|
+
name: mustermann
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
@@ -68,61 +68,61 @@ dependencies:
|
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '3'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
71
|
+
name: rdiscount
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
74
|
- - "~>"
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
76
|
+
version: '2'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: '
|
83
|
+
version: '2'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
85
|
+
name: bundler
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
|
-
- - "
|
88
|
+
- - "~>"
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version: '0'
|
90
|
+
version: '2.0'
|
91
91
|
type: :development
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
|
-
- - "
|
95
|
+
- - "~>"
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version: '0'
|
97
|
+
version: '2.0'
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
|
-
name:
|
99
|
+
name: cgi
|
100
100
|
requirement: !ruby/object:Gem::Requirement
|
101
101
|
requirements:
|
102
|
-
- - "
|
102
|
+
- - ">="
|
103
103
|
- !ruby/object:Gem::Version
|
104
|
-
version: '
|
104
|
+
version: '0'
|
105
105
|
type: :development
|
106
106
|
prerelease: false
|
107
107
|
version_requirements: !ruby/object:Gem::Requirement
|
108
108
|
requirements:
|
109
|
-
- - "
|
109
|
+
- - ">="
|
110
110
|
- !ruby/object:Gem::Version
|
111
|
-
version: '
|
111
|
+
version: '0'
|
112
112
|
- !ruby/object:Gem::Dependency
|
113
|
-
name:
|
113
|
+
name: rack-test
|
114
114
|
requirement: !ruby/object:Gem::Requirement
|
115
115
|
requirements:
|
116
|
-
- - "
|
116
|
+
- - ">="
|
117
117
|
- !ruby/object:Gem::Version
|
118
|
-
version: '
|
118
|
+
version: '0'
|
119
119
|
type: :development
|
120
120
|
prerelease: false
|
121
121
|
version_requirements: !ruby/object:Gem::Requirement
|
122
122
|
requirements:
|
123
|
-
- - "
|
123
|
+
- - ">="
|
124
124
|
- !ruby/object:Gem::Version
|
125
|
-
version: '
|
125
|
+
version: '0'
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: rake
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -138,33 +138,33 @@ dependencies:
|
|
138
138
|
- !ruby/object:Gem::Version
|
139
139
|
version: '0'
|
140
140
|
- !ruby/object:Gem::Dependency
|
141
|
-
name:
|
141
|
+
name: rdoc
|
142
142
|
requirement: !ruby/object:Gem::Requirement
|
143
143
|
requirements:
|
144
144
|
- - "~>"
|
145
145
|
- !ruby/object:Gem::Version
|
146
|
-
version: '
|
146
|
+
version: '6.0'
|
147
147
|
type: :development
|
148
148
|
prerelease: false
|
149
149
|
version_requirements: !ruby/object:Gem::Requirement
|
150
150
|
requirements:
|
151
151
|
- - "~>"
|
152
152
|
- !ruby/object:Gem::Version
|
153
|
-
version: '
|
153
|
+
version: '6.0'
|
154
154
|
- !ruby/object:Gem::Dependency
|
155
|
-
name:
|
155
|
+
name: rspec
|
156
156
|
requirement: !ruby/object:Gem::Requirement
|
157
157
|
requirements:
|
158
|
-
- - "
|
158
|
+
- - "~>"
|
159
159
|
- !ruby/object:Gem::Version
|
160
|
-
version: '
|
160
|
+
version: '3'
|
161
161
|
type: :development
|
162
162
|
prerelease: false
|
163
163
|
version_requirements: !ruby/object:Gem::Requirement
|
164
164
|
requirements:
|
165
|
-
- - "
|
165
|
+
- - "~>"
|
166
166
|
- !ruby/object:Gem::Version
|
167
|
-
version: '
|
167
|
+
version: '3'
|
168
168
|
description: A toolkit for building REST APIs on top of Rack
|
169
169
|
email: me@julik.nl
|
170
170
|
executables: []
|
@@ -175,6 +175,7 @@ extra_rdoc_files:
|
|
175
175
|
files:
|
176
176
|
- ".gitignore"
|
177
177
|
- ".travis.yml"
|
178
|
+
- CHANGELOG.md
|
178
179
|
- Gemfile
|
179
180
|
- LICENSE.txt
|
180
181
|
- README.md
|
@@ -191,20 +192,16 @@ files:
|
|
191
192
|
- lib/apiculture/indifferent_hash.rb
|
192
193
|
- lib/apiculture/markdown_segment.rb
|
193
194
|
- lib/apiculture/method_documentation.rb
|
195
|
+
- lib/apiculture/openapi_documentation.rb
|
194
196
|
- lib/apiculture/sinatra_instance_methods.rb
|
195
197
|
- lib/apiculture/timestamp_promise.rb
|
196
198
|
- lib/apiculture/version.rb
|
197
|
-
- spec/apiculture/action_spec.rb
|
198
|
-
- spec/apiculture/app_documentation_spec.rb
|
199
|
-
- spec/apiculture/method_documentation_spec.rb
|
200
|
-
- spec/apiculture_spec.rb
|
201
|
-
- spec/spec_helper.rb
|
202
199
|
homepage: https://github.com/WeTransfer/apiculture
|
203
200
|
licenses:
|
204
201
|
- MIT
|
205
202
|
metadata:
|
206
203
|
allowed_push_host: https://rubygems.org
|
207
|
-
post_install_message:
|
204
|
+
post_install_message:
|
208
205
|
rdoc_options: []
|
209
206
|
require_paths:
|
210
207
|
- lib
|
@@ -219,8 +216,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
219
216
|
- !ruby/object:Gem::Version
|
220
217
|
version: '0'
|
221
218
|
requirements: []
|
222
|
-
rubygems_version: 3.
|
223
|
-
signing_key:
|
219
|
+
rubygems_version: 3.2.33
|
220
|
+
signing_key:
|
224
221
|
specification_version: 4
|
225
222
|
summary: Sweet API sauce on top of Rack
|
226
223
|
test_files: []
|
@@ -1,45 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe Apiculture::Action do
|
4
|
-
context '.new' do
|
5
|
-
it 'exposes the methods of the object given as a first argument to initialize' do
|
6
|
-
action_class = Class.new(described_class)
|
7
|
-
fake_app = double('Apiculture::App', something: 'value')
|
8
|
-
action = action_class.new(fake_app)
|
9
|
-
expect(action).to respond_to(:something)
|
10
|
-
expect(action.something).to eq('value')
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'converts keyword arguments to instance variables' do
|
14
|
-
action_class = Class.new(described_class)
|
15
|
-
action = action_class.new(nil, foo: 'a string')
|
16
|
-
expect(action.instance_variable_get('@foo')).to eq('a string')
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'responds to perform()' do
|
21
|
-
expect(described_class.new(nil)).to respond_to(:perform)
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'can use bail() to throw a Sinatra halt' do
|
25
|
-
fake_app = double('Apiculture::App')
|
26
|
-
expect(fake_app).to receive(:json_halt).with('Failure', status: 400)
|
27
|
-
action_class = Class.new(described_class)
|
28
|
-
action_class.new(fake_app).bail "Failure"
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'can use bail() to throw a Sinatra halt with a custom status' do
|
32
|
-
fake_app = double('Apiculture::App')
|
33
|
-
expect(fake_app).to receive(:json_halt).with("Failure", status: 417)
|
34
|
-
|
35
|
-
action_class = Class.new(described_class)
|
36
|
-
action_class.new(fake_app).bail "Failure", status: 417
|
37
|
-
end
|
38
|
-
|
39
|
-
it 'can use bail() to throw a Sinatra halt with extra JSON attributes' do
|
40
|
-
fake_app = double('Apiculture::App')
|
41
|
-
expect(fake_app).to receive(:json_halt).with("Failure", status: 417, message: "Totale")
|
42
|
-
action_class = Class.new(described_class)
|
43
|
-
action_class.new(fake_app).bail "Failure", status: 417, message: 'Totale'
|
44
|
-
end
|
45
|
-
end
|
@@ -1,126 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
|
3
|
-
describe "Apiculture.api_documentation" do
|
4
|
-
let(:app) do
|
5
|
-
Class.new(Apiculture::App) do
|
6
|
-
extend Apiculture
|
7
|
-
|
8
|
-
markdown_string 'This API is very important. Because it has to do with pancakes.'
|
9
|
-
|
10
|
-
documentation_build_time!
|
11
|
-
|
12
|
-
desc 'Order a pancake'
|
13
|
-
required_param :diameter, "Diameter of the pancake. The pancake will be **bold**", Integer
|
14
|
-
param :topping, 'Type of topping', String
|
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' }
|
23
|
-
api_method :post, '/pancakes' do
|
24
|
-
end
|
25
|
-
|
26
|
-
desc 'Check the pancake status'
|
27
|
-
route_param :id, 'Pancake ID to check status on'
|
28
|
-
responds_with 200, 'When the pancake is found', { status: 'Baking' }
|
29
|
-
responds_with 404, 'When no such pancake exists', { status: 'No such pancake' }
|
30
|
-
api_method :get, '/pancake/:id' do
|
31
|
-
end
|
32
|
-
|
33
|
-
desc 'Throw away the pancake'
|
34
|
-
route_param :id, 'Pancake ID to delete'
|
35
|
-
api_method :delete, '/pancake/:id' do
|
36
|
-
end
|
37
|
-
|
38
|
-
desc 'Pancake ingredients are in the URL'
|
39
|
-
route_param :topping_id, 'Pancake topping ID', Integer, cast: :to_i
|
40
|
-
api_method :get, '/pancake/with/:topping_id' do |topping_id|
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
it 'generates app documentation as HTML without the body element' do
|
46
|
-
docco = app.api_documentation
|
47
|
-
generated_html = docco.to_html_fragment
|
48
|
-
|
49
|
-
expect(generated_html).not_to include('<body')
|
50
|
-
expect(generated_html).to include('Pancake ID to check status on')
|
51
|
-
expect(generated_html).to include('Pancake ID to delete')
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'generates app documentation in HTML' do
|
55
|
-
docco = app.api_documentation
|
56
|
-
generated_html = docco.to_html
|
57
|
-
|
58
|
-
if ENV['SHOW_TEST_DOC']
|
59
|
-
File.open('t.html', 'w') do |f|
|
60
|
-
f.write(generated_html)
|
61
|
-
f.flush
|
62
|
-
`open #{f.path}`
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
expect(generated_html).to include('<body')
|
67
|
-
expect(generated_html).to include('Pancake ID to check status on')
|
68
|
-
expect(generated_html).to include('When the pancake has been baked successfully')
|
69
|
-
expect(generated_html).to include('"id": "abdef..c21"')
|
70
|
-
expect(generated_html).to end_with("\n")
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'generates app documentation in Markdown' do
|
74
|
-
docco = app.api_documentation
|
75
|
-
generated_markdown = docco.to_markdown
|
76
|
-
|
77
|
-
expect(generated_markdown).not_to include('<body')
|
78
|
-
expect(generated_markdown).to include('## POST /pancakes')
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'generates app documentation honoring the mount point' do
|
82
|
-
overridden = Class.new(Apiculture::App) do
|
83
|
-
extend Apiculture
|
84
|
-
mounted_at '/api/v2/'
|
85
|
-
api_method :get, '/pancakes' do
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
generated_markdown = overridden.api_documentation.to_markdown
|
90
|
-
expect(generated_markdown).to include('## GET /api/v2/pancakes')
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'generates app documentation injecting the inline Markdown strings' do
|
94
|
-
app_class = Class.new(Apiculture::App) do
|
95
|
-
extend Apiculture
|
96
|
-
markdown_string '# This describes important stuff'
|
97
|
-
api_method :get, '/pancakes' do
|
98
|
-
end
|
99
|
-
markdown_string '# This describes even more important stuff'
|
100
|
-
markdown_string 'This is a paragraph'
|
101
|
-
end
|
102
|
-
|
103
|
-
generated_html = app_class.api_documentation.to_html
|
104
|
-
expect(generated_html).to include('<h2>GET /pancakes</h2>')
|
105
|
-
expect(generated_html).to include('<h1>This describes even more important stuff')
|
106
|
-
expect(generated_html).to include('<h1>This describes important stuff')
|
107
|
-
expect(generated_html).to include('<p>This is a paragraph')
|
108
|
-
end
|
109
|
-
|
110
|
-
context 'with a file containing Markdown that has to be spliced into the docs' do
|
111
|
-
before(:each) { File.open('./TEST.md', 'w') { |f| f << "# This is an important header" } }
|
112
|
-
after(:each) { File.unlink('./TEST.md') }
|
113
|
-
it 'splices the contents of the file using markdown_file' do
|
114
|
-
app_class = Class.new(Apiculture::App) do
|
115
|
-
extend Apiculture
|
116
|
-
markdown_file './TEST.md'
|
117
|
-
api_method :get, '/pancakes' do
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
generated_html = app_class.api_documentation.to_html
|
122
|
-
expect(generated_html).to include('<h2>GET /pancakes</h2>')
|
123
|
-
expect(generated_html).to include('<h1>This is an important header')
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|