diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e1b73b..d4b1859 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * `ServerNameBefore32`: detect Server Object `name` usage in pre-3.2 documents (3.2 addition) * `ExampleValueFieldsBefore32`: detect Example Object `dataValue` / `serializedValue` usage in pre-3.2 documents (3.2 additions) * validate a value against a schema without `type`: keywords such as `properties`, `required`, `items`, `maxLength`, `enum` and `minimum` now apply when the value has the type the keyword is for, as in JSON Schema. They were all skipped before, so a value that breaks them may now fail validation +* accept a file object for array-form `type` with `format: binary` (3.1) when `coerce_value` is off, the same way as a single `type: string` ## 2.5.0 (2026-10-21) * add OpenAPI 3.2 `SpecValidator` rules diff --git a/lib/openapi_parser/schema_validator.rb b/lib/openapi_parser/schema_validator.rb index 814d4ec..69e8100 100644 --- a/lib/openapi_parser/schema_validator.rb +++ b/lib/openapi_parser/schema_validator.rb @@ -124,6 +124,11 @@ def validator(value, schema) return one_of_validator if schema.one_of return nil_validator if value.nil? + # 3.1: `format: binary` with `string` in an array-form `type` accepts a + # non-String object such as an uploaded file, the same way as a single + # `type: string`. The string validator skips the check for `binary`. + return string_validator if binary_string_in_type_array?(value, schema) + # 3.1: pick the type from an Array whose primitive class matches the # value, then dispatch as if it were a single-type schema. If nothing # matches we still pick a candidate so the relevant validator emits @@ -169,6 +174,14 @@ def validator_for_type(type) end end + # 3.1: a non-String value for an array-form `type` that lists `string` + # with `format: binary`, such as an uploaded file object. + def binary_string_in_type_array?(value, schema) + return false if value.is_a?(String) + + schema.format == 'binary' && schema.type.is_a?(Array) && schema.type.include?('string') + end + # 3.1: when coercing, a value whose class matches no listed type is tried # against each listed type, the same way a single `type` coerces it. # Request params arrive as Strings ("5" for `type: [integer, "null"]`), diff --git a/spec/openapi_parser/schema_validator_spec.rb b/spec/openapi_parser/schema_validator_spec.rb index 94ef51c..87726ae 100644 --- a/spec/openapi_parser/schema_validator_spec.rb +++ b/spec/openapi_parser/schema_validator_spec.rb @@ -1194,4 +1194,56 @@ class ValidatableTest end end end + + describe 'array-form type with format: binary (3.1)' do + let(:options) { ::OpenAPIParser::SchemaValidator::Options.new } + let(:target) { { 'type' => ['string', 'null'], 'format' => 'binary' } } + let(:schema) do + raw = { + 'openapi' => '3.1.0', + 'info' => { 'title' => 'test', 'version' => '1.0' }, + 'paths' => {}, + 'components' => { 'schemas' => { 'Target' => target } }, + } + OpenAPIParser.parse(raw, strict_reference_validation: false).components.schemas['Target'] + end + + context 'when an arbitrary object except String is given' do + it 'passes validation the same way as a single string type' do + value = ['b', 'i', 'n', 'a', 'r', 'y'] + expect(OpenAPIParser::SchemaValidator.validate(value, schema, options)).to eq value + end + end + + context 'when a String is given' do + it 'passes validation' do + expect(OpenAPIParser::SchemaValidator.validate('binary', schema, options)).to eq 'binary' + end + end + + context 'when nil is given' do + it 'passes validation' do + expect(OpenAPIParser::SchemaValidator.validate(nil, schema, options)).to eq nil + end + end + + context 'when string is not listed' do + let(:target) { { 'type' => ['integer', 'null'], 'format' => 'binary' } } + + it 'raises a type-mismatch error' do + expect do + OpenAPIParser::SchemaValidator.validate(['b', 'i', 'n', 'a', 'r', 'y'], schema, options) + end.to raise_error(OpenAPIParser::ValidateError, /expected \["integer", "null"\]/) + end + end + + context 'when coerce_value is on' do + let(:options) { ::OpenAPIParser::SchemaValidator::Options.new(coerce_value: true) } + + it 'passes validation' do + value = ['b', 'i', 'n', 'a', 'r', 'y'] + expect(OpenAPIParser::SchemaValidator.validate(value, schema, options)).to eq value + end + end + end end