swaggable 0.4.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +13 -0
- data/Gemfile +9 -3
- data/README.md +86 -11
- data/lib/swaggable.rb +5 -0
- data/lib/swaggable/api_definition.rb +10 -0
- data/lib/swaggable/attribute_definition.rb +72 -0
- data/lib/swaggable/definition_base.rb +15 -0
- data/lib/swaggable/endpoint_definition.rb +1 -6
- data/lib/swaggable/enumerable_attributes.rb +29 -0
- data/lib/swaggable/grape_adapter.rb +4 -0
- data/lib/swaggable/grape_entity_translator.rb +38 -0
- data/lib/swaggable/parameter_definition.rb +11 -26
- data/lib/swaggable/rack_app.rb +4 -0
- data/lib/swaggable/response_definition.rb +1 -6
- data/lib/swaggable/schema_definition.rb +36 -0
- data/lib/swaggable/swagger_2_serializer.rb +42 -1
- data/lib/swaggable/swagger_2_validator.rb +5 -0
- data/lib/swaggable/tag_definition.rb +1 -6
- data/lib/swaggable/version.rb +1 -1
- data/spec/spec_helper.rb +7 -0
- data/spec/swaggable/api_definition_spec.rb +49 -1
- data/spec/swaggable/attribute_definition_spec.rb +87 -0
- data/spec/swaggable/grape_adapter_spec.rb +22 -0
- data/spec/swaggable/grape_entity_translator_spec.rb +64 -0
- data/spec/swaggable/integration_spec.rb +50 -6
- data/spec/swaggable/parameter_definition_spec.rb +27 -2
- data/spec/swaggable/rack_app_spec.rb +10 -0
- data/spec/swaggable/schema_definition_spec.rb +51 -0
- data/spec/swaggable/swagger_2_serializer_spec.rb +132 -1
- data/spec/swaggable/swagger_2_validator_spec.rb +15 -0
- data/swaggable.gemspec +3 -3
- metadata +28 -4
data/lib/swaggable/rack_app.rb
CHANGED
@@ -2,15 +2,10 @@ require 'forwarding_dsl'
|
|
2
2
|
|
3
3
|
module Swaggable
|
4
4
|
class ResponseDefinition
|
5
|
-
include
|
5
|
+
include DefinitionBase
|
6
6
|
|
7
7
|
getsetter :status
|
8
8
|
getsetter :description
|
9
|
-
|
10
|
-
def initialize args = {}
|
11
|
-
args.each {|k, v| self.send("#{k}=", v) }
|
12
|
-
yield self if block_given?
|
13
|
-
end
|
14
9
|
end
|
15
10
|
end
|
16
11
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Swaggable
|
2
|
+
class SchemaDefinition
|
3
|
+
include DefinitionBase
|
4
|
+
|
5
|
+
getsetter :name
|
6
|
+
|
7
|
+
def attributes &block
|
8
|
+
ForwardingDsl.run(
|
9
|
+
@attributes ||= build_attributes,
|
10
|
+
&block
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
attributes.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def == other
|
19
|
+
self.name == other.name if other.respond_to?(:name)
|
20
|
+
end
|
21
|
+
alias eql? ==
|
22
|
+
|
23
|
+
def hash
|
24
|
+
name.hash
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def build_attributes
|
30
|
+
MiniObject::IndexedList.new.tap do |l|
|
31
|
+
l.build { AttributeDefinition.new }
|
32
|
+
l.key {|e| e.name }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -18,6 +18,7 @@ module Swaggable
|
|
18
18
|
info: serialize_info(api),
|
19
19
|
tags: api.tags.map{|t| serialize_tag t },
|
20
20
|
paths: serialize_endpoints(api.endpoints),
|
21
|
+
definitions: serialize_definitions(api),
|
21
22
|
}
|
22
23
|
end
|
23
24
|
|
@@ -69,11 +70,40 @@ module Swaggable
|
|
69
70
|
required: parameter.required?,
|
70
71
|
}
|
71
72
|
|
72
|
-
p[:type] = parameter.type || 'string'
|
73
|
+
p[:type] = parameter.type || 'string' unless parameter.location == :body
|
73
74
|
p[:description] = parameter.description if parameter.description
|
75
|
+
|
76
|
+
unless parameter.schema.empty?
|
77
|
+
p[:schema] = {:"$ref" => "#/definitions/#{parameter.schema.name}"}
|
78
|
+
end
|
79
|
+
|
74
80
|
p
|
75
81
|
end
|
76
82
|
|
83
|
+
def serialize_parameter_schema schema
|
84
|
+
out = {type: 'object'}
|
85
|
+
|
86
|
+
required_attrs = schema.attributes.select(&:required?)
|
87
|
+
out[:required] = required_attrs.map(&:name) if required_attrs.any?
|
88
|
+
|
89
|
+
out[:properties] = schema.attributes.inject({}) do |acc, attribute|
|
90
|
+
acc[attribute.name] = serialize_parameter_attribute attribute
|
91
|
+
acc
|
92
|
+
end
|
93
|
+
|
94
|
+
out
|
95
|
+
end
|
96
|
+
|
97
|
+
def serialize_parameter_attribute attribute
|
98
|
+
{
|
99
|
+
type: attribute.json_type,
|
100
|
+
}.
|
101
|
+
tap do |e|
|
102
|
+
e[:description] = attribute.description if attribute.description
|
103
|
+
e[:format] = attribute.json_format if attribute.json_format
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
77
107
|
def serialize_responses responses
|
78
108
|
if responses.any?
|
79
109
|
responses.inject({}) do |acc, r|
|
@@ -85,8 +115,19 @@ module Swaggable
|
|
85
115
|
end
|
86
116
|
end
|
87
117
|
|
118
|
+
def serialize_definitions api
|
119
|
+
api.used_schemas.inject({}) do |acc, schema|
|
120
|
+
acc[schema.name] = serialize_parameter_schema schema
|
121
|
+
acc
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
88
125
|
def validate! api
|
89
126
|
Swagger2Validator.validate! serialize(api)
|
90
127
|
end
|
128
|
+
|
129
|
+
def validate api
|
130
|
+
Swagger2Validator.validate serialize(api)
|
131
|
+
end
|
91
132
|
end
|
92
133
|
end
|
@@ -2,18 +2,13 @@ require 'forwarding_dsl'
|
|
2
2
|
|
3
3
|
module Swaggable
|
4
4
|
class TagDefinition
|
5
|
-
include
|
5
|
+
include DefinitionBase
|
6
6
|
|
7
7
|
getsetter(
|
8
8
|
:name,
|
9
9
|
:description,
|
10
10
|
)
|
11
11
|
|
12
|
-
def initialize args = {}
|
13
|
-
args.each {|k, v| self.send("#{k}=", v) }
|
14
|
-
yield self if block_given?
|
15
|
-
end
|
16
|
-
|
17
12
|
def == other
|
18
13
|
self.name == other.name if other.respond_to?(:name)
|
19
14
|
end
|
data/lib/swaggable/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -11,6 +11,13 @@ RSpec.configure do |config|
|
|
11
11
|
# config.formatter = :documentation # :documentation, :progress, :html, :textmate
|
12
12
|
end
|
13
13
|
|
14
|
+
WebMock.disable_net_connect!(:allow => "codeclimate.com")
|
15
|
+
|
16
|
+
if !!ENV['CODECLIMATE_REPO_TOKEN']
|
17
|
+
require 'codeclimate-test-reporter'
|
18
|
+
CodeClimate::TestReporter.start
|
19
|
+
end
|
20
|
+
|
14
21
|
$LOAD_PATH.unshift File.expand_path('lib')
|
15
22
|
require 'swaggable'
|
16
23
|
|
@@ -123,7 +123,55 @@ RSpec.describe 'Swaggable::ApiDefinition' do
|
|
123
123
|
end
|
124
124
|
|
125
125
|
it 'is frozen to avoid giving the false impression that it can be modified' do
|
126
|
-
expect{ subject.tags << instance_double(Swaggable::TagDefinition) }.to raise_error
|
126
|
+
expect{ subject.tags << instance_double(Swaggable::TagDefinition) }.to raise_error(RuntimeError)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#used_schemas' do
|
131
|
+
it 'is collected from params' do
|
132
|
+
schema = instance_double(Swaggable::SchemaDefinition, name: 'An schema', empty?: false)
|
133
|
+
endpoint = Swaggable::EndpointDefinition.new verb: :get, path: '/'
|
134
|
+
parameter = Swaggable::ParameterDefinition.new name: 'A param', schema: schema
|
135
|
+
endpoint.parameters << parameter
|
136
|
+
subject.endpoints << endpoint
|
137
|
+
expect(subject.used_schemas.first).to eq schema
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'avoids duplicates' do
|
141
|
+
schema_1 = Swaggable::SchemaDefinition.new name: 'schema_1'
|
142
|
+
schema_1_again = Swaggable::SchemaDefinition.new name: 'schema_1'
|
143
|
+
schema_2 = Swaggable::SchemaDefinition.new name: 'schema_2'
|
144
|
+
|
145
|
+
schema_1.attributes.add_new { name :attr_1 }
|
146
|
+
schema_1_again.attributes.add_new { name :attr_1 }
|
147
|
+
schema_2.attributes.add_new { name :attr_2 }
|
148
|
+
|
149
|
+
parameter_1 = Swaggable::ParameterDefinition.new name: 'A param', schema: schema_1
|
150
|
+
parameter_2 = Swaggable::ParameterDefinition.new name: 'A param', schema: schema_1_again
|
151
|
+
parameter_3 = Swaggable::ParameterDefinition.new name: 'A param', schema: schema_2
|
152
|
+
|
153
|
+
endpoint_a = Swaggable::EndpointDefinition.new verb: :get, path: '/a'
|
154
|
+
endpoint_b = Swaggable::EndpointDefinition.new verb: :get, path: '/b'
|
155
|
+
endpoint_c = Swaggable::EndpointDefinition.new verb: :get, path: '/c'
|
156
|
+
|
157
|
+
endpoint_a.parameters << parameter_1
|
158
|
+
endpoint_b.parameters << parameter_2
|
159
|
+
endpoint_c.parameters << parameter_3
|
160
|
+
|
161
|
+
subject.endpoints << endpoint_a
|
162
|
+
subject.endpoints << endpoint_b
|
163
|
+
subject.endpoints << endpoint_c
|
164
|
+
|
165
|
+
expect(subject.used_schemas.to_a).to eq [schema_1_again, schema_2]
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'is empty array when no tags are present' do
|
169
|
+
subject.endpoints.clear
|
170
|
+
expect(subject.used_schemas).to eq []
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'is frozen to avoid giving the false impression that it can be modified' do
|
174
|
+
expect{ subject.used_schemas << instance_double(Swaggable::SchemaDefinition) }.to raise_error(RuntimeError)
|
127
175
|
end
|
128
176
|
end
|
129
177
|
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Swaggable::AttributeDefinition' do
|
4
|
+
subject { subject_instance }
|
5
|
+
let(:subject_instance) { subject_class.new }
|
6
|
+
let(:subject_class) { Swaggable::AttributeDefinition }
|
7
|
+
|
8
|
+
it 'has a name' do
|
9
|
+
subject.name = 'my_attr'
|
10
|
+
expect(subject.name).to eq 'my_attr'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has a description' do
|
14
|
+
subject.description = 'my_attr'
|
15
|
+
expect(subject.description).to eq 'my_attr'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has string type' do
|
19
|
+
subject.type :string
|
20
|
+
expect(subject.json_type).to be :string
|
21
|
+
expect(subject.json_format).to be nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'has integer type' do
|
25
|
+
subject.type :integer
|
26
|
+
expect(subject.json_type).to be :integer
|
27
|
+
expect(subject.json_format).to be :int32
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'has float type' do
|
31
|
+
subject.type :float
|
32
|
+
expect(subject.json_type).to be :number
|
33
|
+
expect(subject.json_format).to be :float
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'has long type' do
|
37
|
+
subject.type :long
|
38
|
+
expect(subject.json_type).to be :integer
|
39
|
+
expect(subject.json_format).to be :int64
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'has double type' do
|
43
|
+
subject.type :double
|
44
|
+
expect(subject.json_type).to be :number
|
45
|
+
expect(subject.json_format).to be :double
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'has byte type' do
|
49
|
+
subject.type :byte
|
50
|
+
expect(subject.json_type).to be :string
|
51
|
+
expect(subject.json_format).to be :byte
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'has boolean type' do
|
55
|
+
subject.type :boolean
|
56
|
+
expect(subject.json_type).to be :boolean
|
57
|
+
expect(subject.json_format).to be nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'has date type' do
|
61
|
+
subject.type :date
|
62
|
+
expect(subject.json_type).to be :string
|
63
|
+
expect(subject.json_format).to be :date
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'has date_time type' do
|
67
|
+
subject.type :date_time
|
68
|
+
expect(subject.json_type).to be :string
|
69
|
+
expect(subject.json_format).to be :"date-time"
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'has password type' do
|
73
|
+
subject.type :password
|
74
|
+
expect(subject.json_type).to be :string
|
75
|
+
expect(subject.json_format).to be :password
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'doesn\'t allow random types' do
|
79
|
+
expect{ subject.type :asdfgh }.to raise_error(ArgumentError)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'can be required or optional' do
|
83
|
+
subject.required true
|
84
|
+
expect(subject).to be_required
|
85
|
+
expect(subject).not_to be_optional
|
86
|
+
end
|
87
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
2
|
require 'grape'
|
3
|
+
require 'grape-entity'
|
3
4
|
|
4
5
|
RSpec.describe 'Swaggable::GrapeAdapter' do
|
5
6
|
let(:subject_class) { Swaggable::GrapeAdapter }
|
@@ -193,6 +194,27 @@ RSpec.describe 'Swaggable::GrapeAdapter' do
|
|
193
194
|
expect(api.endpoints.first.responses.first.description).to eq 'Created'
|
194
195
|
end
|
195
196
|
end
|
197
|
+
|
198
|
+
describe 'entities' do
|
199
|
+
it 'generates an schema' do
|
200
|
+
user_class = Class.new(Grape::Entity)
|
201
|
+
|
202
|
+
grape.desc 'Create User', entity: user_class
|
203
|
+
grape.post('/')
|
204
|
+
|
205
|
+
parameter_definition = Swaggable::ParameterDefinition.new name: :first_name
|
206
|
+
|
207
|
+
allow(Swaggable::GrapeEntityTranslator).
|
208
|
+
to receive(:parameter_from).
|
209
|
+
with(user_class).
|
210
|
+
and_return(parameter_definition)
|
211
|
+
|
212
|
+
do_import
|
213
|
+
|
214
|
+
parameter = api.endpoints['POST /'].parameters[:first_name]
|
215
|
+
expect(parameter).to be parameter_definition
|
216
|
+
end
|
217
|
+
end
|
196
218
|
end
|
197
219
|
end
|
198
220
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'grape'
|
3
|
+
require 'grape-entity'
|
4
|
+
|
5
|
+
RSpec.describe 'Swaggable::GrapeEntityTranslator' do
|
6
|
+
subject { Swaggable::GrapeEntityTranslator }
|
7
|
+
|
8
|
+
describe '.parameter_from' do
|
9
|
+
let(:generated_parameter) { subject.parameter_from entity }
|
10
|
+
|
11
|
+
let(:entity) do
|
12
|
+
Class.new(Grape::Entity) do
|
13
|
+
def self.name
|
14
|
+
'UserEntity'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns a parameter' do
|
20
|
+
expect(generated_parameter).to be_a Swaggable::ParameterDefinition
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'is body param' do
|
24
|
+
expect(generated_parameter.location).to be :body
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'gives it a name' do
|
28
|
+
expect(generated_parameter.name).to eq 'UserEntity'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'gives a name to the schema too' do
|
32
|
+
expect(generated_parameter.schema.name).to eq 'UserEntity'
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'schema attributes' do
|
36
|
+
let(:generated_attr) { generated_parameter.schema.attributes.first }
|
37
|
+
|
38
|
+
it 'sets the name' do
|
39
|
+
entity.expose :first_name
|
40
|
+
expect(generated_attr.name).to be :first_name
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'sets the type' do
|
44
|
+
entity.expose :first_name, documentation: {type: 'String'}
|
45
|
+
expect(generated_attr.type).to be :string
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'sets the description' do
|
49
|
+
entity.expose :first_name, documentation: {desc: 'First Name'}
|
50
|
+
expect(generated_attr.description).to eq 'First Name'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'sets the required field' do
|
54
|
+
entity.expose :first_name, documentation: {required: true}
|
55
|
+
expect(generated_attr).to be_required
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'defaults the required field to false' do
|
59
|
+
entity.expose :first_name, documentation: {}
|
60
|
+
expect(generated_attr).not_to be_required
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -42,8 +42,12 @@ RSpec.describe 'Integration' do
|
|
42
42
|
end
|
43
43
|
|
44
44
|
context 'dsl' do
|
45
|
-
|
46
|
-
|
45
|
+
let(:rack_app) { Swaggable::RackApp.new(api_definition: api) }
|
46
|
+
|
47
|
+
let(:swagger) { Swaggable::Swagger2Serializer.new.serialize api }
|
48
|
+
|
49
|
+
let :api do
|
50
|
+
Swaggable::ApiDefinition.new do
|
47
51
|
version '1.0'
|
48
52
|
title 'My API'
|
49
53
|
description 'A test API'
|
@@ -51,9 +55,9 @@ RSpec.describe 'Integration' do
|
|
51
55
|
|
52
56
|
endpoints.add_new do
|
53
57
|
path '/users/{id}'
|
54
|
-
verb :
|
55
|
-
description '
|
56
|
-
summary '
|
58
|
+
verb :put
|
59
|
+
description 'Updates an user'
|
60
|
+
summary 'Updates attributes of such user'
|
57
61
|
|
58
62
|
tags.add_new do
|
59
63
|
name 'Users'
|
@@ -68,6 +72,24 @@ RSpec.describe 'Integration' do
|
|
68
72
|
type :boolean # [:string, :number, :integer, :boolean, :array, :file, nil]
|
69
73
|
end
|
70
74
|
|
75
|
+
parameters.add_new do
|
76
|
+
name 'user'
|
77
|
+
description 'The new attributes for the user'
|
78
|
+
location :body
|
79
|
+
required true
|
80
|
+
|
81
|
+
schema do
|
82
|
+
name :user
|
83
|
+
|
84
|
+
attributes do
|
85
|
+
add_new do
|
86
|
+
name :first_name
|
87
|
+
type :string
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
71
93
|
responses.add_new do
|
72
94
|
status 200
|
73
95
|
description 'Success'
|
@@ -82,9 +104,11 @@ RSpec.describe 'Integration' do
|
|
82
104
|
produces << :json
|
83
105
|
end
|
84
106
|
end
|
107
|
+
end
|
85
108
|
|
109
|
+
it 'supports a full description of the API' do
|
86
110
|
expect(api.version).to eq '1.0'
|
87
|
-
expect(api.endpoints.first).to be api.endpoints['
|
111
|
+
expect(api.endpoints.first).to be api.endpoints['PUT /users/{id}']
|
88
112
|
expect(api.endpoints.first.path).to eq '/users/{id}'
|
89
113
|
expect(api.endpoints.first.tags.first).to be api.endpoints.first.tags['Users']
|
90
114
|
expect(api.endpoints.first.tags.first.name).to eq 'Users'
|
@@ -95,6 +119,26 @@ RSpec.describe 'Integration' do
|
|
95
119
|
expect(api.endpoints.first.consumes).to eq [:json]
|
96
120
|
expect(api.endpoints.first.produces).to eq [:json]
|
97
121
|
end
|
122
|
+
|
123
|
+
it 'renders proper swagger JSON' do
|
124
|
+
param_schema = swagger[:paths]["/users/{id}"][:put][:parameters].detect{|p| p[:name] == 'user' }[:schema]
|
125
|
+
|
126
|
+
expected_param_schema = {
|
127
|
+
type: 'object',
|
128
|
+
properties: {
|
129
|
+
first_name: {
|
130
|
+
type: :string,
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
expect(param_schema).to eq({:$ref => "#/definitions/user"})
|
136
|
+
expect(swagger[:definitions][:user]).to eq expected_param_schema
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'validates' do
|
140
|
+
expect(rack_app.validate).to be_empty
|
141
|
+
end
|
98
142
|
end
|
99
143
|
end
|
100
144
|
|