From 5d78711a7bf041c9b329e76595d0955c7bffd43b Mon Sep 17 00:00:00 2001 From: Muhammad Nawzad Date: Wed, 11 Sep 2024 14:10:52 +0300 Subject: [PATCH 1/4] Allows headers to be passed optionally --- lib/rspec/openapi/extractors/hanami.rb | 3 ++- lib/rspec/openapi/extractors/rack.rb | 3 ++- lib/rspec/openapi/extractors/rails.rb | 3 ++- lib/rspec/openapi/record.rb | 1 + lib/rspec/openapi/record_builder.rb | 3 ++- lib/rspec/openapi/schema_builder.rb | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/rspec/openapi/extractors/hanami.rb b/lib/rspec/openapi/extractors/hanami.rb index 3f1155dc..b100ae84 100644 --- a/lib/rspec/openapi/extractors/hanami.rb +++ b/lib/rspec/openapi/extractors/hanami.rb @@ -61,6 +61,7 @@ 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_headers = metadata[:optional_headers] || [] security = metadata[:security] description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example) deprecated = metadata[:deprecated] @@ -76,7 +77,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_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..bae1f6a7 100644 --- a/lib/rspec/openapi/extractors/rack.rb +++ b/lib/rspec/openapi/extractors/rack.rb @@ -11,13 +11,14 @@ 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_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_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..0ad829cb 100644 --- a/lib/rspec/openapi/extractors/rails.rb +++ b/lib/rspec/openapi/extractors/rails.rb @@ -21,6 +21,7 @@ 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_headers = metadata[:optional_headers] || [] security = metadata[:security] description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example) deprecated = metadata[:deprecated] @@ -34,7 +35,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_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..7fe7184e 100644 --- a/lib/rspec/openapi/record.rb +++ b/lib/rspec/openapi/record.rb @@ -9,6 +9,7 @@ :required_request_params, # @param [Array] - ["param1", "param2"] :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..fad429fd 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_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,7 @@ def build(context, example:, extractor:) query_params: request.query_parameters, request_params: raw_request_params(request), required_request_params: required_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..46583f46 100644 --- a/lib/rspec/openapi/schema_builder.rb +++ b/lib/rspec/openapi/schema_builder.rb @@ -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 From 5b2850797f68fd7f8e50135d4cbeab3ef6f7c2b4 Mon Sep 17 00:00:00 2001 From: Muhammad Nawzad Date: Wed, 11 Sep 2024 14:25:08 +0300 Subject: [PATCH 2/4] Fixes YAML parsing issue when the file exists but empty --- lib/rspec/openapi/schema_file.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 From 07fd7b9065ff70a51463f5e7e010ae15bce43c36 Mon Sep 17 00:00:00 2001 From: Muhammad Nawzad Date: Thu, 26 Sep 2024 16:08:44 +0300 Subject: [PATCH 3/4] Allows optional request parameters --- lib/rspec/openapi/extractors/hanami.rb | 3 ++- lib/rspec/openapi/extractors/rack.rb | 3 ++- lib/rspec/openapi/extractors/rails.rb | 3 ++- lib/rspec/openapi/record.rb | 1 + lib/rspec/openapi/record_builder.rb | 3 ++- lib/rspec/openapi/schema_builder.rb | 14 +++++++------- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/rspec/openapi/extractors/hanami.rb b/lib/rspec/openapi/extractors/hanami.rb index b100ae84..5c88e804 100644 --- a/lib/rspec/openapi/extractors/hanami.rb +++ b/lib/rspec/openapi/extractors/hanami.rb @@ -61,6 +61,7 @@ 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) @@ -77,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, optional_headers, 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 bae1f6a7..72021d22 100644 --- a/lib/rspec/openapi/extractors/rack.rb +++ b/lib/rspec/openapi/extractors/rack.rb @@ -11,6 +11,7 @@ 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) @@ -18,7 +19,7 @@ def request_attributes(request, example) raw_path_params = request.path_parameters path = request.path summary ||= "#{request.method} #{path}" - [path, summary, tags, operation_id, required_request_params, optional_headers, 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 0ad829cb..c129112d 100644 --- a/lib/rspec/openapi/extractors/rails.rb +++ b/lib/rspec/openapi/extractors/rails.rb @@ -21,6 +21,7 @@ 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) @@ -35,7 +36,7 @@ def request_attributes(request, example) summary ||= "#{request.method} #{path}" - [path, summary, tags, operation_id, required_request_params, optional_headers, 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 7fe7184e..9b6783a5 100644 --- a/lib/rspec/openapi/record.rb +++ b/lib/rspec/openapi/record.rb @@ -7,6 +7,7 @@ :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"] diff --git a/lib/rspec/openapi/record_builder.rb b/lib/rspec/openapi/record_builder.rb index fad429fd..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, optional_headers, 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,7 @@ 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, diff --git a/lib/rspec/openapi/schema_builder.rb b/lib/rspec/openapi/schema_builder.rb index 46583f46..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 @@ -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 From 04401bf8918a7289ba7811d68d9eeb7caec20c42 Mon Sep 17 00:00:00 2001 From: Muhammad Nawzad Date: Thu, 26 Sep 2024 16:22:17 +0300 Subject: [PATCH 4/4] Adds optionality record fields to the readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) 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 # ...