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.
@@ -26,5 +26,9 @@ module Swaggable
26
26
  def validate!
27
27
  serializer.validate! api_definition
28
28
  end
29
+
30
+ def validate
31
+ serializer.validate api_definition
32
+ end
29
33
  end
30
34
  end
@@ -2,15 +2,10 @@ require 'forwarding_dsl'
2
2
 
3
3
  module Swaggable
4
4
  class ResponseDefinition
5
- include ForwardingDsl::Getsetter
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
@@ -7,6 +7,11 @@ module Swaggable
7
7
  JSON::Validator.validate!(schema, swagger)
8
8
  end
9
9
 
10
+ def self.validate swagger
11
+ preload_draft4
12
+ JSON::Validator.fully_validate(schema, swagger, :errors_as_objects => true)
13
+ end
14
+
10
15
  private
11
16
 
12
17
  def self.schema
@@ -2,18 +2,13 @@ require 'forwarding_dsl'
2
2
 
3
3
  module Swaggable
4
4
  class TagDefinition
5
- include ForwardingDsl::Getsetter
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
@@ -1,3 +1,3 @@
1
1
  module Swaggable
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.1'
3
3
  end
@@ -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
- it 'supports a full description of the API' do
46
- api = Swaggable::ApiDefinition.new do
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 :get
55
- description 'Shows an user'
56
- summary 'Returns the JSON representation of such user'
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['GET /users/{id}']
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