swaggable 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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