diff --git a/README.md b/README.md index fccdd2e4..f19745b0 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,8 @@ Some examples' attributes can be overwritten via RSpec metadata options. Example description: 'list all posts ordered by pub_date', tags: %w[v1 posts], required_request_params: %w[limit], + optional_request_params: %w[data optional_address], # request body parameters that will NOT be set as required + optional_headers: %w[Accept-Languages rowsPerPage], # request header params that can be optional security: [{"MyToken" => []}], } do # ... diff --git a/lib/rspec/openapi/extractors/hanami.rb b/lib/rspec/openapi/extractors/hanami.rb index 3f1155dc..5c88e804 100644 --- a/lib/rspec/openapi/extractors/hanami.rb +++ b/lib/rspec/openapi/extractors/hanami.rb @@ -61,6 +61,8 @@ def request_attributes(request, example) tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example) operation_id = metadata[:operation_id] required_request_params = metadata[:required_request_params] || [] + optional_request_params = metadata[:optional_request_params] || [] + optional_headers = metadata[:optional_headers] || [] security = metadata[:security] description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example) deprecated = metadata[:deprecated] @@ -76,7 +78,7 @@ def request_attributes(request, example) raw_path_params = raw_path_params.slice(*(raw_path_params.keys - RSpec::OpenAPI.ignored_path_params)) - [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated] + [path, summary, tags, operation_id, required_request_params, optional_request_params, optional_headers, raw_path_params, description, security, deprecated] end # @param [RSpec::ExampleGroups::*] context diff --git a/lib/rspec/openapi/extractors/rack.rb b/lib/rspec/openapi/extractors/rack.rb index 21b9752a..72021d22 100644 --- a/lib/rspec/openapi/extractors/rack.rb +++ b/lib/rspec/openapi/extractors/rack.rb @@ -11,13 +11,15 @@ def request_attributes(request, example) tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example) operation_id = metadata[:operation_id] required_request_params = metadata[:required_request_params] || [] + optional_request_params = metadata[:optional_request_params] || [] + optional_headers = metadata[:optional_headers] || [] security = metadata[:security] description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example) deprecated = metadata[:deprecated] raw_path_params = request.path_parameters path = request.path summary ||= "#{request.method} #{path}" - [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated] + [path, summary, tags, operation_id, required_request_params, optional_request_params, optional_headers, raw_path_params, description, security, deprecated] end # @param [RSpec::ExampleGroups::*] context diff --git a/lib/rspec/openapi/extractors/rails.rb b/lib/rspec/openapi/extractors/rails.rb index b5d1309f..c129112d 100644 --- a/lib/rspec/openapi/extractors/rails.rb +++ b/lib/rspec/openapi/extractors/rails.rb @@ -21,6 +21,8 @@ def request_attributes(request, example) tags = metadata[:tags] || RSpec::OpenAPI.tags_builder.call(example) operation_id = metadata[:operation_id] required_request_params = metadata[:required_request_params] || [] + optional_request_params = metadata[:optional_request_params] || [] + optional_headers = metadata[:optional_headers] || [] security = metadata[:security] description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example) deprecated = metadata[:deprecated] @@ -34,7 +36,7 @@ def request_attributes(request, example) summary ||= "#{request.method} #{path}" - [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated] + [path, summary, tags, operation_id, required_request_params, optional_request_params, optional_headers, raw_path_params, description, security, deprecated] end # @param [RSpec::ExampleGroups::*] context diff --git a/lib/rspec/openapi/record.rb b/lib/rspec/openapi/record.rb index d336fdcc..9b6783a5 100644 --- a/lib/rspec/openapi/record.rb +++ b/lib/rspec/openapi/record.rb @@ -7,8 +7,10 @@ :query_params, # @param [Hash] - {:query=>"string"} :request_params, # @param [Hash] - {:request=>"body"} :required_request_params, # @param [Array] - ["param1", "param2"] + :optional_request_params, # @param [Array] - ["param3", "param4"] :request_content_type, # @param [String] - "application/json" :request_headers, # @param [Array] - [["header_key1", "header_value1"], ["header_key2", "header_value2"]] + :optional_headers, # @param [Array] - ["header1", "header2"] :summary, # @param [String] - "v1/statuses #show" :tags, # @param [Array] - ["Status"] :operation_id, # @param [String] - "request-1234" diff --git a/lib/rspec/openapi/record_builder.rb b/lib/rspec/openapi/record_builder.rb index 32adf97d..b65c9807 100644 --- a/lib/rspec/openapi/record_builder.rb +++ b/lib/rspec/openapi/record_builder.rb @@ -11,7 +11,7 @@ def build(context, example:, extractor:) request, response = extractor.request_response(context) return if request.nil? - path, summary, tags, operation_id, required_request_params, raw_path_params, description, security, deprecated = + path, summary, tags, operation_id, required_request_params, optional_request_params, optional_headers, raw_path_params, description, security, deprecated = extractor.request_attributes(request, example) return if RSpec::OpenAPI.ignored_paths.any? { |ignored_path| path.match?(ignored_path) } @@ -25,6 +25,8 @@ def build(context, example:, extractor:) query_params: request.query_parameters, request_params: raw_request_params(request), required_request_params: required_request_params, + optional_request_params: optional_request_params, + optional_headers: optional_headers, request_content_type: request.media_type, request_headers: request_headers, summary: summary, diff --git a/lib/rspec/openapi/schema_builder.rb b/lib/rspec/openapi/schema_builder.rb index 49b52728..62abb1b6 100644 --- a/lib/rspec/openapi/schema_builder.rb +++ b/lib/rspec/openapi/schema_builder.rb @@ -48,8 +48,8 @@ def build(record) private - def enrich_with_required_keys(obj) - obj[:required] = obj[:properties]&.keys || [] + def enrich_with_required_keys(obj, optional_request_params) + obj[:required] = (obj[:properties]&.keys - optional_request_params) || [] obj end @@ -88,7 +88,7 @@ def build_parameters(record) { name: build_parameter_name(key, value), in: 'header', - required: true, + required: record.optional_headers.exclude?(key), schema: build_property(try_cast(value)), example: (try_cast(value) if example_enabled?), }.compact @@ -130,14 +130,14 @@ def build_request_body(record) { content: { normalize_content_type(record.request_content_type) => { - schema: build_property(record.request_params), + schema: build_property(record.request_params, optional_request_params: record.optional_request_params), example: (build_example(record.request_params) if example_enabled?), }.compact, }, } end - def build_property(value, disposition: nil) + def build_property(value, disposition: nil, optional_request_params: []) property = build_type(value, disposition) case value @@ -145,15 +145,15 @@ def build_property(value, disposition: nil) property[:items] = if value.empty? {} # unknown else - build_property(value.first) + build_property(value.first, optional_request_params: optional_request_params) end when Hash property[:properties] = {}.tap do |properties| value.each do |key, v| - properties[key] = build_property(v) + properties[key] = build_property(v, optional_request_params: optional_request_params) end end - property = enrich_with_required_keys(property) + property = enrich_with_required_keys(property, optional_request_params) end property end diff --git a/lib/rspec/openapi/schema_file.rb b/lib/rspec/openapi/schema_file.rb index 53f14f9a..8c1f62e6 100644 --- a/lib/rspec/openapi/schema_file.rb +++ b/lib/rspec/openapi/schema_file.rb @@ -24,7 +24,11 @@ def edit(&block) def read return {} unless File.exist?(@path) - RSpec::OpenAPI::KeyTransformer.symbolize(YAML.safe_load(File.read(@path))) # this can also parse JSON + content = YAML.safe_load(File.read(@path)) # This can also parse JSON + + return {} if content.nil? + + RSpec::OpenAPI::KeyTransformer.symbolize(content) end # @param [Hash] spec