api_sketch 0.1.1 → 0.1.2
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 +2 -0
- data/CHANGELOG.md +7 -2
- data/README.md +63 -9
- data/api_sketch.gemspec +0 -1
- data/examples/api_project/config/initializers/shared_blocks.rb +15 -0
- data/examples/api_project/resources/places.rb +138 -0
- data/examples/api_project/resources/users/points/stats.rb +30 -0
- data/examples/api_project/resources/users/points.rb +32 -0
- data/examples/api_project/resources/users.rb +341 -0
- data/lib/api_sketch/config.rb +1 -1
- data/lib/api_sketch/dsl/attribute_parser.rb +19 -0
- data/lib/api_sketch/dsl/attributes.rb +45 -0
- data/lib/api_sketch/dsl/base.rb +8 -0
- data/lib/api_sketch/dsl/complex_attribute_parser.rb +11 -0
- data/lib/api_sketch/dsl/headers.rb +17 -0
- data/lib/api_sketch/dsl/parameters.rb +31 -0
- data/lib/api_sketch/dsl/responses.rb +22 -0
- data/lib/api_sketch/dsl.rb +27 -165
- data/lib/api_sketch/examples_server.rb +1 -1
- data/lib/api_sketch/generators/base.rb +42 -0
- data/lib/api_sketch/{generators.rb → generators/bootstrap.rb} +2 -46
- data/lib/api_sketch/model/attribute.rb +42 -0
- data/lib/api_sketch/model/base.rb +17 -0
- data/lib/api_sketch/model/header.rb +7 -0
- data/lib/api_sketch/model/parameters.rb +50 -0
- data/lib/api_sketch/model/resource.rb +99 -0
- data/lib/api_sketch/model/response.rb +12 -0
- data/lib/api_sketch/model/shared_block.rb +17 -0
- data/lib/api_sketch/model.rb +0 -242
- data/lib/api_sketch/{renderers.rb → response_renderer.rb} +0 -0
- data/lib/api_sketch/version.rb +1 -1
- data/lib/api_sketch.rb +20 -3
- data/spec/lib/api_sketch/dsl/attribute_parser_spec.rb +14 -0
- data/spec/lib/api_sketch/dsl/attributes_spec.rb +14 -0
- data/spec/lib/api_sketch/dsl/complex_attribute_parser_spec.rb +14 -0
- data/spec/lib/api_sketch/dsl/headers_spec.rb +14 -0
- data/spec/lib/api_sketch/dsl/parameters_spec.rb +14 -0
- data/spec/lib/api_sketch/dsl/responses_spec.rb +14 -0
- data/spec/lib/api_sketch/dsl_spec.rb +5 -3
- data/spec/lib/api_sketch/model/parameters_spec.rb +28 -0
- data/spec/lib/api_sketch/{renderers_spec.rb → response_renderer_spec.rb} +51 -1
- data/spec/spec_helper.rb +3 -1
- data/spec/support/shared_examples.rb +9 -0
- metadata +43 -9
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
@@ -0,0 +1,341 @@
|
|
1
|
+
resource "Update user profile" do
|
2
|
+
action "update"
|
3
|
+
description "Authenticated user could update his profile fields and password"
|
4
|
+
path "/api/users/me.json"
|
5
|
+
http_method "PUT"
|
6
|
+
format "json"
|
7
|
+
|
8
|
+
headers do
|
9
|
+
add "Authorization" do
|
10
|
+
value "Token token=:token_value"
|
11
|
+
description ":token_value - is an authorization token value"
|
12
|
+
example { (:A..:z).to_a.shuffle[0,16].join }
|
13
|
+
required true
|
14
|
+
end
|
15
|
+
|
16
|
+
add "X-Test" do
|
17
|
+
value "Test=:perform_test"
|
18
|
+
description ":perform_test - test boolean value"
|
19
|
+
example true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
parameters do
|
24
|
+
query :document do
|
25
|
+
string "hello_message" do
|
26
|
+
description "some message"
|
27
|
+
end
|
28
|
+
|
29
|
+
integer "repeat_times" do
|
30
|
+
description "times to repeat hello message"
|
31
|
+
end
|
32
|
+
|
33
|
+
integer "page" do
|
34
|
+
description "page number"
|
35
|
+
required false
|
36
|
+
default 1
|
37
|
+
end
|
38
|
+
|
39
|
+
integer "per_page" do
|
40
|
+
description "items per page amount"
|
41
|
+
required false
|
42
|
+
default 25
|
43
|
+
end
|
44
|
+
|
45
|
+
string "name" do
|
46
|
+
description "place name"
|
47
|
+
required true
|
48
|
+
end
|
49
|
+
|
50
|
+
float "range" do
|
51
|
+
description "search range in km"
|
52
|
+
required false
|
53
|
+
example { rand(100) + rand.round(2) }
|
54
|
+
end
|
55
|
+
|
56
|
+
datetime "start_at" do
|
57
|
+
description "start at datetime"
|
58
|
+
required false
|
59
|
+
example { Time.now.to_s }
|
60
|
+
end
|
61
|
+
|
62
|
+
timestamp "seconds" do
|
63
|
+
description "seconds today"
|
64
|
+
example { Time.now.to_i }
|
65
|
+
end
|
66
|
+
|
67
|
+
array "place_ids" do
|
68
|
+
description "user's places ids"
|
69
|
+
required false
|
70
|
+
content do
|
71
|
+
integer do
|
72
|
+
description "hello number"
|
73
|
+
end
|
74
|
+
string do
|
75
|
+
description "more text here"
|
76
|
+
end
|
77
|
+
document do
|
78
|
+
content do
|
79
|
+
boolean "is_it_true" do
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
document do
|
84
|
+
description "some useless data :)"
|
85
|
+
content do
|
86
|
+
string "test" do
|
87
|
+
description "test string"
|
88
|
+
end
|
89
|
+
document "keys" do
|
90
|
+
content do
|
91
|
+
integer "sum" do
|
92
|
+
end
|
93
|
+
string "details text" do
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
body :document do
|
105
|
+
document "user" do
|
106
|
+
description "user's parameters fields"
|
107
|
+
required true
|
108
|
+
content do
|
109
|
+
string "email" do
|
110
|
+
description "user's email value"
|
111
|
+
end
|
112
|
+
string "password" do
|
113
|
+
description "user's profile password"
|
114
|
+
end
|
115
|
+
string "first_name" do
|
116
|
+
description "user's first name"
|
117
|
+
end
|
118
|
+
string "last_name" do
|
119
|
+
description "user's last name"
|
120
|
+
end
|
121
|
+
string "country_locode" do
|
122
|
+
example { ["US", "UA"].sample }
|
123
|
+
description "Country location code"
|
124
|
+
end
|
125
|
+
|
126
|
+
document "stats" do
|
127
|
+
content do
|
128
|
+
timestamp "login_at" do
|
129
|
+
description "last login timestamp"
|
130
|
+
example { Time.now.to_i }
|
131
|
+
end
|
132
|
+
|
133
|
+
integer "login_count" do
|
134
|
+
description "login count"
|
135
|
+
example { rand(10000) }
|
136
|
+
end
|
137
|
+
|
138
|
+
string "rank" do
|
139
|
+
description "users rank"
|
140
|
+
example { ["Junior", "Middle", "Senior"].sample }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
responses do
|
150
|
+
context "Success" do
|
151
|
+
http_status :ok # 200
|
152
|
+
|
153
|
+
parameters do
|
154
|
+
body :document do
|
155
|
+
document "user" do
|
156
|
+
content do
|
157
|
+
integer "id" do
|
158
|
+
description "User's ID"
|
159
|
+
end
|
160
|
+
string "email" do
|
161
|
+
description "user's email value"
|
162
|
+
example { "user#{rand(100)}@email.com" }
|
163
|
+
end
|
164
|
+
string "first_name" do
|
165
|
+
description "user's first name"
|
166
|
+
example { "First name #{rand(100)}" }
|
167
|
+
end
|
168
|
+
string "last_name" do
|
169
|
+
description "user's last name"
|
170
|
+
example { "Last name #{rand(100)}" }
|
171
|
+
end
|
172
|
+
document "country" do
|
173
|
+
content do
|
174
|
+
string "name" do
|
175
|
+
description "Country name"
|
176
|
+
example { ["USA", "Ukraine", "Poland"].sample }
|
177
|
+
end
|
178
|
+
string "id" do
|
179
|
+
example :location_code
|
180
|
+
description "Country ID (Location code)"
|
181
|
+
example { ["US", "UA", "PL"].sample }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
array "values" do
|
186
|
+
content do
|
187
|
+
string do
|
188
|
+
example { ["A", "B", "C"].sample }
|
189
|
+
end
|
190
|
+
integer do
|
191
|
+
example { rand(100) }
|
192
|
+
end
|
193
|
+
document do
|
194
|
+
content do
|
195
|
+
string "key" do
|
196
|
+
example { "Test #{rand(100)}" }
|
197
|
+
end
|
198
|
+
timestamp "time" do
|
199
|
+
example { Time.now.to_i }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
array "authentications" do
|
206
|
+
content do
|
207
|
+
document do
|
208
|
+
content do
|
209
|
+
string "uid" do
|
210
|
+
description "user's id at social network"
|
211
|
+
example { 5.times.map { 4.times.map { rand(10).to_s }.join }.join("-") }
|
212
|
+
end
|
213
|
+
string "provider" do
|
214
|
+
example { ["facebook", "twitter", "google_plus"].sample }
|
215
|
+
description "user's social network type"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context "Failure" do
|
228
|
+
http_status :bad_request # 400
|
229
|
+
|
230
|
+
parameters do
|
231
|
+
body :document do
|
232
|
+
document "error" do
|
233
|
+
content do
|
234
|
+
string "message" do
|
235
|
+
description "Error description"
|
236
|
+
example { "Epic fail at your parameters" }
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
resource "Get user profile" do
|
247
|
+
action "show"
|
248
|
+
description "Authenticated user could get data from his profile"
|
249
|
+
path "/api/users/me.json"
|
250
|
+
http_method "GET"
|
251
|
+
format "json"
|
252
|
+
|
253
|
+
headers do
|
254
|
+
add "Authorization" do
|
255
|
+
value "Token token=:token_value"
|
256
|
+
description ":token_value - is an authorization token value"
|
257
|
+
example { (:A..:z).to_a.shuffle[0,16].join }
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
responses do
|
262
|
+
context "Success" do
|
263
|
+
http_status :ok # 200
|
264
|
+
|
265
|
+
parameters do
|
266
|
+
body :document do
|
267
|
+
document "user" do
|
268
|
+
content do
|
269
|
+
integer "id" do
|
270
|
+
description "User's ID"
|
271
|
+
end
|
272
|
+
string "email" do
|
273
|
+
description "user's email value"
|
274
|
+
example { "user#{rand(100)}@email.com" }
|
275
|
+
end
|
276
|
+
string "first_name" do
|
277
|
+
description "user's first name"
|
278
|
+
example { "First name #{rand(100)}" }
|
279
|
+
end
|
280
|
+
string "last_name" do
|
281
|
+
description "user's last name"
|
282
|
+
example { "Last name #{rand(100)}" }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
|
293
|
+
resource "Create user profile" do
|
294
|
+
action "create"
|
295
|
+
description "User could register his profile"
|
296
|
+
path "/api/users.json"
|
297
|
+
http_method "POST"
|
298
|
+
format "json"
|
299
|
+
|
300
|
+
parameters do
|
301
|
+
body :document do
|
302
|
+
document "user" do
|
303
|
+
description "user's parameters fields"
|
304
|
+
required true
|
305
|
+
content do
|
306
|
+
string "email" do
|
307
|
+
description "user's email value"
|
308
|
+
end
|
309
|
+
string "password" do
|
310
|
+
description "user's profile password"
|
311
|
+
end
|
312
|
+
string "first_name" do
|
313
|
+
description "user's first name"
|
314
|
+
end
|
315
|
+
string "last_name" do
|
316
|
+
description "user's last name"
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
responses do
|
324
|
+
context "Success" do
|
325
|
+
http_status :ok # 200
|
326
|
+
|
327
|
+
parameters do
|
328
|
+
body :document do
|
329
|
+
string :token do
|
330
|
+
description "Auth token"
|
331
|
+
end
|
332
|
+
|
333
|
+
string :email do
|
334
|
+
description "User email"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
data/lib/api_sketch/config.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
class ApiSketch::DSL::AttributeParser < ApiSketch::DSL::Base
|
2
|
+
|
3
|
+
def initialize(container_type, &block)
|
4
|
+
@attribute_values = {}
|
5
|
+
@container_type = container_type
|
6
|
+
# INFO: Such long method name is used to ensure that we are would not have such value as key at hash
|
7
|
+
define_singleton_method(:set_attributes_as_hash_value_format, block)
|
8
|
+
set_attributes_as_hash_value_format
|
9
|
+
end
|
10
|
+
|
11
|
+
def method_missing(method_name, *arguments, &block)
|
12
|
+
@attribute_values[method_name] = arguments.first || block
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
@attribute_values
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class ApiSketch::DSL::Attributes < ApiSketch::DSL::Base
|
2
|
+
|
3
|
+
TYPES = [:integer, :string, :float, :boolean, :datetime, :timestamp, :document, :array]
|
4
|
+
|
5
|
+
def initialize(container_type, &block)
|
6
|
+
@container_type = container_type
|
7
|
+
@params = []
|
8
|
+
define_singleton_method(:initialize_attributes, block)
|
9
|
+
initialize_attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_a
|
13
|
+
@params
|
14
|
+
end
|
15
|
+
|
16
|
+
TYPES.each do |type_name|
|
17
|
+
define_method(type_name) do |*args, &block|
|
18
|
+
name = args.first
|
19
|
+
if @container_type == :document
|
20
|
+
if name.nil? || name.empty? # key name is not provided
|
21
|
+
raise ::ApiSketch::Error.new, "Key inside document should have name"
|
22
|
+
end
|
23
|
+
elsif @container_type == :array
|
24
|
+
if (!name.nil? && !name.empty?) # key name is provided
|
25
|
+
raise ::ApiSketch::Error.new, "Array element can't have name"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@params << self.class.build_by(type_name, name, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def build_by(data_type, attribute_name, &block)
|
34
|
+
options = {data_type: data_type}
|
35
|
+
options[:name] = attribute_name if attribute_name
|
36
|
+
case data_type
|
37
|
+
when :document, :array
|
38
|
+
::ApiSketch::Model::Attribute.new(::ApiSketch::DSL::ComplexAttributeParser.new(data_type, &block).to_h.merge(options))
|
39
|
+
else
|
40
|
+
::ApiSketch::Model::Attribute.new(::ApiSketch::DSL::AttributeParser.new(data_type, &block).to_h.merge(options))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class ApiSketch::DSL::ComplexAttributeParser < ApiSketch::DSL::AttributeParser
|
2
|
+
|
3
|
+
def method_missing(method_name, *arguments, &block)
|
4
|
+
if method_name == :content
|
5
|
+
@attribute_values[:content] = ApiSketch::DSL::Attributes.new(@container_type, &block).to_a
|
6
|
+
else
|
7
|
+
super(method_name, *arguments, &block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class ApiSketch::DSL::Headers < ApiSketch::DSL::Base
|
2
|
+
|
3
|
+
def initialize(&block)
|
4
|
+
@list = []
|
5
|
+
define_singleton_method(:initialize_headers_list, block)
|
6
|
+
initialize_headers_list
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_a
|
10
|
+
@list
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(name, &block)
|
14
|
+
@list << ::ApiSketch::Model::Header.new(::ApiSketch::DSL::AttributeParser.new(:document, &block).to_h.merge(name: name))
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class ApiSketch::DSL::Parameters < ApiSketch::DSL::Base
|
2
|
+
|
3
|
+
def initialize(&block)
|
4
|
+
@query = []
|
5
|
+
@body = []
|
6
|
+
@query_container_type = nil
|
7
|
+
@body_container_type = nil
|
8
|
+
define_singleton_method(:initialize_parameters_list, block)
|
9
|
+
initialize_parameters_list
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_h
|
13
|
+
{
|
14
|
+
query: @query,
|
15
|
+
body: @body,
|
16
|
+
query_container_type: @query_container_type,
|
17
|
+
body_container_type: @body_container_type
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def query(container_type, &block)
|
22
|
+
@query_container_type = container_type
|
23
|
+
@query += ::ApiSketch::DSL::Attributes.new(container_type, &block).to_a
|
24
|
+
end
|
25
|
+
|
26
|
+
def body(container_type, &block)
|
27
|
+
@body_container_type = container_type
|
28
|
+
@body += ::ApiSketch::DSL::Attributes.new(container_type, &block).to_a
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class ApiSketch::DSL::Responses < ApiSketch::DSL::Base
|
2
|
+
|
3
|
+
def initialize(&block)
|
4
|
+
@list = []
|
5
|
+
define_singleton_method(:initialize_responses_list, block)
|
6
|
+
initialize_responses_list
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_a
|
10
|
+
@list
|
11
|
+
end
|
12
|
+
|
13
|
+
def context(name, &block)
|
14
|
+
attributes = ::ApiSketch::DSL::AttributeParser.new(:root, &block).to_h
|
15
|
+
if attributes[:parameters]
|
16
|
+
params = ::ApiSketch::DSL::Parameters.new(&attributes[:parameters]).to_h
|
17
|
+
attributes[:parameters] = ::ApiSketch::Model::Parameters.new(params)
|
18
|
+
end
|
19
|
+
@list << ::ApiSketch::Model::Response.new(attributes.merge(name: name))
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|