swaggable 0.4.0 → 0.5.1
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 -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
|
|