Skip to content

Commit 090fecb

Browse files
committed
feat: add OAS 3.2 additionalOperations support
Process operations defined under path_item.additionalOperations (OAS 3.2.0 field for non-standard HTTP methods like COPY, LINK). Extract register_operation helper from the REQUEST_METHODS loop to share logic between standard methods and additionalOperations. Method keys from additionalOperations are downcased to match the router's internal UPPERCASE convention (router.route_at upcases). 3 tests: COPY method routed, GET still works, undefined LINK rejected. 570 examples, 0 failures, 100% line + branch coverage. Authored by: Aaron Lippold<lippold@gmail.com>
1 parent b509001 commit 090fecb

2 files changed

Lines changed: 75 additions & 22 deletions

File tree

lib/openapi_first/builder.rb

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,33 +66,42 @@ def router # rubocop:disable Metrics/MethodLength
6666
path_parameters = path_item_object['parameters'] || []
6767
path_item_object.resolved.keys.intersection(REQUEST_METHODS).map do |request_method|
6868
operation_object = path_item_object[request_method]
69-
operation_parameters = operation_object['parameters'] || []
70-
parameters = parse_parameters(operation_parameters.chain(path_parameters))
71-
72-
build_requests(path:, request_method:, operation_object:,
73-
parameters:).each do |request|
74-
router.add_request(
75-
request,
76-
request_method:,
77-
path:,
78-
content_type: request.content_type,
79-
allow_empty_content: request.allow_empty_content?
80-
)
81-
build_responses(request:, responses: operation_object['responses']).each do |response|
82-
router.add_response(
83-
response,
84-
request_method:,
85-
path:,
86-
status: response.status,
87-
response_content_type: response.content_type
88-
)
89-
end
90-
end
69+
register_operation(router, path:, request_method:, operation_object:, path_parameters:)
70+
end
71+
72+
path_item_object['additionalOperations']&.each do |request_method, operation_object|
73+
register_operation(router, path:, request_method: request_method.downcase,
74+
operation_object:, path_parameters:)
9175
end
9276
end
9377
router
9478
end
9579

80+
def register_operation(router, path:, request_method:, operation_object:, path_parameters:)
81+
operation_parameters = operation_object['parameters'] || []
82+
parameters = parse_parameters(operation_parameters.chain(path_parameters))
83+
84+
build_requests(path:, request_method:, operation_object:,
85+
parameters:).each do |request|
86+
router.add_request(
87+
request,
88+
request_method:,
89+
path:,
90+
content_type: request.content_type,
91+
allow_empty_content: request.allow_empty_content?
92+
)
93+
build_responses(request:, responses: operation_object['responses']).each do |response|
94+
router.add_response(
95+
response,
96+
request_method:,
97+
path:,
98+
status: response.status,
99+
response_content_type: response.content_type
100+
)
101+
end
102+
end
103+
end
104+
96105
def parse_parameters(parameters)
97106
grouped_parameters = group_parameters(parameters)
98107
ParsedParameters.new(

spec/definition_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,50 @@ def build_request(path, method: 'GET')
4949
end
5050
end
5151

52+
describe 'OAS 3.2 additionalOperations' do
53+
let(:definition) do
54+
OpenapiFirst.parse({
55+
'openapi' => '3.2.0',
56+
'info' => { 'title' => 'Test', 'version' => '1.0' },
57+
'paths' => {
58+
'/files/{id}' => {
59+
'get' => {
60+
'operationId' => 'getFile',
61+
'responses' => { '200' => { 'description' => 'OK' } }
62+
},
63+
'additionalOperations' => {
64+
'COPY' => {
65+
'operationId' => 'copyFile',
66+
'responses' => { '200' => { 'description' => 'Copied' } }
67+
}
68+
}
69+
}
70+
}
71+
})
72+
end
73+
74+
it 'routes requests using non-standard HTTP methods from additionalOperations' do
75+
request = build_request('/files/123', method: 'COPY')
76+
validated = definition.validate_request(request)
77+
expect(validated.error).to be_nil
78+
expect(validated.operation_id).to eq('copyFile')
79+
end
80+
81+
it 'does not break standard method routing alongside additionalOperations' do
82+
request = build_request('/files/123', method: 'GET')
83+
validated = definition.validate_request(request)
84+
expect(validated.error).to be_nil
85+
expect(validated.operation_id).to eq('getFile')
86+
end
87+
88+
it 'returns method_not_allowed for undefined additional methods' do
89+
request = build_request('/files/123', method: 'LINK')
90+
validated = definition.validate_request(request)
91+
expect(validated.error).not_to be_nil
92+
expect(validated.error.type).to eq(:method_not_allowed)
93+
end
94+
end
95+
5296
describe '#title' do
5397
it 'returns the title from info.title' do
5498
definition = OpenapiFirst.parse({

0 commit comments

Comments
 (0)