Skip to content

Commit 067b7f5

Browse files
authored
Set "required" automatically for Request Body (#95)
* required-keys on requestBody * Check invalid keys are gone * Preserve properties that appears in all test cases * Check the generated component contains "required" * Test nested object * Support nested object in required body * refactor
1 parent a8d0293 commit 067b7f5

File tree

8 files changed

+84
-26
lines changed

8 files changed

+84
-26
lines changed

lib/rspec/openapi/schema_builder.rb

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ def build(record)
3939

4040
private
4141

42+
def enrich_with_required_keys(obj)
43+
obj[:required] = obj[:properties]&.keys
44+
obj
45+
end
46+
4247
def response_example(record, disposition:)
4348
return nil if !example_enabled? || disposition
4449

@@ -114,29 +119,31 @@ def build_request_body(record)
114119
{
115120
content: {
116121
normalize_content_type(record.request_content_type) => {
117-
schema: build_property(record.request_params),
122+
schema: build_property(record.request_params, set_required: true),
118123
example: (build_example(record.request_params) if example_enabled?),
119124
}.compact,
120125
},
121126
}
122127
end
123128

124-
def build_property(value, disposition: nil)
129+
# TODO: Remove set_required when implementing "required" to all places
130+
def build_property(value, disposition: nil, set_required: false)
125131
property = build_type(value, disposition)
126132

127133
case value
128134
when Array
129135
if value.empty?
130136
property[:items] = {} # unknown
131137
else
132-
property[:items] = build_property(value.first)
138+
property[:items] = build_property(value.first, set_required: set_required)
133139
end
134140
when Hash
135141
property[:properties] = {}.tap do |properties|
136142
value.each do |key, v|
137-
properties[key] = build_property(v)
143+
properties[key] = build_property(v, set_required: set_required)
138144
end
139145
end
146+
property = enrich_with_required_keys(property) if set_required
140147
end
141148
property
142149
end

lib/rspec/openapi/schema_merger.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ def merge_schema!(base, spec)
3737
if key == 'parameters'
3838
base[key] = value | base[key]
3939
base[key].uniq! { |param| param.slice('name', 'in') }
40+
elsif key == 'required'
41+
# Preserve properties that appears in all test cases
42+
base[key] = value & base[key]
4043
else
44+
# last one wins
4145
base[key] = value
4246
end
4347
else

spec/rails/app/controllers/users_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ def create
44
name: params[:name],
55
relations: {
66
avatar: {
7-
url: params[:avatar_url],
7+
url: params[:avatar_url] || 'https://example.com/avatar.png',
88
},
99
pets: params[:pets] || [],
1010
},

spec/rails/doc/openapi.json

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,12 @@
189189
"database_id": {
190190
"type": "integer"
191191
}
192-
}
192+
},
193+
"required": [
194+
"name",
195+
"description",
196+
"database_id"
197+
]
193198
},
194199
"example": {
195200
"name": "k0kubun",
@@ -407,9 +412,16 @@
407412
"caption": {
408413
"type": "string"
409414
}
410-
}
415+
},
416+
"required": [
417+
"image",
418+
"caption"
419+
]
411420
}
412-
}
421+
},
422+
"required": [
423+
"nested"
424+
]
413425
},
414426
"example": {
415427
"nested": {
@@ -569,7 +581,10 @@
569581
"no_content": {
570582
"type": "string"
571583
}
572-
}
584+
},
585+
"required": [
586+
"no_content"
587+
]
573588
},
574589
"example": {
575590
"no_content": "true"

spec/rails/doc/smart/expected.yaml

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,13 @@ paths:
143143
content:
144144
application/json:
145145
schema:
146-
type: object
147-
properties:
148-
name:
149-
type: string
150-
avatar_url:
151-
type: string
146+
"$ref": "#/components/schemas/PostUsersRequest"
152147
example:
153148
name: alice
154149
avatar_url: "https://example.com/avatar.png"
150+
foo:
151+
bar:
152+
baz: 42
155153
responses:
156154
'201':
157155
description: returns an user
@@ -266,3 +264,24 @@ components:
266264
type: string
267265
age:
268266
type: integer
267+
PostUsersRequest:
268+
type: object
269+
properties:
270+
name:
271+
type: string
272+
avatar_url:
273+
type: string
274+
foo:
275+
type: object
276+
properties:
277+
bar:
278+
type: object
279+
properties:
280+
baz:
281+
type: integer
282+
required:
283+
- baz
284+
required:
285+
- bar
286+
required:
287+
- name

spec/rails/doc/smart/openapi.yaml

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,16 +212,7 @@ paths:
212212
content:
213213
application/json:
214214
schema:
215-
type: object
216-
properties:
217-
name:
218-
type: string
219-
avatar:
220-
"$ref": "#/components/schemas/Avatar"
221-
pets:
222-
type: array
223-
items:
224-
"$ref": "#/components/schemas/Pet"
215+
"$ref": "#/components/schemas/PostUsersRequest"
225216
responses:
226217
'201':
227218
description: returns a user

spec/requests/rails_smart_merge_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,25 @@
6161

6262
RSpec.describe 'Users', type: :request do
6363
describe '#create' do
64+
it 'accepts missing avatar_url' do
65+
post '/users', headers: { authorization: 'k0kubun', 'Content-Type': 'application/json' }, params: {
66+
name: 'alice',
67+
}.to_json
68+
expect(response.status).to eq(201)
69+
end
70+
71+
it 'accepts nested object' do
72+
post '/users', headers: { authorization: 'k0kubun', 'Content-Type': 'application/json' }, params: {
73+
name: 'alice',
74+
foo: {
75+
bar: {
76+
baz: 42,
77+
},
78+
},
79+
}.to_json
80+
expect(response.status).to eq(201)
81+
end
82+
6483
it 'returns an user' do
6584
post '/users', headers: { authorization: 'k0kubun', 'Content-Type': 'application/json' }, params: {
6685
name: 'alice',

spec/roda/doc/openapi.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
"id": {
2121
"type": "integer"
2222
}
23-
}
23+
},
24+
"required": [
25+
"id"
26+
]
2427
},
2528
"example": {
2629
"id": 1

0 commit comments

Comments
 (0)