From 4ae8cc00466dfc1898d10dd3ebab9131c1bc5118 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 12:32:12 +0100 Subject: [PATCH 01/47] Remove maximum and minimum in NumericBuilder --- src/OpenAPI/Builder/Numeric.php | 17 ++++++++++----- src/OpenAPI/Specification/APISchema.php | 2 +- src/OpenAPI/Specification/Numeric.php | 8 ------- tests/OpenAPI/Specification/NumericTest.php | 24 --------------------- 4 files changed, 13 insertions(+), 38 deletions(-) diff --git a/src/OpenAPI/Builder/Numeric.php b/src/OpenAPI/Builder/Numeric.php index 287eb364..ef9c4813 100644 --- a/src/OpenAPI/Builder/Numeric.php +++ b/src/OpenAPI/Builder/Numeric.php @@ -91,13 +91,20 @@ private function handleInteger(OpenAPI\Specification\Numeric $specification): ar private function handleNumericConstraints(OpenAPI\Specification\Numeric $specification): array { $chain = []; - - if ($specification->maximum !== null) { - $chain[] = new Maximum($specification->maximum, $specification->exclusiveMaximum); + // if keywords->maximum !== null + // keywords->maximum->limit + if ($specification->keywords->maximum !== null) { + $chain[] = new Maximum( + $specification->keywords->maximum->limit, + $specification->keywords->maximum->exclusive, + ); } - if ($specification->minimum !== null) { - $chain[] = new Minimum($specification->minimum, $specification->exclusiveMinimum); + if ($specification->keywords->minimum !== null) { + $chain[] = new Minimum( + $specification->keywords->minimum->limit, + $specification->keywords->minimum->exclusive, + ); } if ($specification->multipleOf !== null) { diff --git a/src/OpenAPI/Specification/APISchema.php b/src/OpenAPI/Specification/APISchema.php index 4d72c1c3..8a597479 100644 --- a/src/OpenAPI/Specification/APISchema.php +++ b/src/OpenAPI/Specification/APISchema.php @@ -15,7 +15,7 @@ abstract class APISchema implements Specification public function __construct( public readonly string $fieldName, - V30\Keywords | V31\Keywords $keywords + public V30\Keywords | V31\Keywords $keywords ) { $this->enum = isset($keywords->enum) ? array_map(fn($e) => $e->value, $keywords->enum) : diff --git a/src/OpenAPI/Specification/Numeric.php b/src/OpenAPI/Specification/Numeric.php index ad227985..1ed5259b 100644 --- a/src/OpenAPI/Specification/Numeric.php +++ b/src/OpenAPI/Specification/Numeric.php @@ -10,10 +10,6 @@ class Numeric extends APISchema { - public readonly bool $exclusiveMaximum; - public readonly bool $exclusiveMinimum; - public readonly float | int | null $maximum; - public readonly float | int | null $minimum; public readonly float | int | null $multipleOf; public readonly string $type; @@ -36,10 +32,6 @@ public function __construct( ); } - $this->exclusiveMaximum = $keywords->maximum->exclusive ?? false; - $this->exclusiveMinimum = $keywords->minimum->exclusive ?? false; - $this->maximum = $keywords->maximum?->limit; - $this->minimum = $keywords->minimum?->limit; $this->multipleOf = $keywords->multipleOf; parent::__construct($fieldName, $keywords); diff --git a/tests/OpenAPI/Specification/NumericTest.php b/tests/OpenAPI/Specification/NumericTest.php index f1e10f85..07b873db 100644 --- a/tests/OpenAPI/Specification/NumericTest.php +++ b/tests/OpenAPI/Specification/NumericTest.php @@ -53,10 +53,6 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'number')), [ 'type' => 'number', - 'maximum' => null, - 'minimum' => null, - 'exclusiveMaximum' => false, - 'exclusiveMinimum' => false, 'multipleOf' => null, 'enum' => null, ], @@ -65,10 +61,6 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'integer')), [ 'type' => 'integer', - 'maximum' => null, - 'minimum' => null, - 'exclusiveMaximum' => false, - 'exclusiveMinimum' => false, 'multipleOf' => null, 'enum' => null, ], @@ -78,18 +70,10 @@ public static function dataSetsToConstruct(): array type: 'number', enum: [new Value(3), new Value(9)], multipleOf: 3, - exclusiveMaximum: true, - exclusiveMinimum: true, - maximum: 10, - minimum: 1, format: 'float', )), [ 'type' => 'number', - 'maximum' => 10, - 'minimum' => 1, - 'exclusiveMaximum' => true, - 'exclusiveMinimum' => true, 'multipleOf' => 3, 'enum' => [3, 9], 'format' => 'float', @@ -100,18 +84,10 @@ enum: [new Value(3), new Value(9)], type: 'integer', enum: [new Value(9)], multipleOf: 3, - exclusiveMaximum: true, - exclusiveMinimum: true, - maximum: 10, - minimum: 1, format: 'square of 3', )), [ 'type' => 'integer', - 'maximum' => 10, - 'minimum' => 1, - 'exclusiveMaximum' => true, - 'exclusiveMinimum' => true, 'multipleOf' => 3, 'enum' => [9], 'format' => 'square of 3', From 59a54007e83da93fbb41203f1543e18d6097f62a Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 12:38:26 +0100 Subject: [PATCH 02/47] Remove multipleOf from Numeric Specification --- src/OpenAPI/Builder/Numeric.php | 4 ++-- src/OpenAPI/Specification/Numeric.php | 3 --- tests/OpenAPI/Specification/NumericTest.php | 5 ----- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/OpenAPI/Builder/Numeric.php b/src/OpenAPI/Builder/Numeric.php index ef9c4813..965a0e5a 100644 --- a/src/OpenAPI/Builder/Numeric.php +++ b/src/OpenAPI/Builder/Numeric.php @@ -107,8 +107,8 @@ private function handleNumericConstraints(OpenAPI\Specification\Numeric $specifi ); } - if ($specification->multipleOf !== null) { - $chain[] = new MultipleOf($specification->multipleOf); + if ($specification->keywords->multipleOf !== null) { + $chain[] = new MultipleOf($specification->keywords->multipleOf); } return $chain; diff --git a/src/OpenAPI/Specification/Numeric.php b/src/OpenAPI/Specification/Numeric.php index 1ed5259b..ee1ef569 100644 --- a/src/OpenAPI/Specification/Numeric.php +++ b/src/OpenAPI/Specification/Numeric.php @@ -10,7 +10,6 @@ class Numeric extends APISchema { - public readonly float | int | null $multipleOf; public readonly string $type; public function __construct( @@ -32,8 +31,6 @@ public function __construct( ); } - $this->multipleOf = $keywords->multipleOf; - parent::__construct($fieldName, $keywords); } } diff --git a/tests/OpenAPI/Specification/NumericTest.php b/tests/OpenAPI/Specification/NumericTest.php index 07b873db..3312c87c 100644 --- a/tests/OpenAPI/Specification/NumericTest.php +++ b/tests/OpenAPI/Specification/NumericTest.php @@ -53,7 +53,6 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'number')), [ 'type' => 'number', - 'multipleOf' => null, 'enum' => null, ], ], @@ -61,7 +60,6 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'integer')), [ 'type' => 'integer', - 'multipleOf' => null, 'enum' => null, ], ], @@ -69,12 +67,10 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'number', enum: [new Value(3), new Value(9)], - multipleOf: 3, format: 'float', )), [ 'type' => 'number', - 'multipleOf' => 3, 'enum' => [3, 9], 'format' => 'float', ], @@ -88,7 +84,6 @@ enum: [new Value(9)], )), [ 'type' => 'integer', - 'multipleOf' => 3, 'enum' => [9], 'format' => 'square of 3', ], From f29fd66dd93795625e289c8d6ce69d07afe5c19e Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 13:06:49 +0100 Subject: [PATCH 03/47] Remove min and max length from Strings specification --- src/OpenAPI/Builder/Strings.php | 11 +++++++++-- src/OpenAPI/Specification/Strings.php | 4 ---- tests/OpenAPI/Specification/StringsTest.php | 6 ------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/OpenAPI/Builder/Strings.php b/src/OpenAPI/Builder/Strings.php index 357e3d1b..b4403cc1 100644 --- a/src/OpenAPI/Builder/Strings.php +++ b/src/OpenAPI/Builder/Strings.php @@ -70,8 +70,15 @@ public function build(Specification $specification): Processor ); } - if ($specification->minLength > 0 || $specification->maxLength !== null) { - $chain[] = new Length($specification->minLength, $specification->maxLength); + if ( + $specification->keywords->maxLength !== null + || ($specification->keywords->minLength !== null + && $specification->keywords->minLength > 0) + ) { + $chain[] = new Length( + $specification->keywords?->minLength ?? 0, + $specification->keywords?->maxLength, + ); } if ($specification->pattern !== null) { diff --git a/src/OpenAPI/Specification/Strings.php b/src/OpenAPI/Specification/Strings.php index dbeed3c2..c32e1c07 100644 --- a/src/OpenAPI/Specification/Strings.php +++ b/src/OpenAPI/Specification/Strings.php @@ -10,8 +10,6 @@ class Strings extends APISchema { - public readonly ?int $maxLength; - public readonly int $minLength; public readonly ?string $pattern; public function __construct( @@ -27,8 +25,6 @@ public function __construct( ); } - $this->maxLength = $keywords->maxLength; - $this->minLength = $keywords->minLength; $this->pattern = $keywords->pattern; parent::__construct($fieldName, $keywords); diff --git a/tests/OpenAPI/Specification/StringsTest.php b/tests/OpenAPI/Specification/StringsTest.php index 83fbe178..40a514aa 100644 --- a/tests/OpenAPI/Specification/StringsTest.php +++ b/tests/OpenAPI/Specification/StringsTest.php @@ -48,8 +48,6 @@ public static function dataSetsToConstruct(): array 'default values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string')), [ - 'maxLength' => null, - 'minLength' => 0, 'pattern' => null, 'enum' => null, 'format' => '', @@ -59,14 +57,10 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'string', enum: [new Value('This is a string'), new Value('So is this')], - maxLength: 20, - minLength: 6, pattern: '.+', format: 'arbitrary', )), [ - 'maxLength' => 20, - 'minLength' => 6, 'pattern' => '.+', 'enum' => ['This is a string', 'So is this'], 'format' => 'arbitrary', From e1cfe87afb1d61ab82800fad892e862582da5bf1 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 13:11:56 +0100 Subject: [PATCH 04/47] Remove pattern from Strings Specification --- src/OpenAPI/Builder/Strings.php | 16 ++++++++-------- src/OpenAPI/Specification/Strings.php | 4 ---- tests/OpenAPI/Specification/StringsTest.php | 3 --- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/OpenAPI/Builder/Strings.php b/src/OpenAPI/Builder/Strings.php index b4403cc1..c645100b 100644 --- a/src/OpenAPI/Builder/Strings.php +++ b/src/OpenAPI/Builder/Strings.php @@ -72,19 +72,19 @@ public function build(Specification $specification): Processor if ( $specification->keywords->maxLength !== null - || ($specification->keywords->minLength !== null - && $specification->keywords->minLength > 0) + || $specification->keywords->minLength > 0 ) { $chain[] = new Length( - $specification->keywords?->minLength ?? 0, - $specification->keywords?->maxLength, + $specification->keywords->minLength, + $specification->keywords->maxLength, ); } - if ($specification->pattern !== null) { - $chain[] = new Regex( - sprintf('#%s#u', str_replace('#', '\#', $specification->pattern)) - ); + if ($specification->keywords->pattern !== null) { + $chain[] = new Regex(sprintf( + '#%s#u', + str_replace('#', '\#', $specification->keywords->pattern), + )); } return new Field($specification->fieldName, ...$chain); diff --git a/src/OpenAPI/Specification/Strings.php b/src/OpenAPI/Specification/Strings.php index c32e1c07..55a1315f 100644 --- a/src/OpenAPI/Specification/Strings.php +++ b/src/OpenAPI/Specification/Strings.php @@ -10,8 +10,6 @@ class Strings extends APISchema { - public readonly ?string $pattern; - public function __construct( string $fieldName, V30\Keywords | V31\Keywords $keywords, @@ -25,8 +23,6 @@ public function __construct( ); } - $this->pattern = $keywords->pattern; - parent::__construct($fieldName, $keywords); } } diff --git a/tests/OpenAPI/Specification/StringsTest.php b/tests/OpenAPI/Specification/StringsTest.php index 40a514aa..4f7ea9f9 100644 --- a/tests/OpenAPI/Specification/StringsTest.php +++ b/tests/OpenAPI/Specification/StringsTest.php @@ -48,7 +48,6 @@ public static function dataSetsToConstruct(): array 'default values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string')), [ - 'pattern' => null, 'enum' => null, 'format' => '', ], @@ -57,11 +56,9 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'string', enum: [new Value('This is a string'), new Value('So is this')], - pattern: '.+', format: 'arbitrary', )), [ - 'pattern' => '.+', 'enum' => ['This is a string', 'So is this'], 'format' => 'arbitrary', ], From 933199f10c011e80ce14508fc6d1a1d3e13b4f9b Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 13:17:57 +0100 Subject: [PATCH 05/47] Remove uniqueItems from Arrays --- src/OpenAPI/Builder/Arrays.php | 2 +- src/OpenAPI/Specification/Arrays.php | 2 -- tests/OpenAPI/Specification/ArraysTest.php | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/OpenAPI/Builder/Arrays.php b/src/OpenAPI/Builder/Arrays.php index 6ab1fcf8..350b47f4 100644 --- a/src/OpenAPI/Builder/Arrays.php +++ b/src/OpenAPI/Builder/Arrays.php @@ -72,7 +72,7 @@ public function build(Specification $specification): Processor $beforeChain[] = new Count($specification->minItems, $specification->maxItems); } - if ($specification->uniqueItems === true) { + if ($specification->keywords->uniqueItems === true) { $beforeChain[] = new Unique(); } diff --git a/src/OpenAPI/Specification/Arrays.php b/src/OpenAPI/Specification/Arrays.php index 1b4ee97e..830aa4a8 100644 --- a/src/OpenAPI/Specification/Arrays.php +++ b/src/OpenAPI/Specification/Arrays.php @@ -13,7 +13,6 @@ class Arrays extends APISchema public readonly V30\Schema | V31\Schema $items; public readonly ?int $maxItems; public readonly int $minItems; - public readonly bool $uniqueItems; public function __construct( string $fieldName, @@ -33,7 +32,6 @@ public function __construct( $this->items = $keywords->items; $this->maxItems = $keywords->maxItems; $this->minItems = $keywords->minItems; - $this->uniqueItems = $keywords->uniqueItems; parent::__construct($fieldName, $keywords); } diff --git a/tests/OpenAPI/Specification/ArraysTest.php b/tests/OpenAPI/Specification/ArraysTest.php index e541792a..52b080b4 100644 --- a/tests/OpenAPI/Specification/ArraysTest.php +++ b/tests/OpenAPI/Specification/ArraysTest.php @@ -54,7 +54,6 @@ public static function dataSetsToConstruct(): array ), 'maxItems' => null, 'minItems' => 0, - 'uniqueItems' => false, 'enum' => null, 'format' => '', ], @@ -65,7 +64,6 @@ public static function dataSetsToConstruct(): array enum: [new Value([1, 2, 3]), new Value([5, 6, 7])], maxItems: 5, minItems: 2, - uniqueItems: true, items: new Partial\Schema(type: 'integer'), format: 'array of ints', )), @@ -76,7 +74,6 @@ enum: [new Value([1, 2, 3]), new Value([5, 6, 7])], ), 'maxItems' => 5, 'minItems' => 2, - 'uniqueItems' => true, 'enum' => [[1, 2, 3], [5, 6, 7]], 'format' => 'array of ints', ], From cdcbe05a1acf8647d8458c6b4b674cd911a93af7 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 13:19:51 +0100 Subject: [PATCH 06/47] Remove min and max properties from Arrays Specification --- src/OpenAPI/Builder/Arrays.php | 10 ++++++++-- src/OpenAPI/Specification/Arrays.php | 4 ---- tests/OpenAPI/Specification/ArraysTest.php | 6 ------ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/OpenAPI/Builder/Arrays.php b/src/OpenAPI/Builder/Arrays.php index 350b47f4..9f69fc62 100644 --- a/src/OpenAPI/Builder/Arrays.php +++ b/src/OpenAPI/Builder/Arrays.php @@ -68,8 +68,14 @@ public function build(Specification $specification): Processor $beforeChain[] = new Contained($specification->enum); } - if ($specification->minItems > 0 || $specification->maxItems !== null) { - $beforeChain[] = new Count($specification->minItems, $specification->maxItems); + if ( + $specification->keywords->minItems > 0 + || $specification->keywords->maxItems !== null + ) { + $beforeChain[] = new Count( + $specification->keywords->minItems, + $specification->keywords->maxItems, + ); } if ($specification->keywords->uniqueItems === true) { diff --git a/src/OpenAPI/Specification/Arrays.php b/src/OpenAPI/Specification/Arrays.php index 830aa4a8..d6c5b14c 100644 --- a/src/OpenAPI/Specification/Arrays.php +++ b/src/OpenAPI/Specification/Arrays.php @@ -11,8 +11,6 @@ class Arrays extends APISchema { public readonly V30\Schema | V31\Schema $items; - public readonly ?int $maxItems; - public readonly int $minItems; public function __construct( string $fieldName, @@ -30,8 +28,6 @@ public function __construct( } $this->items = $keywords->items; - $this->maxItems = $keywords->maxItems; - $this->minItems = $keywords->minItems; parent::__construct($fieldName, $keywords); } diff --git a/tests/OpenAPI/Specification/ArraysTest.php b/tests/OpenAPI/Specification/ArraysTest.php index 52b080b4..7beeae8f 100644 --- a/tests/OpenAPI/Specification/ArraysTest.php +++ b/tests/OpenAPI/Specification/ArraysTest.php @@ -52,8 +52,6 @@ public static function dataSetsToConstruct(): array new Identifier('test', 'items'), true ), - 'maxItems' => null, - 'minItems' => 0, 'enum' => null, 'format' => '', ], @@ -62,8 +60,6 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'array', enum: [new Value([1, 2, 3]), new Value([5, 6, 7])], - maxItems: 5, - minItems: 2, items: new Partial\Schema(type: 'integer'), format: 'array of ints', )), @@ -72,8 +68,6 @@ enum: [new Value([1, 2, 3]), new Value([5, 6, 7])], new Identifier('test', 'items'), new Partial\Schema(type: 'integer'), ), - 'maxItems' => 5, - 'minItems' => 2, 'enum' => [[1, 2, 3], [5, 6, 7]], 'format' => 'array of ints', ], From 1f0df398c45dd4ac1c8b3cec750735889d35c3e2 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 13:21:16 +0100 Subject: [PATCH 07/47] Remove items from Arrays --- src/OpenAPI/Builder/Arrays.php | 2 +- src/OpenAPI/Specification/Arrays.php | 4 ---- tests/OpenAPI/Specification/ArraysTest.php | 9 --------- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/OpenAPI/Builder/Arrays.php b/src/OpenAPI/Builder/Arrays.php index 9f69fc62..37a1a433 100644 --- a/src/OpenAPI/Builder/Arrays.php +++ b/src/OpenAPI/Builder/Arrays.php @@ -88,7 +88,7 @@ public function build(Specification $specification): Processor $specification->fieldName, $beforeSet, $this->fromSchema( - $specification->items, + $specification->keywords->items, '', $specification->convertFromString, ) diff --git a/src/OpenAPI/Specification/Arrays.php b/src/OpenAPI/Specification/Arrays.php index d6c5b14c..8abe84c4 100644 --- a/src/OpenAPI/Specification/Arrays.php +++ b/src/OpenAPI/Specification/Arrays.php @@ -10,8 +10,6 @@ class Arrays extends APISchema { - public readonly V30\Schema | V31\Schema $items; - public function __construct( string $fieldName, V30\Keywords | V31\Keywords $keywords, @@ -27,8 +25,6 @@ public function __construct( ); } - $this->items = $keywords->items; - parent::__construct($fieldName, $keywords); } } diff --git a/tests/OpenAPI/Specification/ArraysTest.php b/tests/OpenAPI/Specification/ArraysTest.php index 7beeae8f..f45ff3d0 100644 --- a/tests/OpenAPI/Specification/ArraysTest.php +++ b/tests/OpenAPI/Specification/ArraysTest.php @@ -48,10 +48,6 @@ public static function dataSetsToConstruct(): array 'default values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'array')), [ - 'items' => new V30\Schema( - new Identifier('test', 'items'), - true - ), 'enum' => null, 'format' => '', ], @@ -60,14 +56,9 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'array', enum: [new Value([1, 2, 3]), new Value([5, 6, 7])], - items: new Partial\Schema(type: 'integer'), format: 'array of ints', )), [ - 'items' => new V30\Schema( - new Identifier('test', 'items'), - new Partial\Schema(type: 'integer'), - ), 'enum' => [[1, 2, 3], [5, 6, 7]], 'format' => 'array of ints', ], From c6fc0e4b56cd6d0275f3964b3f097976315e643e Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 13:21:57 +0100 Subject: [PATCH 08/47] Remove unnecessary parenthesis --- tests/OpenAPI/Specification/ArraysTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/OpenAPI/Specification/ArraysTest.php b/tests/OpenAPI/Specification/ArraysTest.php index f45ff3d0..756cfe79 100644 --- a/tests/OpenAPI/Specification/ArraysTest.php +++ b/tests/OpenAPI/Specification/ArraysTest.php @@ -27,7 +27,7 @@ public function throwsExceptionForMissingType(): void new Arrays( '', - (new V30\Schema(new Identifier('test'), new Partial\Schema()))->value + new V30\Schema(new Identifier('test'), new Partial\Schema())->value ); } @@ -38,7 +38,7 @@ public function throwsExceptionForInvalidType(): void new Arrays( '', - (new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string')))->value + new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string'))->value ); } From 5f5ba3dcd8ff2edbfc5ae7ac4df95b82efbda0ab Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 14:38:53 +0100 Subject: [PATCH 09/47] Remove most keywords from Objects --- src/OpenAPI/Builder/Objects.php | 32 ++++++++++++++------- src/OpenAPI/Specification/Objects.php | 16 ----------- tests/OpenAPI/Specification/ObjectsTest.php | 25 ---------------- 3 files changed, 21 insertions(+), 52 deletions(-) diff --git a/src/OpenAPI/Builder/Objects.php b/src/OpenAPI/Builder/Objects.php index db834685..40c72529 100644 --- a/src/OpenAPI/Builder/Objects.php +++ b/src/OpenAPI/Builder/Objects.php @@ -75,36 +75,46 @@ public function build(Specification $specification): Processor $beforeChain[] = new Contained($specification->enum); } - if (!empty($specification->required)) { - $beforeChain[] = new RequiredFields(...$specification->required); + if (!empty($specification->keywords->required)) { + $beforeChain[] = new RequiredFields( + ...$specification->keywords->required + ); } - if ($specification->additionalProperties->value === false) { - $beforeChain[] = new FixedFields(...array_keys($specification->properties)); + if ($specification->keywords->additionalProperties->value === false) { + $beforeChain[] = new FixedFields( + ...array_keys($specification->keywords->properties) + ); } - if ($specification->minProperties > 0 || isset($specification->maxProperties)) { - $beforeChain[] = new Count($specification->minProperties, $specification->maxProperties); + if ( + $specification->keywords->minProperties > 0 + || $specification->keywords->maxProperties !== null + ) { + $beforeChain[] = new Count( + $specification->keywords->minProperties, + $specification->keywords->maxProperties, + ); } $beforeSet = new BeforeSet(...$beforeChain); $fields = []; - foreach ($specification->properties as $key => $schema) { + foreach ($specification->keywords->properties as $key => $schema) { $fields [] = $this->fromSchema( $schema, $key, - $specification->convertFromString + $specification->convertFromString, ); } - if (!is_bool($specification->additionalProperties->value)) { + if (!is_bool($specification->keywords->additionalProperties->value)) { $fields [] = new DefaultProcessor( $this->fromSchema( - $specification->additionalProperties, + $specification->keywords->additionalProperties, '', - $specification->convertFromString + $specification->convertFromString, ) ); } diff --git a/src/OpenAPI/Specification/Objects.php b/src/OpenAPI/Specification/Objects.php index 0bfff5d5..2383088e 100644 --- a/src/OpenAPI/Specification/Objects.php +++ b/src/OpenAPI/Specification/Objects.php @@ -10,16 +10,6 @@ class Objects extends APISchema { - // @TODO support minProperties and maxProperties - public readonly V30\Schema | V31\Schema $additionalProperties; - /** @var V30\Schema[] | V31\Schema[] */ - public readonly array $properties; - /** @var string[] */ - public readonly array $required; - - public readonly ?int $maxProperties; - public readonly int $minProperties; - public function __construct( string $fieldName, V30\Keywords | V31\Keywords $keywords, @@ -35,12 +25,6 @@ public function __construct( ); } - $this->additionalProperties = $keywords->additionalProperties; - $this->properties = $keywords->properties; - $this->required = $keywords->required; - $this->maxProperties = $keywords->maxProperties; - $this->minProperties = $keywords->minProperties; - parent::__construct($fieldName, $keywords); } } diff --git a/tests/OpenAPI/Specification/ObjectsTest.php b/tests/OpenAPI/Specification/ObjectsTest.php index d8e29055..8854a0dd 100644 --- a/tests/OpenAPI/Specification/ObjectsTest.php +++ b/tests/OpenAPI/Specification/ObjectsTest.php @@ -48,12 +48,6 @@ public static function dataSetsToConstruct(): array 'default values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'object')), [ - 'additionalProperties' => new V30\Schema( - new Identifier('test', 'additionalProperties'), - true, - ), - 'properties' => [], - 'required' => [], 'enum' => null, 'format' => '', ], @@ -61,15 +55,8 @@ public static function dataSetsToConstruct(): array 'additionalProperties assigned false' => [ new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'object', - additionalProperties: false, )), [ - 'additionalProperties' => new V30\Schema( - new Identifier('test', 'additionalProperties'), - false, - ), - 'properties' => [], - 'required' => [], 'enum' => null, 'format' => '', ], @@ -78,21 +65,9 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'object', enum: [new Value(['id' => 5]), new Value(['id' => 10])], - required: ['id'], - properties: ['id' => new Partial\Schema(type: 'integer')], - additionalProperties: new Partial\Schema(type: 'string'), format: 'you cannot say yes', )), [ - 'additionalProperties' => new V30\Schema( - new Identifier('test', 'additionalProperties'), - new Partial\Schema(type: 'string') - ), - 'properties' => ['id' => new V30\Schema( - new Identifier('test', 'properties(id)'), - new Partial\Schema(type: 'integer') - )], - 'required' => ['id'], 'enum' => [['id' => 5], ['id' => 10]], 'format' => 'you cannot say yes', ], From 6b4ad844e6daf2ec80a70ddf33df00b21e8e91ee Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 14:50:11 +0100 Subject: [PATCH 10/47] Remove enum from APISchema --- src/OpenAPI/Builder/Arrays.php | 10 ++++++++-- src/OpenAPI/Builder/Numeric.php | 10 ++++++++-- src/OpenAPI/Builder/Objects.php | 10 ++++++++-- src/OpenAPI/Builder/Strings.php | 10 ++++++++-- src/OpenAPI/Builder/TrueFalse.php | 10 ++++++++-- src/OpenAPI/Specification/APISchema.php | 5 ----- tests/OpenAPI/Specification/ArraysTest.php | 3 --- tests/OpenAPI/Specification/NumericTest.php | 6 ------ tests/OpenAPI/Specification/ObjectsTest.php | 4 ---- tests/OpenAPI/Specification/StringsTest.php | 3 --- tests/OpenAPI/Specification/TrueFalseTest.php | 3 --- 11 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/OpenAPI/Builder/Arrays.php b/src/OpenAPI/Builder/Arrays.php index 37a1a433..071453ca 100644 --- a/src/OpenAPI/Builder/Arrays.php +++ b/src/OpenAPI/Builder/Arrays.php @@ -11,6 +11,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\PipeDelimited; use Membrane\OpenAPI\Filter\FormatStyle\SpaceDelimited; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\BeforeSet; use Membrane\Processor\Collection; @@ -64,8 +65,13 @@ public function build(Specification $specification): Processor $beforeChain[] = new IsList(); - if ($specification->enum !== null) { - $beforeChain[] = new Contained($specification->enum); + if ( + $specification->keywords->enum !== null + ) { + $beforeChain[] = new Contained(array_map( + fn(Value $v) => $v->value, + $specification->keywords->enum, + )); } if ( diff --git a/src/OpenAPI/Builder/Numeric.php b/src/OpenAPI/Builder/Numeric.php index 965a0e5a..70a99475 100644 --- a/src/OpenAPI/Builder/Numeric.php +++ b/src/OpenAPI/Builder/Numeric.php @@ -14,6 +14,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\Field; use Membrane\Validator; @@ -62,8 +63,13 @@ public function build(Specification $specification): Processor $this->handleNumber($specification) : $this->handleInteger($specification)); - if ($specification->enum !== null) { - $chain[] = new Contained($specification->enum); + if ( + $specification->keywords->enum !== null + ) { + $chain[] = new Contained(array_map( + fn(Value $v) => $v->value, + $specification->keywords->enum, + )); } $chain = array_merge($chain, $this->handleNumericConstraints($specification)); diff --git a/src/OpenAPI/Builder/Objects.php b/src/OpenAPI/Builder/Objects.php index 40c72529..dece2f4a 100644 --- a/src/OpenAPI/Builder/Objects.php +++ b/src/OpenAPI/Builder/Objects.php @@ -12,6 +12,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\PipeDelimited; use Membrane\OpenAPI\Filter\FormatStyle\SpaceDelimited; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\BeforeSet; use Membrane\Processor\DefaultProcessor; @@ -71,8 +72,13 @@ public function build(Specification $specification): Processor $beforeChain[] = new IsArray(); - if ($specification->enum !== null) { - $beforeChain[] = new Contained($specification->enum); + if ( + $specification->keywords->enum !== null + ) { + $beforeChain[] = new Contained(array_map( + fn(Value $v) => $v->value, + $specification->keywords->enum, + )); } if (!empty($specification->keywords->required)) { diff --git a/src/OpenAPI/Builder/Strings.php b/src/OpenAPI/Builder/Strings.php index c645100b..9681dac5 100644 --- a/src/OpenAPI/Builder/Strings.php +++ b/src/OpenAPI/Builder/Strings.php @@ -12,6 +12,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\Field; use Membrane\Validator\Collection\Contained; @@ -54,8 +55,13 @@ public function build(Specification $specification): Processor $chain[] = new IsString(); - if ($specification->enum !== null) { - $chain[] = new Contained($specification->enum); + if ( + $specification->keywords->enum !== null + ) { + $chain[] = new Contained(array_map( + fn(Value $v) => $v->value, + $specification->keywords->enum, + )); } if ($specification->format === 'date') { diff --git a/src/OpenAPI/Builder/TrueFalse.php b/src/OpenAPI/Builder/TrueFalse.php index cb95442c..698e57c4 100644 --- a/src/OpenAPI/Builder/TrueFalse.php +++ b/src/OpenAPI/Builder/TrueFalse.php @@ -11,6 +11,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\Field; use Membrane\Validator\Collection\Contained; @@ -52,8 +53,13 @@ public function build(Specification $specification): Processor [new BoolString(), new ToBool()] : [new IsBool()]); - if ($specification->enum !== null) { - $chain[] = new Contained($specification->enum); + if ( + $specification->keywords->enum !== null + ) { + $chain[] = new Contained(array_map( + fn(Value $v) => $v->value, + $specification->keywords->enum, + )); } return new Field($specification->fieldName, ...$chain); diff --git a/src/OpenAPI/Specification/APISchema.php b/src/OpenAPI/Specification/APISchema.php index 8a597479..952dd94f 100644 --- a/src/OpenAPI/Specification/APISchema.php +++ b/src/OpenAPI/Specification/APISchema.php @@ -9,17 +9,12 @@ abstract class APISchema implements Specification { - /** @var mixed[] | null */ - public readonly ?array $enum; public readonly string $format; public function __construct( public readonly string $fieldName, public V30\Keywords | V31\Keywords $keywords ) { - $this->enum = isset($keywords->enum) ? - array_map(fn($e) => $e->value, $keywords->enum) : - null; $this->format = $keywords->format; } } diff --git a/tests/OpenAPI/Specification/ArraysTest.php b/tests/OpenAPI/Specification/ArraysTest.php index 756cfe79..38b28fbc 100644 --- a/tests/OpenAPI/Specification/ArraysTest.php +++ b/tests/OpenAPI/Specification/ArraysTest.php @@ -48,18 +48,15 @@ public static function dataSetsToConstruct(): array 'default values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'array')), [ - 'enum' => null, 'format' => '', ], ], 'assigned values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'array', - enum: [new Value([1, 2, 3]), new Value([5, 6, 7])], format: 'array of ints', )), [ - 'enum' => [[1, 2, 3], [5, 6, 7]], 'format' => 'array of ints', ], ], diff --git a/tests/OpenAPI/Specification/NumericTest.php b/tests/OpenAPI/Specification/NumericTest.php index 3312c87c..0c95cba2 100644 --- a/tests/OpenAPI/Specification/NumericTest.php +++ b/tests/OpenAPI/Specification/NumericTest.php @@ -53,38 +53,32 @@ public static function dataSetsToConstruct(): array new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'number')), [ 'type' => 'number', - 'enum' => null, ], ], 'default values for integer' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'integer')), [ 'type' => 'integer', - 'enum' => null, ], ], 'assigned values for number' => [ new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'number', - enum: [new Value(3), new Value(9)], format: 'float', )), [ 'type' => 'number', - 'enum' => [3, 9], 'format' => 'float', ], ], 'assigned values for integer' => [ new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'integer', - enum: [new Value(9)], multipleOf: 3, format: 'square of 3', )), [ 'type' => 'integer', - 'enum' => [9], 'format' => 'square of 3', ], ], diff --git a/tests/OpenAPI/Specification/ObjectsTest.php b/tests/OpenAPI/Specification/ObjectsTest.php index 8854a0dd..a5e90e07 100644 --- a/tests/OpenAPI/Specification/ObjectsTest.php +++ b/tests/OpenAPI/Specification/ObjectsTest.php @@ -48,7 +48,6 @@ public static function dataSetsToConstruct(): array 'default values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'object')), [ - 'enum' => null, 'format' => '', ], ], @@ -57,18 +56,15 @@ public static function dataSetsToConstruct(): array type: 'object', )), [ - 'enum' => null, 'format' => '', ], ], 'all relevant keywords assigned values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'object', - enum: [new Value(['id' => 5]), new Value(['id' => 10])], format: 'you cannot say yes', )), [ - 'enum' => [['id' => 5], ['id' => 10]], 'format' => 'you cannot say yes', ], ], diff --git a/tests/OpenAPI/Specification/StringsTest.php b/tests/OpenAPI/Specification/StringsTest.php index 4f7ea9f9..a0144a73 100644 --- a/tests/OpenAPI/Specification/StringsTest.php +++ b/tests/OpenAPI/Specification/StringsTest.php @@ -48,18 +48,15 @@ public static function dataSetsToConstruct(): array 'default values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string')), [ - 'enum' => null, 'format' => '', ], ], 'assigned values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'string', - enum: [new Value('This is a string'), new Value('So is this')], format: 'arbitrary', )), [ - 'enum' => ['This is a string', 'So is this'], 'format' => 'arbitrary', ], ], diff --git a/tests/OpenAPI/Specification/TrueFalseTest.php b/tests/OpenAPI/Specification/TrueFalseTest.php index bd285b54..8785c039 100644 --- a/tests/OpenAPI/Specification/TrueFalseTest.php +++ b/tests/OpenAPI/Specification/TrueFalseTest.php @@ -48,18 +48,15 @@ public static function dataSetsToConstruct(): array 'default values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'boolean')), [ - 'enum' => null, 'format' => '', ], ], 'assigned values' => [ new V30\Schema(new Identifier('test'), new Partial\Schema( type: 'boolean', - enum: [new Value(false), new Value(null)], format: 'you cannot say yes', )), [ - 'enum' => [false, null], 'format' => 'you cannot say yes', ], ], From 492a13e06660a86f40214fe4adebc73b28e67ac1 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 14:56:26 +0100 Subject: [PATCH 11/47] Remove format from APISchema --- src/OpenAPI/Builder/Numeric.php | 2 +- src/OpenAPI/Builder/Strings.php | 4 +- src/OpenAPI/Specification/APISchema.php | 6 +-- tests/OpenAPI/Specification/ArraysTest.php | 42 +---------------- tests/OpenAPI/Specification/NumericTest.php | 25 +--------- tests/OpenAPI/Specification/ObjectsTest.php | 46 +------------------ tests/OpenAPI/Specification/StringsTest.php | 40 ++-------------- tests/OpenAPI/Specification/TrueFalseTest.php | 42 ++--------------- 8 files changed, 16 insertions(+), 191 deletions(-) diff --git a/src/OpenAPI/Builder/Numeric.php b/src/OpenAPI/Builder/Numeric.php index 70a99475..396b3db4 100644 --- a/src/OpenAPI/Builder/Numeric.php +++ b/src/OpenAPI/Builder/Numeric.php @@ -80,7 +80,7 @@ public function build(Specification $specification): Processor /** @return Filter[]|Validator[] */ private function handleNumber(OpenAPI\Specification\Numeric $specification): array { - if (in_array($specification->format, ['float', 'double'], true)) { + if (in_array($specification->keywords->format, ['float', 'double'], true)) { return $specification->convertFromString ? [new NumericString(), new ToFloat()] : [new IsFloat()]; } else { return $specification->convertFromString ? [new NumericString(), new ToNumber()] : [new IsNumber()]; diff --git a/src/OpenAPI/Builder/Strings.php b/src/OpenAPI/Builder/Strings.php index 9681dac5..6dcd72a1 100644 --- a/src/OpenAPI/Builder/Strings.php +++ b/src/OpenAPI/Builder/Strings.php @@ -64,11 +64,11 @@ public function build(Specification $specification): Processor )); } - if ($specification->format === 'date') { + if ($specification->keywords->format === 'date') { $chain[] = new DateString('Y-m-d', true); } - if ($specification->format === 'date-time') { + if ($specification->keywords->format === 'date-time') { $chain[] = new ToUpperCase(); $chain[] = new AnyOf( new DateString('Y-m-d\TH:i:sP', true), diff --git a/src/OpenAPI/Specification/APISchema.php b/src/OpenAPI/Specification/APISchema.php index 952dd94f..e27fee1d 100644 --- a/src/OpenAPI/Specification/APISchema.php +++ b/src/OpenAPI/Specification/APISchema.php @@ -9,12 +9,8 @@ abstract class APISchema implements Specification { - public readonly string $format; - public function __construct( public readonly string $fieldName, public V30\Keywords | V31\Keywords $keywords - ) { - $this->format = $keywords->format; - } + ) {} } diff --git a/tests/OpenAPI/Specification/ArraysTest.php b/tests/OpenAPI/Specification/ArraysTest.php index 38b28fbc..e035c033 100644 --- a/tests/OpenAPI/Specification/ArraysTest.php +++ b/tests/OpenAPI/Specification/ArraysTest.php @@ -27,7 +27,7 @@ public function throwsExceptionForMissingType(): void new Arrays( '', - new V30\Schema(new Identifier('test'), new Partial\Schema())->value + new V30\Schema(new Identifier('test'), new Partial\Schema())->value, ); } @@ -38,45 +38,7 @@ public function throwsExceptionForInvalidType(): void new Arrays( '', - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string'))->value + new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string'))->value, ); } - - public static function dataSetsToConstruct(): array - { - return [ - 'default values' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'array')), - [ - 'format' => '', - ], - ], - 'assigned values' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema( - type: 'array', - format: 'array of ints', - )), - [ - 'format' => 'array of ints', - ], - ], - ]; - } - - #[Test] - #[DataProvider('dataSetsToConstruct')] - public function constructTest( - V30\Schema | V31\Schema $schema, - array $expected - ): void { - $sut = new Arrays('', $schema->value); - - foreach ($expected as $key => $value) { - if ($key === 'items') { - self::assertEquals($value, $sut->$key, sprintf('%s does not meet expected value', $key)); - } else { - self::assertSame($value, $sut->$key, sprintf('%s does not meet expected value', $key)); - } - } - } } diff --git a/tests/OpenAPI/Specification/NumericTest.php b/tests/OpenAPI/Specification/NumericTest.php index 0c95cba2..2eed0783 100644 --- a/tests/OpenAPI/Specification/NumericTest.php +++ b/tests/OpenAPI/Specification/NumericTest.php @@ -49,39 +49,18 @@ public function throwsExceptionForInvalidType(): void public static function dataSetsToConstruct(): array { return [ - 'default values for number' => [ + 'number' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'number')), [ 'type' => 'number', ], ], - 'default values for integer' => [ + 'integer' => [ new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'integer')), [ 'type' => 'integer', ], ], - 'assigned values for number' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema( - type: 'number', - format: 'float', - )), - [ - 'type' => 'number', - 'format' => 'float', - ], - ], - 'assigned values for integer' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema( - type: 'integer', - multipleOf: 3, - format: 'square of 3', - )), - [ - 'type' => 'integer', - 'format' => 'square of 3', - ], - ], ]; } diff --git a/tests/OpenAPI/Specification/ObjectsTest.php b/tests/OpenAPI/Specification/ObjectsTest.php index a5e90e07..0e744c4f 100644 --- a/tests/OpenAPI/Specification/ObjectsTest.php +++ b/tests/OpenAPI/Specification/ObjectsTest.php @@ -27,7 +27,7 @@ public function throwsExceptionForMissingType(): void new Objects( '', - (new V30\Schema(new Identifier('test'), new Partial\Schema()))->value + new V30\Schema(new Identifier('test'), new Partial\Schema())->value, ); } @@ -38,49 +38,7 @@ public function throwsExceptionForIncorrectType(): void new Objects( '', - (new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string')))->value, + new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string'))->value, ); } - - public static function dataSetsToConstruct(): array - { - return [ - 'default values' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'object')), - [ - 'format' => '', - ], - ], - 'additionalProperties assigned false' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema( - type: 'object', - )), - [ - 'format' => '', - ], - ], - 'all relevant keywords assigned values' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema( - type: 'object', - format: 'you cannot say yes', - )), - [ - 'format' => 'you cannot say yes', - ], - ], - ]; - } - - #[DataProvider('dataSetsToConstruct')] - #[Test] - public function constructTest( - V30\Schema | V31\Schema $schema, - array $expected - ): void { - $sut = new Objects('', $schema->value); - - foreach ($expected as $key => $value) { - self::assertEquals($value, $sut->$key, sprintf('%s does not meet expected value', $key)); - } - } } diff --git a/tests/OpenAPI/Specification/StringsTest.php b/tests/OpenAPI/Specification/StringsTest.php index a0144a73..97ca8932 100644 --- a/tests/OpenAPI/Specification/StringsTest.php +++ b/tests/OpenAPI/Specification/StringsTest.php @@ -8,10 +8,8 @@ use Membrane\OpenAPI\Specification\APISchema; use Membrane\OpenAPI\Specification\Strings; use Membrane\OpenAPIReader\ValueObject\Partial; -use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; -use Membrane\OpenAPIReader\ValueObject\Value; +use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30}; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -27,7 +25,7 @@ public function throwsExceptionForMissingType(): void new Strings( '', - (new V30\Schema(new Identifier('test'), new Partial\Schema()))->value, + new V30\Schema(new Identifier('test'), new Partial\Schema())->value, ); } @@ -38,39 +36,7 @@ public function throwsExceptionForIncorrectType(): void new Strings( '', - (new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'integer')))->value, + new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'integer'))->value, ); } - - public static function dataSetsToConstruct(): array - { - return [ - 'default values' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string')), - [ - 'format' => '', - ], - ], - 'assigned values' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema( - type: 'string', - format: 'arbitrary', - )), - [ - 'format' => 'arbitrary', - ], - ], - ]; - } - - #[DataProvider('dataSetsToConstruct')] - #[Test] - public function constructTest(V30\Schema|V31\Schema $schema, array $expected): void - { - $sut = new Strings('', $schema->value); - - foreach ($expected as $key => $value) { - self::assertSame($value, $sut->$key, sprintf('%s does not meet expected value', $key)); - } - } } diff --git a/tests/OpenAPI/Specification/TrueFalseTest.php b/tests/OpenAPI/Specification/TrueFalseTest.php index 8785c039..a3459fa5 100644 --- a/tests/OpenAPI/Specification/TrueFalseTest.php +++ b/tests/OpenAPI/Specification/TrueFalseTest.php @@ -8,10 +8,8 @@ use Membrane\OpenAPI\Specification\APISchema; use Membrane\OpenAPI\Specification\TrueFalse; use Membrane\OpenAPIReader\ValueObject\Partial; -use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; -use Membrane\OpenAPIReader\ValueObject\Value; +use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30}; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -27,7 +25,7 @@ public function throwsExceptionForMissingType(): void new TrueFalse( '', - (new V30\Schema(new Identifier('test'), new Partial\Schema()))->value + new V30\Schema(new Identifier('test'), new Partial\Schema())->value, ); } @@ -38,41 +36,7 @@ public function throwsExceptionForInvalidType(): void new TrueFalse( '', - (new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string')))->value + new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string'))->value, ); } - - public static function dataSetsToConstruct(): array - { - return [ - 'default values' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'boolean')), - [ - 'format' => '', - ], - ], - 'assigned values' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema( - type: 'boolean', - format: 'you cannot say yes', - )), - [ - 'format' => 'you cannot say yes', - ], - ], - ]; - } - - #[DataProvider('dataSetsToConstruct')] - #[Test] - public function constructTest( - V30\Schema | V31\Schema $schema, - array $expected - ): void { - $sut = new TrueFalse('', $schema->value); - - foreach ($expected as $key => $value) { - self::assertSame($value, $sut->$key, sprintf('%s does not meet expected value', $key)); - } - } } From 9b655ad0a0112a0c30322b0f3ee24874523852e4 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 15:03:45 +0100 Subject: [PATCH 12/47] Remove type check from Numeric Specification --- src/OpenAPI/Builder/Numeric.php | 20 +++++- src/OpenAPI/Specification/Numeric.php | 16 ----- tests/OpenAPI/Specification/NumericTest.php | 79 --------------------- 3 files changed, 17 insertions(+), 98 deletions(-) delete mode 100644 tests/OpenAPI/Specification/NumericTest.php diff --git a/src/OpenAPI/Builder/Numeric.php b/src/OpenAPI/Builder/Numeric.php index 396b3db4..1bc8237d 100644 --- a/src/OpenAPI/Builder/Numeric.php +++ b/src/OpenAPI/Builder/Numeric.php @@ -4,6 +4,7 @@ namespace Membrane\OpenAPI\Builder; +use DomainException; use Membrane\Builder\Specification; use Membrane\Filter; use Membrane\Filter\String\LeftTrim; @@ -14,6 +15,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\Field; @@ -39,6 +41,18 @@ public function build(Specification $specification): Processor { assert($specification instanceof OpenAPI\Specification\Numeric); + $types = $specification->keywords->types; + if (in_array(Type::Integer, $types)) { + $type = Type::Integer->value; + } elseif (in_array(Type::Number, $types)) { + $type = Type::Number->value; + } else { + throw new \DomainException(sprintf( + 'numeric builder expected integer or number types, received: %s', + implode(', ', array_map(fn($t) => $t->value, $types)), + )); + } + $chain = $specification->convertFromArray ? [new Filter\String\Implode(',')] : []; @@ -46,7 +60,7 @@ public function build(Specification $specification): Processor if (isset($specification->style)) { switch (Style::tryFrom($specification->style)) { case Style::Matrix: - $chain[] = new Matrix($specification->type, false); + $chain[] = new Matrix($type, false); break; case Style::Label: $chain[] = new LeftTrim('.'); @@ -54,12 +68,12 @@ public function build(Specification $specification): Processor case Style::Form: case Style::SpaceDelimited: case Style::PipeDelimited: - $chain[] = new Form($specification->type, false); + $chain[] = new Form($type, false); break; } } - $chain = array_merge($chain, $specification->type === 'number' ? + $chain = array_merge($chain, $type === 'number' ? $this->handleNumber($specification) : $this->handleInteger($specification)); diff --git a/src/OpenAPI/Specification/Numeric.php b/src/OpenAPI/Specification/Numeric.php index ee1ef569..d56e1e76 100644 --- a/src/OpenAPI/Specification/Numeric.php +++ b/src/OpenAPI/Specification/Numeric.php @@ -4,14 +4,10 @@ namespace Membrane\OpenAPI\Specification; -use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; -use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; class Numeric extends APISchema { - public readonly string $type; - public function __construct( string $fieldName, V30\Keywords | V31\Keywords $keywords, @@ -19,18 +15,6 @@ public function __construct( public readonly bool $convertFromArray = false, public readonly ?string $style = null, ) { - $types = $keywords->types; - if (in_array(Type::Integer, $types)) { - $this->type = Type::Integer->value; - } elseif (in_array(Type::Number, $types)) { - $this->type = Type::Number->value; - } else { - throw CannotProcessSpecification::mismatchedType( - ['integer', 'number'], - array_map(fn($t) => $t->value, $types), - ); - } - parent::__construct($fieldName, $keywords); } } diff --git a/tests/OpenAPI/Specification/NumericTest.php b/tests/OpenAPI/Specification/NumericTest.php deleted file mode 100644 index 2eed0783..00000000 --- a/tests/OpenAPI/Specification/NumericTest.php +++ /dev/null @@ -1,79 +0,0 @@ -value, - ); - } - - #[Test] - public function throwsExceptionForInvalidType(): void - { - self::expectExceptionObject( - CannotProcessSpecification::mismatchedType(['integer', 'number'], ['string']), - ); - - new Numeric( - '', - (new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string')))->value, - ); - } - - public static function dataSetsToConstruct(): array - { - return [ - 'number' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'number')), - [ - 'type' => 'number', - ], - ], - 'integer' => [ - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'integer')), - [ - 'type' => 'integer', - ], - ], - ]; - } - - #[DataProvider('dataSetsToConstruct')] - #[Test] - public function constructTest( - V30\Schema $schema, - array $expected - ): void { - $sut = new Numeric('', $schema->value); - - foreach ($expected as $key => $value) { - self::assertSame($value, $sut->$key, sprintf('%s did not meet expected value', $key)); - } - } -} From 60ef10711ee464ad6e8cced57d69c00207d530bb Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 15:09:31 +0100 Subject: [PATCH 13/47] Remove typecheck from Arrays specification --- src/OpenAPI/Builder/Arrays.php | 11 ++++++ src/OpenAPI/Specification/Arrays.php | 9 ----- tests/OpenAPI/Specification/ArraysTest.php | 44 ---------------------- 3 files changed, 11 insertions(+), 53 deletions(-) delete mode 100644 tests/OpenAPI/Specification/ArraysTest.php diff --git a/src/OpenAPI/Builder/Arrays.php b/src/OpenAPI/Builder/Arrays.php index 071453ca..a1d8286b 100644 --- a/src/OpenAPI/Builder/Arrays.php +++ b/src/OpenAPI/Builder/Arrays.php @@ -11,6 +11,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\PipeDelimited; use Membrane\OpenAPI\Filter\FormatStyle\SpaceDelimited; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\BeforeSet; @@ -30,6 +31,16 @@ public function supports(Specification $specification): bool public function build(Specification $specification): Processor { assert($specification instanceof \Membrane\OpenAPI\Specification\Arrays); + if (!in_array(Type::Array, $specification->keywords->types)) { + throw new \DomainException(sprintf( + 'arrays builder expected array types, received: %s', + implode(', ', array_map( + fn($t) => $t->value, + $specification->keywords->types, + )), + )); + } + $beforeChain = $specification->convertFromArray ? [new Filter\String\Implode(',')] : diff --git a/src/OpenAPI/Specification/Arrays.php b/src/OpenAPI/Specification/Arrays.php index 8abe84c4..4bf73f0d 100644 --- a/src/OpenAPI/Specification/Arrays.php +++ b/src/OpenAPI/Specification/Arrays.php @@ -4,9 +4,7 @@ namespace Membrane\OpenAPI\Specification; -use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; -use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; class Arrays extends APISchema { @@ -18,13 +16,6 @@ public function __construct( public readonly ?string $style = null, public readonly ?bool $explode = null, ) { - if (!in_array(Type::Array, $keywords->types)) { - throw CannotProcessSpecification::mismatchedType( - ['array'], - array_map(fn($t) => $t->value, $keywords->types), - ); - } - parent::__construct($fieldName, $keywords); } } diff --git a/tests/OpenAPI/Specification/ArraysTest.php b/tests/OpenAPI/Specification/ArraysTest.php deleted file mode 100644 index e035c033..00000000 --- a/tests/OpenAPI/Specification/ArraysTest.php +++ /dev/null @@ -1,44 +0,0 @@ -value, - ); - } - - #[Test] - public function throwsExceptionForInvalidType(): void - { - self::expectExceptionObject(CannotProcessSpecification::mismatchedType(['array'], ['string'])); - - new Arrays( - '', - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string'))->value, - ); - } -} From 5e25750224f87a0ce19a76fef07cba15043e38e2 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 15:11:34 +0100 Subject: [PATCH 14/47] Remove type check from Objects Specification --- src/OpenAPI/Builder/Objects.php | 10 +++++ src/OpenAPI/Specification/Objects.php | 9 ----- tests/OpenAPI/Specification/ObjectsTest.php | 44 --------------------- 3 files changed, 10 insertions(+), 53 deletions(-) delete mode 100644 tests/OpenAPI/Specification/ObjectsTest.php diff --git a/src/OpenAPI/Builder/Objects.php b/src/OpenAPI/Builder/Objects.php index dece2f4a..1da62816 100644 --- a/src/OpenAPI/Builder/Objects.php +++ b/src/OpenAPI/Builder/Objects.php @@ -12,6 +12,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\PipeDelimited; use Membrane\OpenAPI\Filter\FormatStyle\SpaceDelimited; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\BeforeSet; @@ -33,6 +34,15 @@ public function supports(Specification $specification): bool public function build(Specification $specification): Processor { assert($specification instanceof \Membrane\OpenAPI\Specification\Objects); + if (!in_array(Type::Object, $specification->keywords->types)) { + throw new \DomainException(sprintf( + 'arrays builder expected object types, received: %s', + implode(', ', array_map( + fn($t) => $t->value, + $specification->keywords->types, + )), + )); + } $beforeChain = []; diff --git a/src/OpenAPI/Specification/Objects.php b/src/OpenAPI/Specification/Objects.php index 2383088e..2bbdf99f 100644 --- a/src/OpenAPI/Specification/Objects.php +++ b/src/OpenAPI/Specification/Objects.php @@ -4,9 +4,7 @@ namespace Membrane\OpenAPI\Specification; -use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; -use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; class Objects extends APISchema { @@ -18,13 +16,6 @@ public function __construct( public readonly ?string $style = null, public readonly ?bool $explode = null, ) { - if (!in_array(Type::Object, $keywords->types)) { - throw CannotProcessSpecification::mismatchedType( - ['object'], - array_map(fn($t) => $t->value, $keywords->types), - ); - } - parent::__construct($fieldName, $keywords); } } diff --git a/tests/OpenAPI/Specification/ObjectsTest.php b/tests/OpenAPI/Specification/ObjectsTest.php deleted file mode 100644 index 0e744c4f..00000000 --- a/tests/OpenAPI/Specification/ObjectsTest.php +++ /dev/null @@ -1,44 +0,0 @@ -value, - ); - } - - #[Test] - public function throwsExceptionForIncorrectType(): void - { - self::expectExceptionObject(CannotProcessSpecification::mismatchedType(['object'], ['string'])); - - new Objects( - '', - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string'))->value, - ); - } -} From d46e5fae3607592f63820b77fc0bcec954214656 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 15:13:25 +0100 Subject: [PATCH 15/47] Correct Objects builder exception --- src/OpenAPI/Builder/Objects.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenAPI/Builder/Objects.php b/src/OpenAPI/Builder/Objects.php index 1da62816..92d741a2 100644 --- a/src/OpenAPI/Builder/Objects.php +++ b/src/OpenAPI/Builder/Objects.php @@ -36,7 +36,7 @@ public function build(Specification $specification): Processor assert($specification instanceof \Membrane\OpenAPI\Specification\Objects); if (!in_array(Type::Object, $specification->keywords->types)) { throw new \DomainException(sprintf( - 'arrays builder expected object types, received: %s', + 'object builder expected object types, received: %s', implode(', ', array_map( fn($t) => $t->value, $specification->keywords->types, From 1bc8e2552d8ed1992ad5f429edf910f68d873c4a Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 15:14:06 +0100 Subject: [PATCH 16/47] Remove type check from Strings Specification --- src/OpenAPI/Builder/Strings.php | 11 ++++++ src/OpenAPI/Specification/Strings.php | 7 ---- tests/OpenAPI/Specification/StringsTest.php | 42 --------------------- 3 files changed, 11 insertions(+), 49 deletions(-) delete mode 100644 tests/OpenAPI/Specification/StringsTest.php diff --git a/src/OpenAPI/Builder/Strings.php b/src/OpenAPI/Builder/Strings.php index 6dcd72a1..09a822a6 100644 --- a/src/OpenAPI/Builder/Strings.php +++ b/src/OpenAPI/Builder/Strings.php @@ -12,6 +12,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\Field; @@ -32,6 +33,16 @@ public function supports(Specification $specification): bool public function build(Specification $specification): Processor { assert($specification instanceof \Membrane\OpenAPI\Specification\Strings); + if (!in_array(Type::String, $specification->keywords->types)) { + throw new \DomainException(sprintf( + 'strings builder expected string types, received: %s', + implode(', ', array_map( + fn($t) => $t->value, + $specification->keywords->types, + )), + )); + } + $chain = $specification->convertFromArray ? [new Implode(',')] : diff --git a/src/OpenAPI/Specification/Strings.php b/src/OpenAPI/Specification/Strings.php index 55a1315f..f7af274b 100644 --- a/src/OpenAPI/Specification/Strings.php +++ b/src/OpenAPI/Specification/Strings.php @@ -16,13 +16,6 @@ public function __construct( public readonly bool $convertFromArray = false, public readonly ?string $style = null, ) { - if (!in_array(Type::String, $keywords->types)) { - throw CannotProcessSpecification::mismatchedType( - ['string'], - array_map(fn($t) => $t->value, $keywords->types), - ); - } - parent::__construct($fieldName, $keywords); } } diff --git a/tests/OpenAPI/Specification/StringsTest.php b/tests/OpenAPI/Specification/StringsTest.php deleted file mode 100644 index 97ca8932..00000000 --- a/tests/OpenAPI/Specification/StringsTest.php +++ /dev/null @@ -1,42 +0,0 @@ -value, - ); - } - - #[Test] - public function throwsExceptionForIncorrectType(): void - { - self::expectExceptionObject(CannotProcessSpecification::mismatchedType(['string'], ['integer'])); - - new Strings( - '', - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'integer'))->value, - ); - } -} From c86e2711d43bee59fa3650b9929fc12afd5fee16 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 15:15:44 +0100 Subject: [PATCH 17/47] Remove type check from TrueFalse Specification --- src/OpenAPI/Builder/TrueFalse.php | 10 +++++ src/OpenAPI/Specification/TrueFalse.php | 9 ---- tests/OpenAPI/Specification/TrueFalseTest.php | 42 ------------------- 3 files changed, 10 insertions(+), 51 deletions(-) delete mode 100644 tests/OpenAPI/Specification/TrueFalseTest.php diff --git a/src/OpenAPI/Builder/TrueFalse.php b/src/OpenAPI/Builder/TrueFalse.php index 698e57c4..d2d9b126 100644 --- a/src/OpenAPI/Builder/TrueFalse.php +++ b/src/OpenAPI/Builder/TrueFalse.php @@ -11,6 +11,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; +use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\Field; @@ -28,6 +29,15 @@ public function supports(Specification $specification): bool public function build(Specification $specification): Processor { assert($specification instanceof \Membrane\OpenAPI\Specification\TrueFalse); + if (!in_array(Type::Boolean, $specification->keywords->types)) { + throw new \DomainException(sprintf( + 'truefalse builder expected boolean types, received: %s', + implode(', ', array_map( + fn($t) => $t->value, + $specification->keywords->types, + )), + )); + } $chain = $specification->convertFromArray ? [new Implode(',')] : diff --git a/src/OpenAPI/Specification/TrueFalse.php b/src/OpenAPI/Specification/TrueFalse.php index 0d8d7671..00d199b0 100644 --- a/src/OpenAPI/Specification/TrueFalse.php +++ b/src/OpenAPI/Specification/TrueFalse.php @@ -4,9 +4,7 @@ namespace Membrane\OpenAPI\Specification; -use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; -use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; class TrueFalse extends APISchema { @@ -17,13 +15,6 @@ public function __construct( public readonly bool $convertFromArray = false, public readonly ?string $style = null, ) { - if (!in_array(Type::Boolean, $keywords->types)) { - throw CannotProcessSpecification::mismatchedType( - ['boolean'], - array_map(fn($t) => $t->value, $keywords->types), - ); - } - parent::__construct($fieldName, $keywords); } } diff --git a/tests/OpenAPI/Specification/TrueFalseTest.php b/tests/OpenAPI/Specification/TrueFalseTest.php deleted file mode 100644 index a3459fa5..00000000 --- a/tests/OpenAPI/Specification/TrueFalseTest.php +++ /dev/null @@ -1,42 +0,0 @@ -value, - ); - } - - #[Test] - public function throwsExceptionForInvalidType(): void - { - self::expectExceptionObject(CannotProcessSpecification::mismatchedType(['boolean'], ['string'])); - - new TrueFalse( - '', - new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string'))->value, - ); - } -} From dbfbe3149c70a5017eceea14874071936c2f62fb Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 15:33:43 +0100 Subject: [PATCH 18/47] Remove Numeric Specification Make Numeric builder an independent class --- src/OpenAPI/Builder/APIBuilder.php | 6 +- src/OpenAPI/Builder/Numeric.php | 88 +++++----- src/OpenAPI/Specification/Numeric.php | 20 --- .../Command/CacheOpenAPIProcessorsTest.php | 1 - .../Service/CacheOpenAPIProcessorsTest.php | 1 - tests/MembraneTest.php | 1 - tests/OpenAPI/Builder/ArraysTest.php | 1 - tests/OpenAPI/Builder/NumericTest.php | 154 ++++++++---------- tests/OpenAPI/Builder/ObjectsTest.php | 1 - .../Builder/OpenAPIRequestBuilderTest.php | 3 +- .../Builder/OpenAPIResponseBuilderTest.php | 2 - .../OpenAPI/Builder/ParameterBuilderTest.php | 1 - tests/OpenAPI/Builder/RequestBuilderTest.php | 1 - tests/OpenAPI/Builder/ResponseBuilderTest.php | 2 - 14 files changed, 114 insertions(+), 168 deletions(-) delete mode 100644 src/OpenAPI/Specification/Numeric.php diff --git a/src/OpenAPI/Builder/APIBuilder.php b/src/OpenAPI/Builder/APIBuilder.php index 1ca245d7..5a05ec14 100644 --- a/src/OpenAPI/Builder/APIBuilder.php +++ b/src/OpenAPI/Builder/APIBuilder.php @@ -15,7 +15,7 @@ use Membrane\Validator\Type\IsNull; use Membrane\Validator\Utility; -abstract class APIBuilder implements Builder +abstract class APIBuilder { private Arrays $arrayBuilder; private TrueFalse $trueFalseBuilder; @@ -103,13 +103,13 @@ public function fromSchema( )), Type::Integer, Type::Number => $this->getNumericBuilder() - ->build(new OpenAPI\Specification\Numeric( + ->build( $fieldName, $schema->value, $convertFromString, $convertFromArray, $style - )), + ), Type::String => ($this->getStringBuilder()) ->build(new OpenAPI\Specification\Strings( diff --git a/src/OpenAPI/Builder/Numeric.php b/src/OpenAPI/Builder/Numeric.php index 1bc8237d..676ad949 100644 --- a/src/OpenAPI/Builder/Numeric.php +++ b/src/OpenAPI/Builder/Numeric.php @@ -4,16 +4,14 @@ namespace Membrane\OpenAPI\Builder; -use DomainException; -use Membrane\Builder\Specification; use Membrane\Filter; use Membrane\Filter\String\LeftTrim; use Membrane\Filter\Type\ToFloat; use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; -use Membrane\OpenAPI; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; +use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; @@ -30,18 +28,16 @@ use Membrane\Validator\Type\IsInt; use Membrane\Validator\Type\IsNumber; -class Numeric extends APIBuilder +final readonly class Numeric { - public function supports(Specification $specification): bool - { - return $specification instanceof OpenAPI\Specification\Numeric; - } - - public function build(Specification $specification): Processor - { - assert($specification instanceof OpenAPI\Specification\Numeric); - - $types = $specification->keywords->types; + public function build( + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString = false, + bool $convertFromArray = false, + ?string $style = null, + ): Processor { + $types = $keywords->types; if (in_array(Type::Integer, $types)) { $type = Type::Integer->value; } elseif (in_array(Type::Number, $types)) { @@ -53,12 +49,12 @@ public function build(Specification $specification): Processor )); } - $chain = $specification->convertFromArray ? + $chain = $convertFromArray ? [new Filter\String\Implode(',')] : []; - if (isset($specification->style)) { - switch (Style::tryFrom($specification->style)) { + if (isset($style)) { + switch (Style::tryFrom($style)) { case Style::Matrix: $chain[] = new Matrix($type, false); break; @@ -73,62 +69,70 @@ public function build(Specification $specification): Processor } } - $chain = array_merge($chain, $type === 'number' ? - $this->handleNumber($specification) : - $this->handleInteger($specification)); + $chain = array_merge($chain, $type === 'number' + ? $this->handleNumber($keywords, $convertFromString) + : $this->handleInteger($keywords, $convertFromString)); if ( - $specification->keywords->enum !== null + $keywords->enum !== null ) { $chain[] = new Contained(array_map( fn(Value $v) => $v->value, - $specification->keywords->enum, + $keywords->enum, )); } - $chain = array_merge($chain, $this->handleNumericConstraints($specification)); + $chain = array_merge( + $chain, + $this->handleNumericConstraints($keywords), + ); - return new Field($specification->fieldName, ...$chain); + return new Field($fieldName, ...$chain); } /** @return Filter[]|Validator[] */ - private function handleNumber(OpenAPI\Specification\Numeric $specification): array - { - if (in_array($specification->keywords->format, ['float', 'double'], true)) { - return $specification->convertFromString ? [new NumericString(), new ToFloat()] : [new IsFloat()]; + private function handleNumber( + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString, + ): array { + if (in_array($keywords->format, ['float', 'double'], true)) { + return $convertFromString ? [new NumericString(), new ToFloat()] : [new IsFloat()]; } else { - return $specification->convertFromString ? [new NumericString(), new ToNumber()] : [new IsNumber()]; + return $convertFromString ? [new NumericString(), new ToNumber()] : [new IsNumber()]; } } /** @return Filter[]|Validator[] */ - private function handleInteger(OpenAPI\Specification\Numeric $specification): array - { - return $specification->convertFromString ? [new IntString(), new ToInt()] : [new IsInt()]; + private function handleInteger( + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString, + ): array { + return $convertFromString ? [new IntString(), new ToInt()] : [new IsInt()]; } /** @return Validator[] */ - private function handleNumericConstraints(OpenAPI\Specification\Numeric $specification): array - { + private function handleNumericConstraints( + V30\Keywords | V31\Keywords $keywords, + ): array { $chain = []; // if keywords->maximum !== null // keywords->maximum->limit - if ($specification->keywords->maximum !== null) { + if ($keywords->maximum !== null) { $chain[] = new Maximum( - $specification->keywords->maximum->limit, - $specification->keywords->maximum->exclusive, + $keywords->maximum->limit, + $keywords->maximum->exclusive, ); } - if ($specification->keywords->minimum !== null) { + if ($keywords->minimum !== null) { $chain[] = new Minimum( - $specification->keywords->minimum->limit, - $specification->keywords->minimum->exclusive, + $keywords->minimum->limit, + $keywords->minimum->exclusive, ); } - if ($specification->keywords->multipleOf !== null) { - $chain[] = new MultipleOf($specification->keywords->multipleOf); + if ($keywords->multipleOf !== null) { + $chain[] = new MultipleOf($keywords->multipleOf); } return $chain; diff --git a/src/OpenAPI/Specification/Numeric.php b/src/OpenAPI/Specification/Numeric.php deleted file mode 100644 index d56e1e76..00000000 --- a/src/OpenAPI/Specification/Numeric.php +++ /dev/null @@ -1,20 +0,0 @@ -supports($specification)); - } - - #[Test] - public function doesNotSupportSpecificationsOtherThanNumeric(): void - { - $specification = self::createStub(\Membrane\Builder\Specification::class); - $sut = new Numeric(); - - self::assertFalse($sut->supports($specification)); - } - public static function specificationsToBuild(): array { return [ 'integer input to convert from string' => [ - new Specification\Numeric( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'integer')))->value, - true - ), - new Field('', new IntString(), new ToInt()), + new Field('int-from-string', new IntString(), new ToInt()), + 'int-from-string', + new V30\Schema(new Identifier(''), new Partial\Schema(type: 'integer'))->value, + true, ], 'strict integer input' => [ - new Specification\Numeric( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'integer')))->value, - false, - ), - new Field('', new IsInt()), + new Field('strict-integer', new IsInt()), + 'strict-integer', + new V30\Schema(new Identifier(''), new Partial\Schema(type: 'integer'))->value, ], 'number input to convert from string' => [ - new Specification\Numeric( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'number')))->value, - true, - ), - new Field('', new NumericString(), new ToNumber()), + new Field('number-from-string', new NumericString(), new ToNumber()), + 'number-from-string', + new V30\Schema(new Identifier(''), new Partial\Schema(type: 'number'))->value, + true, ], 'strict number input' => [ - new Specification\Numeric( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'number')))->value, - false, - ), - new Field('', new IsNumber()), + new Field('strict-number', new IsNumber()), + 'strict-number', + new V30\Schema(new Identifier(''), new Partial\Schema(type: 'number'))->value, ], 'float input to convert from string' => [ - new Specification\Numeric( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'number', format: 'float')))->value, - true, - ), - new Field('', new NumericString(), new ToFloat()), + new Field('float-from-string', new NumericString(), new ToFloat()), + 'float-from-string', + new V30\Schema(new Identifier(''), new Partial\Schema(type: 'number', format: 'float'))->value, + true, ], 'strict float input' => [ - new Specification\Numeric( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'number', format: 'float')))->value, - false, - ), - new Field('', new IsFloat()), + new Field('strict-float', new IsFloat()), + 'strict-float', + new V30\Schema(new Identifier(''), new Partial\Schema(type: 'number', format: 'float'))->value, ], 'detailed input to convert from string' => [ - new Specification\Numeric( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema( - type: 'integer', - enum: [new Value(1), new Value(2), new Value(3), new Value(null)], - multipleOf: 3, - exclusiveMaximum: true, - exclusiveMinimum: true, - maximum: 4, - minimum: 0, - format: 'int', - )))->value, - true - ), new Field( - '', + 'maximum-detail-from-string', new IntString(), new ToInt(), new Contained([1, 2, 3, null]), new Maximum(4, true), new Minimum(0, true), new MultipleOf(3) - ) + ), + 'maximum-detail-from-string', + new V30\Schema(new Identifier(''), new Partial\Schema( + type: 'integer', + enum: [new Value(1), new Value(2), new Value(3), new Value(null)], + multipleOf: 3, + exclusiveMaximum: true, + exclusiveMinimum: true, + maximum: 4, + minimum: 0, + format: 'int', + ))->value, + true, ], 'strict detailed input' => [ - new Specification\Numeric( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema( - type: 'integer', - enum: [new Value(1), new Value(2), new Value(3), new Value(null)], - multipleOf: 3, - exclusiveMaximum: true, - exclusiveMinimum: true, - maximum: 4, - minimum: 0, - format: 'int', - )))->value, - false - ), new Field( - '', + 'strict-maximum-detail', new IsInt(), new Contained([1, 2, 3, null]), new Maximum(4, true), new Minimum(0, true), new MultipleOf(3) - ) + ), + 'strict-maximum-detail', + new V30\Schema(new Identifier(''), new Partial\Schema( + type: 'integer', + enum: [new Value(1), new Value(2), new Value(3), new Value(null)], + multipleOf: 3, + exclusiveMaximum: true, + exclusiveMinimum: true, + maximum: 4, + minimum: 0, + format: 'int', + ))->value, ], ]; } #[DataProvider('specificationsToBuild')] #[Test] - public function buildTest(Specification\Numeric $specification, Processor $expected): void - { + public function buildTest( + Processor $expected, + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString = false, + bool $convertFromArray = false, + ?string $style = null, + ): void { $sut = new Numeric(); - $actual = $sut->build($specification); + $actual = $sut->build( + $fieldName, + $keywords, + $convertFromString, + $convertFromArray, + $style, + ); self::assertEquals($expected, $actual); } diff --git a/tests/OpenAPI/Builder/ObjectsTest.php b/tests/OpenAPI/Builder/ObjectsTest.php index a3fe8f7a..0c848371 100644 --- a/tests/OpenAPI/Builder/ObjectsTest.php +++ b/tests/OpenAPI/Builder/ObjectsTest.php @@ -41,7 +41,6 @@ #[UsesClass(Strings::class)] #[UsesClass(AnyOf::class)] #[UsesClass(Specification\APISchema::class)] -#[UsesClass(Specification\Numeric::class)] #[UsesClass(Specification\Strings::class)] #[UsesClass(Specification\TrueFalse::class)] #[UsesClass(BeforeSet::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php index c173d357..e7e3c05b 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php @@ -106,7 +106,6 @@ #[UsesClass(AnyOf::class)] #[UsesClass(OneOf::class)] #[UsesClass(\Membrane\OpenAPI\Specification\Arrays::class)] -#[UsesClass(\Membrane\OpenAPI\Specification\Numeric::class)] #[UsesClass(\Membrane\OpenAPI\Specification\Objects::class)] #[UsesClass(\Membrane\OpenAPI\Specification\Strings::class)] #[UsesClass(TrueFalse::class)] @@ -146,7 +145,7 @@ #[UsesClass(KeyValueSplit::class)] class OpenAPIRequestBuilderTest extends MembraneTestCase { - public const FIXTURES = __DIR__ . '/../../fixtures/OpenAPI/'; + public const string FIXTURES = __DIR__ . '/../../fixtures/OpenAPI/'; #[Test, TestDox('It will support the OpenAPIRequest Specification')] public function supportsRequestSpecification(): void diff --git a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php index 7bbeda30..5543d857 100644 --- a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php @@ -14,7 +14,6 @@ use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; use Membrane\OpenAPI\Specification\APISchema; use Membrane\OpenAPI\Specification\Arrays; -use Membrane\OpenAPI\Specification\Numeric; use Membrane\OpenAPI\Specification\Objects; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPI\Specification\Response; @@ -77,7 +76,6 @@ #[UsesClass(APISchema::class)] #[UsesClass(Arrays::class)] #[UsesClass(TrueFalse::class)] -#[UsesClass(Numeric::class)] #[UsesClass(Objects::class)] #[UsesClass(Strings::class)] #[UsesClass(Response::class)] diff --git a/tests/OpenAPI/Builder/ParameterBuilderTest.php b/tests/OpenAPI/Builder/ParameterBuilderTest.php index 54299f14..d1b7d163 100644 --- a/tests/OpenAPI/Builder/ParameterBuilderTest.php +++ b/tests/OpenAPI/Builder/ParameterBuilderTest.php @@ -31,7 +31,6 @@ #[UsesClass(OpenAPISpecification\Parameter::class)] #[UsesClass(OpenAPISpecification\APISchema::class)] #[UsesClass(OpenAPISpecification\Arrays::class)] -#[UsesClass(OpenAPISpecification\Numeric::class)] #[UsesClass(OpenAPISpecification\Strings::class)] #[UsesClass(Explode::class)] #[UsesClass(Processor\BeforeSet::class)] diff --git a/tests/OpenAPI/Builder/RequestBuilderTest.php b/tests/OpenAPI/Builder/RequestBuilderTest.php index 0586d942..3cdbbf7c 100644 --- a/tests/OpenAPI/Builder/RequestBuilderTest.php +++ b/tests/OpenAPI/Builder/RequestBuilderTest.php @@ -77,7 +77,6 @@ #[UsesClass(RequestProcessor::class)] #[UsesClass(APISchema::class)] #[UsesClass(\Membrane\OpenAPI\Specification\Arrays::class)] -#[UsesClass(\Membrane\OpenAPI\Specification\Numeric::class)] #[UsesClass(\Membrane\OpenAPI\Specification\Strings::class)] #[UsesClass(ToInt::class)] #[UsesClass(Explode::class)] diff --git a/tests/OpenAPI/Builder/ResponseBuilderTest.php b/tests/OpenAPI/Builder/ResponseBuilderTest.php index dbbafd68..c7914d6b 100644 --- a/tests/OpenAPI/Builder/ResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/ResponseBuilderTest.php @@ -15,7 +15,6 @@ use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; use Membrane\OpenAPI\Specification\APISchema; use Membrane\OpenAPI\Specification\Arrays; -use Membrane\OpenAPI\Specification\Numeric; use Membrane\OpenAPI\Specification\Objects; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPI\Specification\Response; @@ -79,7 +78,6 @@ #[UsesClass(APISchema::class)] #[UsesClass(Arrays::class)] #[UsesClass(TrueFalse::class)] -#[UsesClass(Numeric::class)] #[UsesClass(Objects::class)] #[UsesClass(Strings::class)] #[UsesClass(Response::class)] From 29488b12e9509edb9971d0a283c4e2daa0f90032 Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 17:07:06 +0100 Subject: [PATCH 19/47] Remove TrueFalse Specification --- src/OpenAPI/Builder/APIBuilder.php | 10 +- src/OpenAPI/Builder/TrueFalse.php | 36 ++--- src/OpenAPI/Specification/TrueFalse.php | 20 --- tests/OpenAPI/Builder/ObjectsTest.php | 1 - .../Builder/OpenAPIRequestBuilderTest.php | 2 - .../Builder/OpenAPIResponseBuilderTest.php | 2 - tests/OpenAPI/Builder/ResponseBuilderTest.php | 2 - tests/OpenAPI/Builder/TrueFalseTest.php | 134 ++++++++---------- 8 files changed, 82 insertions(+), 125 deletions(-) delete mode 100644 src/OpenAPI/Specification/TrueFalse.php diff --git a/src/OpenAPI/Builder/APIBuilder.php b/src/OpenAPI/Builder/APIBuilder.php index 5a05ec14..785b9112 100644 --- a/src/OpenAPI/Builder/APIBuilder.php +++ b/src/OpenAPI/Builder/APIBuilder.php @@ -93,16 +93,18 @@ public function fromSchema( $explode, )), - Type::Boolean => $this->getTrueFalseBuilder() - ->build(new OpenAPI\Specification\TrueFalse( + Type::Boolean => $this + ->getTrueFalseBuilder() + ->build( $fieldName, $schema->value, $convertFromString, $convertFromArray, $style, - )), + ), - Type::Integer, Type::Number => $this->getNumericBuilder() + Type::Integer, Type::Number => $this + ->getNumericBuilder() ->build( $fieldName, $schema->value, diff --git a/src/OpenAPI/Builder/TrueFalse.php b/src/OpenAPI/Builder/TrueFalse.php index d2d9b126..b71ce94a 100644 --- a/src/OpenAPI/Builder/TrueFalse.php +++ b/src/OpenAPI/Builder/TrueFalse.php @@ -10,6 +10,7 @@ use Membrane\Filter\Type\ToBool; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; +use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; @@ -19,32 +20,31 @@ use Membrane\Validator\String\BoolString; use Membrane\Validator\Type\IsBool; -class TrueFalse extends APIBuilder +final readonly class TrueFalse { - public function supports(Specification $specification): bool - { - return $specification instanceof \Membrane\OpenAPI\Specification\TrueFalse; - } - - public function build(Specification $specification): Processor - { - assert($specification instanceof \Membrane\OpenAPI\Specification\TrueFalse); - if (!in_array(Type::Boolean, $specification->keywords->types)) { + public function build( + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString = false, + bool $convertFromArray = false, + ?string $style = null, + ): Processor { + if (!in_array(Type::Boolean, $keywords->types)) { throw new \DomainException(sprintf( 'truefalse builder expected boolean types, received: %s', implode(', ', array_map( fn($t) => $t->value, - $specification->keywords->types, + $keywords->types, )), )); } - $chain = $specification->convertFromArray ? + $chain = $convertFromArray ? [new Implode(',')] : []; - if (isset($specification->style)) { - switch (Style::tryFrom($specification->style)) { + if (isset($style)) { + switch (Style::tryFrom($style)) { case Style::Matrix: $chain[] = new Matrix('boolean', false); break; @@ -59,19 +59,19 @@ public function build(Specification $specification): Processor } } - $chain = array_merge($chain, $specification->convertFromString ? + $chain = array_merge($chain, $convertFromString ? [new BoolString(), new ToBool()] : [new IsBool()]); if ( - $specification->keywords->enum !== null + $keywords->enum !== null ) { $chain[] = new Contained(array_map( fn(Value $v) => $v->value, - $specification->keywords->enum, + $keywords->enum, )); } - return new Field($specification->fieldName, ...$chain); + return new Field($fieldName, ...$chain); } } diff --git a/src/OpenAPI/Specification/TrueFalse.php b/src/OpenAPI/Specification/TrueFalse.php deleted file mode 100644 index 00d199b0..00000000 --- a/src/OpenAPI/Specification/TrueFalse.php +++ /dev/null @@ -1,20 +0,0 @@ -supports($specification)); - } - - #[Test] - public function doesNotSupportNonNumericSpecification(): void - { - $specification = self::createStub(\Membrane\Builder\Specification::class); - $sut = new TrueFalse(); - - self::assertFalse($sut->supports($specification)); - } - public static function specificationsToBuild(): array { return [ - 'input to convert from string' => [ - new Specification\TrueFalse( - '', - (new V30\Schema( - new Identifier('test'), - new Partial\Schema(type: 'boolean') - ))->value, - true, - ), - new Field('', new BoolString(), new ToBool()), + 'MVP' => [ + new Field('mvp', new IsBool()), + 'mvp', + new V30\Schema( + new Identifier('test'), + new Partial\Schema(type: 'boolean') + )->value, ], - 'strict input' => [ - new Specification\TrueFalse( - '', - (new V30\Schema( - new Identifier('test'), - new Partial\Schema(type: 'boolean') - ))->value, - false, - ), - new Field('', new IsBool()), + 'MVP from string' => [ + new Field('mvp-from-string', new BoolString(), new ToBool()), + 'mvp-from-string', + new V30\Schema( + new Identifier('test'), + new Partial\Schema(type: 'boolean') + )->value, + true, ], - 'detailed input to convert from string' => [ - new Specification\TrueFalse( - '', - (new V30\Schema( - new Identifier('test'), - new Partial\Schema( - type: 'boolean', - enum: [new Value(true), new Value(null)], - format: 'rather pointless boolean', - ) - ))->value, - true - ), - new Field('', new BoolString(), new ToBool(), new Contained([true, null])), + 'max' => [ + new Field('max', new IsBool(), new Contained([true, null])), + 'max', + new V30\Schema( + new Identifier('test'), + new Partial\Schema( + type: 'boolean', + enum: [new Value(true), new Value(null)], + format: 'rather pointless boolean', + ) + )->value, ], - 'strict detailed input' => [ - new Specification\TrueFalse( - '', - (new V30\Schema( - new Identifier('test'), - new Partial\Schema( - type: 'boolean', - enum: [new Value(true), new Value(null)], - format: 'rather pointless boolean', - ) - ))->value, - false - ), - new Field('', new IsBool(), new Contained([true, null])), + 'max from string' => [ + new Field('max-from-string', new BoolString(), new ToBool(), new Contained([true, null])), + 'max-from-string', + new V30\Schema( + new Identifier('test'), + new Partial\Schema( + type: 'boolean', + enum: [new Value(true), new Value(null)], + format: 'rather pointless boolean', + ) + )->value, + true, ], + ]; } #[Test] #[DataProvider('specificationsToBuild')] - public function buildTest(Specification\TrueFalse $specification, Processor $expected): void - { - $sut = new TrueFalse(); - - $actual = $sut->build($specification); - - self::assertEquals($expected, $actual); + public function buildTest( + Processor $expected, + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString = false, + bool $convertFromArray = false, + ?string $style = null, + ): void { + self::assertEquals( + $expected, + new TrueFalse()->build( + $fieldName, + $keywords, + $convertFromString, + $convertFromArray, + $style, + ), + ); } } From 3db23ebc8d1202f319f74e52c036ea60bf5346ab Mon Sep 17 00:00:00 2001 From: John Charman Date: Mon, 21 Sep 2026 17:37:02 +0100 Subject: [PATCH 20/47] Remove Strings Specification --- src/OpenAPI/Builder/APIBuilder.php | 11 +- src/OpenAPI/Builder/Strings.php | 51 ++++---- src/OpenAPI/Specification/APISchema.php | 3 +- src/OpenAPI/Specification/Strings.php | 21 ---- .../Command/CacheOpenAPIProcessorsTest.php | 1 - .../Service/CacheOpenAPIProcessorsTest.php | 1 - tests/MembraneTest.php | 1 - tests/OpenAPI/Builder/NumericTest.php | 19 ++- tests/OpenAPI/Builder/ObjectsTest.php | 1 - .../Builder/OpenAPIRequestBuilderTest.php | 1 - .../Builder/OpenAPIResponseBuilderTest.php | 2 - .../OpenAPI/Builder/ParameterBuilderTest.php | 1 - tests/OpenAPI/Builder/RequestBuilderTest.php | 1 - tests/OpenAPI/Builder/ResponseBuilderTest.php | 2 - tests/OpenAPI/Builder/StringsTest.php | 115 +++++++++--------- 15 files changed, 96 insertions(+), 135 deletions(-) delete mode 100644 src/OpenAPI/Specification/Strings.php diff --git a/src/OpenAPI/Builder/APIBuilder.php b/src/OpenAPI/Builder/APIBuilder.php index 785b9112..9beff703 100644 --- a/src/OpenAPI/Builder/APIBuilder.php +++ b/src/OpenAPI/Builder/APIBuilder.php @@ -110,16 +110,17 @@ public function fromSchema( $schema->value, $convertFromString, $convertFromArray, - $style + $style, ), - Type::String => ($this->getStringBuilder()) - ->build(new OpenAPI\Specification\Strings( + Type::String => $this + ->getStringBuilder() + ->build( $fieldName, $schema->value, $convertFromArray, - $style - )), + $style, + ), Type::Object => $this->getObjectBuilder() ->build(new OpenAPI\Specification\Objects( diff --git a/src/OpenAPI/Builder/Strings.php b/src/OpenAPI/Builder/Strings.php index 09a822a6..e81a46dc 100644 --- a/src/OpenAPI/Builder/Strings.php +++ b/src/OpenAPI/Builder/Strings.php @@ -4,13 +4,12 @@ namespace Membrane\OpenAPI\Builder; -use Membrane\Builder\Specification; -use Membrane\Filter\String\Explode; use Membrane\Filter\String\Implode; use Membrane\Filter\String\LeftTrim; use Membrane\Filter\String\ToUpperCase; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; +use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; @@ -23,33 +22,31 @@ use Membrane\Validator\Type\IsString; use Membrane\Validator\Utility\AnyOf; -class Strings extends APIBuilder +final readonly class Strings { - public function supports(Specification $specification): bool - { - return $specification instanceof \Membrane\OpenAPI\Specification\Strings; - } - - public function build(Specification $specification): Processor - { - assert($specification instanceof \Membrane\OpenAPI\Specification\Strings); - if (!in_array(Type::String, $specification->keywords->types)) { + public function build( + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromArray = false, + ?string $style = null, + ): Processor { + if (!in_array(Type::String, $keywords->types)) { throw new \DomainException(sprintf( 'strings builder expected string types, received: %s', implode(', ', array_map( fn($t) => $t->value, - $specification->keywords->types, + $keywords->types, )), )); } - $chain = $specification->convertFromArray ? + $chain = $convertFromArray ? [new Implode(',')] : []; - if (isset($specification->style)) { - switch (Style::tryFrom($specification->style)) { + if (isset($style)) { + switch (Style::tryFrom($style)) { case Style::Matrix: $chain[] = new Matrix('string', false); break; @@ -67,19 +64,19 @@ public function build(Specification $specification): Processor $chain[] = new IsString(); if ( - $specification->keywords->enum !== null + $keywords->enum !== null ) { $chain[] = new Contained(array_map( fn(Value $v) => $v->value, - $specification->keywords->enum, + $keywords->enum, )); } - if ($specification->keywords->format === 'date') { + if ($keywords->format === 'date') { $chain[] = new DateString('Y-m-d', true); } - if ($specification->keywords->format === 'date-time') { + if ($keywords->format === 'date-time') { $chain[] = new ToUpperCase(); $chain[] = new AnyOf( new DateString('Y-m-d\TH:i:sP', true), @@ -88,22 +85,22 @@ public function build(Specification $specification): Processor } if ( - $specification->keywords->maxLength !== null - || $specification->keywords->minLength > 0 + $keywords->maxLength !== null + || $keywords->minLength > 0 ) { $chain[] = new Length( - $specification->keywords->minLength, - $specification->keywords->maxLength, + $keywords->minLength, + $keywords->maxLength, ); } - if ($specification->keywords->pattern !== null) { + if ($keywords->pattern !== null) { $chain[] = new Regex(sprintf( '#%s#u', - str_replace('#', '\#', $specification->keywords->pattern), + str_replace('#', '\#', $keywords->pattern), )); } - return new Field($specification->fieldName, ...$chain); + return new Field($fieldName, ...$chain); } } diff --git a/src/OpenAPI/Specification/APISchema.php b/src/OpenAPI/Specification/APISchema.php index e27fee1d..7dcd3d5c 100644 --- a/src/OpenAPI/Specification/APISchema.php +++ b/src/OpenAPI/Specification/APISchema.php @@ -12,5 +12,6 @@ abstract class APISchema implements Specification public function __construct( public readonly string $fieldName, public V30\Keywords | V31\Keywords $keywords - ) {} + ) { + } } diff --git a/src/OpenAPI/Specification/Strings.php b/src/OpenAPI/Specification/Strings.php deleted file mode 100644 index f7af274b..00000000 --- a/src/OpenAPI/Specification/Strings.php +++ /dev/null @@ -1,21 +0,0 @@ -build( - $fieldName, - $keywords, - $convertFromString, - $convertFromArray, - $style, + self::assertEquals( + $expected, + new Numeric()->build( + $fieldName, + $keywords, + $convertFromString, + $convertFromArray, + $style, + ), ); - - self::assertEquals($expected, $actual); } } diff --git a/tests/OpenAPI/Builder/ObjectsTest.php b/tests/OpenAPI/Builder/ObjectsTest.php index 6c782d63..4f9bbbff 100644 --- a/tests/OpenAPI/Builder/ObjectsTest.php +++ b/tests/OpenAPI/Builder/ObjectsTest.php @@ -41,7 +41,6 @@ #[UsesClass(Strings::class)] #[UsesClass(AnyOf::class)] #[UsesClass(Specification\APISchema::class)] -#[UsesClass(Specification\Strings::class)] #[UsesClass(BeforeSet::class)] #[UsesClass(DefaultProcessor::class)] #[UsesClass(Field::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php index fca6395f..80e2a320 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php @@ -106,7 +106,6 @@ #[UsesClass(OneOf::class)] #[UsesClass(\Membrane\OpenAPI\Specification\Arrays::class)] #[UsesClass(\Membrane\OpenAPI\Specification\Objects::class)] -#[UsesClass(\Membrane\OpenAPI\Specification\Strings::class)] #[UsesClass(Explode::class)] #[UsesClass(Implode::class)] #[UsesClass(Tokenize::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php index b579c1b7..be5404e9 100644 --- a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php @@ -17,7 +17,6 @@ use Membrane\OpenAPI\Specification\Objects; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPI\Specification\Response; -use Membrane\OpenAPI\Specification\Strings; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; use Membrane\Processor; @@ -75,7 +74,6 @@ #[UsesClass(APISchema::class)] #[UsesClass(Arrays::class)] #[UsesClass(Objects::class)] -#[UsesClass(Strings::class)] #[UsesClass(Response::class)] #[UsesClass(BeforeSet::class)] #[UsesClass(Collection::class)] diff --git a/tests/OpenAPI/Builder/ParameterBuilderTest.php b/tests/OpenAPI/Builder/ParameterBuilderTest.php index d1b7d163..750ec907 100644 --- a/tests/OpenAPI/Builder/ParameterBuilderTest.php +++ b/tests/OpenAPI/Builder/ParameterBuilderTest.php @@ -31,7 +31,6 @@ #[UsesClass(OpenAPISpecification\Parameter::class)] #[UsesClass(OpenAPISpecification\APISchema::class)] #[UsesClass(OpenAPISpecification\Arrays::class)] -#[UsesClass(OpenAPISpecification\Strings::class)] #[UsesClass(Explode::class)] #[UsesClass(Processor\BeforeSet::class)] #[UsesClass(Processor\Collection::class)] diff --git a/tests/OpenAPI/Builder/RequestBuilderTest.php b/tests/OpenAPI/Builder/RequestBuilderTest.php index 3cdbbf7c..918dae9c 100644 --- a/tests/OpenAPI/Builder/RequestBuilderTest.php +++ b/tests/OpenAPI/Builder/RequestBuilderTest.php @@ -77,7 +77,6 @@ #[UsesClass(RequestProcessor::class)] #[UsesClass(APISchema::class)] #[UsesClass(\Membrane\OpenAPI\Specification\Arrays::class)] -#[UsesClass(\Membrane\OpenAPI\Specification\Strings::class)] #[UsesClass(ToInt::class)] #[UsesClass(Explode::class)] #[UsesClass(Implode::class)] diff --git a/tests/OpenAPI/Builder/ResponseBuilderTest.php b/tests/OpenAPI/Builder/ResponseBuilderTest.php index 1e16e147..4a9c70ca 100644 --- a/tests/OpenAPI/Builder/ResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/ResponseBuilderTest.php @@ -18,7 +18,6 @@ use Membrane\OpenAPI\Specification\Objects; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPI\Specification\Response; -use Membrane\OpenAPI\Specification\Strings; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method; use Membrane\Processor; use Membrane\Processor\AllOf; @@ -77,7 +76,6 @@ #[UsesClass(APISchema::class)] #[UsesClass(Arrays::class)] #[UsesClass(Objects::class)] -#[UsesClass(Strings::class)] #[UsesClass(Response::class)] #[UsesClass(BeforeSet::class)] #[UsesClass(Collection::class)] diff --git a/tests/OpenAPI/Builder/StringsTest.php b/tests/OpenAPI/Builder/StringsTest.php index 5c9f3f8c..8ae09b89 100644 --- a/tests/OpenAPI/Builder/StringsTest.php +++ b/tests/OpenAPI/Builder/StringsTest.php @@ -7,9 +7,8 @@ use Membrane\Filter\String\ToUpperCase; use Membrane\OpenAPI\Builder\APIBuilder; use Membrane\OpenAPI\Builder\Strings; -use Membrane\OpenAPI\Specification; use Membrane\OpenAPIReader\ValueObject\Partial; -use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30}; +use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; use Membrane\OpenAPIReader\ValueObject\Value; use Membrane\Processor; use Membrane\Processor\AnyOf; @@ -28,7 +27,6 @@ #[CoversClass(Strings::class)] #[CoversClass(APIBuilder::class)] #[UsesClass(AnyOf::class)] -#[UsesClass(Specification\Strings::class)] #[UsesClass(Field::class)] #[UsesClass(Contained::class)] #[UsesClass(DateString::class)] @@ -37,48 +35,32 @@ #[UsesClass(\Membrane\Validator\Utility\AnyOf::class)] class StringsTest extends TestCase { - #[Test] - public function supportsStringsSpecification(): void - { - $specification = self::createStub(Specification\Strings::class); - $sut = new Strings(); - - self::assertTrue($sut->supports($specification)); - } - - #[Test] - public function doesNotSupportNonStringsSpecification(): void - { - $specification = self::createStub(\Membrane\Builder\Specification::class); - $sut = new Strings(); - - self::assertFalse($sut->supports($specification)); - } - public static function specificationsToBuild(): array { return [ - 'minimum input' => [ - new Specification\Strings( - '', - (new V30\Schema(new Identifier('test'), new Partial\Schema(type: 'string')))->value, - ), - new Field('', new IsString()), + 'mvp' => [ + new Field('mvp', new IsString()), + 'mvp', + new V30\Schema( + new Identifier('test'), + new Partial\Schema(type: 'string'), + )->value, ], - 'date input' => [ - new Specification\Strings( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'string', format: 'date')))->value, + 'date' => [ + new Field( + 'date', + new IsString(), + new DateString('Y-m-d', true), ), - new Field('', new IsString(), new DateString('Y-m-d', true)), + 'date', + new V30\Schema( + new Identifier(''), + new Partial\Schema(type: 'string', format: 'date') + )->value, ], - 'date-time input' => [ - new Specification\Strings( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'string', format: 'date-time')))->value, - ), + 'date-time' => [ new Field( - '', + 'date-time', new IsString(), new ToUpperCase(), new \Membrane\Validator\Utility\AnyOf( @@ -86,41 +68,54 @@ public static function specificationsToBuild(): array new DateString('Y-m-d\TH:i:sp', true), ), ), + 'date-time', + new V30\Schema( + new Identifier(''), + new Partial\Schema(type: 'string', format: 'date-time'), + )->value, ], 'detailed input' => [ - new Specification\Strings( - '', - (new V30\Schema( - new Identifier(''), new Partial\Schema( - type: 'string', - enum: [new Value('1970/01/01'), new Value(null)], - maxLength: 100, - minLength: 0, - pattern: '.+', - format: 'date', - ) - ))->value, - ), new Field( - '', + 'max', new IsString(), new Contained(['1970/01/01', null]), new DateString('Y-m-d', true), new Length(0, 100), new Regex('#.+#u'), - ) + ), + 'max', + new V30\Schema( + new Identifier(''), + new Partial\Schema( + type: 'string', + enum: [new Value('1970/01/01'), new Value(null)], + maxLength: 100, + minLength: 0, + pattern: '.+', + format: 'date', + ), + )->value, ], ]; } #[DataProvider('specificationsToBuild')] #[Test] - public function buildTest(Specification\Strings $specification, Processor $expected): void - { - $sut = new Strings(); - - $actual = $sut->build($specification); - - self::assertEquals($expected, $actual); + public function buildTest( + Processor $expected, + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromArray = false, + ?string $style = null, + ): void { + self::assertEquals( + $expected, + new Strings()->build( + $fieldName, + $keywords, + $convertFromArray, + $style, + ), + ); } } From 98feefa3ddeda527223cd2f2202bd98ab1f58ce9 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 10:33:41 +0100 Subject: [PATCH 21/47] Remove Objects Specification --- src/OpenAPI/Builder/APIBuilder.php | 7 +- src/OpenAPI/Builder/Objects.php | 67 ++--- src/OpenAPI/Specification/Objects.php | 21 -- .../Command/CacheOpenAPIProcessorsTest.php | 1 - .../Service/CacheOpenAPIProcessorsTest.php | 1 - tests/MembraneTest.php | 1 - tests/OpenAPI/Builder/ObjectsTest.php | 265 +++++++++--------- .../Builder/OpenAPIRequestBuilderTest.php | 1 - .../Builder/OpenAPIResponseBuilderTest.php | 2 - tests/OpenAPI/Builder/ResponseBuilderTest.php | 1 - 10 files changed, 177 insertions(+), 190 deletions(-) delete mode 100644 src/OpenAPI/Specification/Objects.php diff --git a/src/OpenAPI/Builder/APIBuilder.php b/src/OpenAPI/Builder/APIBuilder.php index 9beff703..8ef3fab5 100644 --- a/src/OpenAPI/Builder/APIBuilder.php +++ b/src/OpenAPI/Builder/APIBuilder.php @@ -122,15 +122,16 @@ public function fromSchema( $style, ), - Type::Object => $this->getObjectBuilder() - ->build(new OpenAPI\Specification\Objects( + Type::Object => $this + ->getObjectBuilder() + ->build( $fieldName, $schema->value, $convertFromString, $convertFromArray, $style, $explode, - )), + ), Type::Null => new Field($fieldName, new IsNull()), }, diff --git a/src/OpenAPI/Builder/Objects.php b/src/OpenAPI/Builder/Objects.php index 92d741a2..9b5ad669 100644 --- a/src/OpenAPI/Builder/Objects.php +++ b/src/OpenAPI/Builder/Objects.php @@ -11,6 +11,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPI\Filter\FormatStyle\PipeDelimited; use Membrane\OpenAPI\Filter\FormatStyle\SpaceDelimited; +use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; @@ -26,50 +27,50 @@ class Objects extends APIBuilder { - public function supports(Specification $specification): bool - { - return $specification instanceof \Membrane\OpenAPI\Specification\Objects; - } - - public function build(Specification $specification): Processor - { - assert($specification instanceof \Membrane\OpenAPI\Specification\Objects); - if (!in_array(Type::Object, $specification->keywords->types)) { + public function build( + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString = false, + bool $convertFromArray = false, + ?string $style = null, + ?bool $explode = null, + ): Processor { + if (!in_array(Type::Object, $keywords->types)) { throw new \DomainException(sprintf( 'object builder expected object types, received: %s', implode(', ', array_map( fn($t) => $t->value, - $specification->keywords->types, + $keywords->types, )), )); } $beforeChain = []; - if ($specification->convertFromArray) { + if ($convertFromArray) { array_unshift($beforeChain, new Filter\String\Implode(',')); } - if (isset($specification->style)) { + if (isset($style)) { $beforeChain = array_merge( $beforeChain, - match (Style::tryFrom($specification->style)) { + match (Style::tryFrom($style)) { Style::Matrix => [ - new Matrix('object', $specification->explode ?? false), + new Matrix('object', $explode ?? false), ], Style::Label => [ new Filter\String\LeftTrim('.'), - $specification->explode ?? false ? + $explode ?? false ? new Filter\String\Tokenize('.=') : new Filter\String\Explode(','), ], Style::Simple => [ - $specification->explode === true ? + $explode === true ? new Filter\String\Tokenize(',=') : new Filter\String\Explode(','), ], Style::Form => [ - new Form('object', $specification->explode ?? true), + new Form('object', $explode ?? true), ], Style::SpaceDelimited => [new SpaceDelimited()], Style::PipeDelimited => [new PipeDelimited()], @@ -83,33 +84,33 @@ public function build(Specification $specification): Processor $beforeChain[] = new IsArray(); if ( - $specification->keywords->enum !== null + $keywords->enum !== null ) { $beforeChain[] = new Contained(array_map( fn(Value $v) => $v->value, - $specification->keywords->enum, + $keywords->enum, )); } - if (!empty($specification->keywords->required)) { + if (!empty($keywords->required)) { $beforeChain[] = new RequiredFields( - ...$specification->keywords->required + ...$keywords->required ); } - if ($specification->keywords->additionalProperties->value === false) { + if ($keywords->additionalProperties->value === false) { $beforeChain[] = new FixedFields( - ...array_keys($specification->keywords->properties) + ...array_keys($keywords->properties) ); } if ( - $specification->keywords->minProperties > 0 - || $specification->keywords->maxProperties !== null + $keywords->minProperties > 0 + || $keywords->maxProperties !== null ) { $beforeChain[] = new Count( - $specification->keywords->minProperties, - $specification->keywords->maxProperties, + $keywords->minProperties, + $keywords->maxProperties, ); } @@ -117,25 +118,25 @@ public function build(Specification $specification): Processor $fields = []; - foreach ($specification->keywords->properties as $key => $schema) { + foreach ($keywords->properties as $key => $schema) { $fields [] = $this->fromSchema( $schema, $key, - $specification->convertFromString, + $convertFromString, ); } - if (!is_bool($specification->keywords->additionalProperties->value)) { + if (!is_bool($keywords->additionalProperties->value)) { $fields [] = new DefaultProcessor( $this->fromSchema( - $specification->keywords->additionalProperties, + $keywords->additionalProperties, '', - $specification->convertFromString, + $convertFromString, ) ); } - $processor = new FieldSet($specification->fieldName, $beforeSet, ...$fields); + $processor = new FieldSet($fieldName, $beforeSet, ...$fields); return $processor; } diff --git a/src/OpenAPI/Specification/Objects.php b/src/OpenAPI/Specification/Objects.php deleted file mode 100644 index 2bbdf99f..00000000 --- a/src/OpenAPI/Specification/Objects.php +++ /dev/null @@ -1,21 +0,0 @@ -supports($specification)); - } - - #[Test] - public function doesNotSupportSpecificationsThatAreNotArrays(): void + public static function specificationsToBuild(): array { - $specification = self::createStub(\Membrane\Builder\Specification::class); - $sut = new Objects(); + /** + * @param array $enum + * @param array $required + * @param array $properties + */ + $keywordsV30 = static fn ( + ?array $enum = null, + ?int $maxProperties = null, + int $minProperties = 0, + array $required = [], + array $properties = [], + bool|Partial\Schema $additionalProperties = true, + ) => new V30\Schema(new Identifier(''), new Partial\Schema( + type: 'object', + enum: $enum, + maxProperties: $maxProperties, + minProperties: $minProperties, + required: $required, + properties: $properties, + additionalProperties: $additionalProperties, + ))->value; - self::assertFalse($sut->supports($specification)); - } + /** @param array $properties */ + $keywordsV31 = static fn ( + ?int $maxProperties = null, + int $minProperties = 0, + array $properties = [], + bool|Partial\Schema $additionalProperties = true, + ) => new V31\Schema(new Identifier(''), new Partial\Schema( + type: 'object', + maxProperties: $maxProperties, + minProperties: $minProperties, + properties: $properties, + additionalProperties: $additionalProperties, + ))->value; - public static function specificationsToBuild(): array - { return [ - '3.0 minimum input' => [ - new Specification\Objects( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'object')))->value, - ), - new FieldSet('', new BeforeSet(new IsArray())), + '3.0 mvp' => [ + new FieldSet('3.0-mvp', new BeforeSet(new IsArray())), + '3.0-mvp', + $keywordsV30(), ], - '3.0 minProperties greater than zero' => [ - new Specification\Objects( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'object', minProperties: 1)))->value, + '3.0 minProperties' => [ + new FieldSet( + '3.0-min', + new BeforeSet(new IsArray(), new Count(1)), ), - new FieldSet('', new BeforeSet(new IsArray(), new Count(1))), + '3.0-min', + $keywordsV30(minProperties: 1), ], '3.0 maxProperties' => [ - new Specification\Objects( - '', - (new V30\Schema(new Identifier(''), new Partial\Schema(type: 'object', maxProperties: 1)))->value, + new FieldSet( + '3.0-max', + new BeforeSet(new IsArray(), new Count(0, 1)), ), - new FieldSet('', new BeforeSet(new IsArray(), new Count(0, 1))), + '3.0-max', + $keywordsV30(maxProperties: 1), ], '3.0 minProperties and maxProperties' => [ - new Specification\Objects( - '', - (new V30\Schema( - new Identifier(''), new Partial\Schema( - type: 'object', - maxProperties: 1, - minProperties: 1, - ) - ))->value, + new FieldSet( + '3.0-min-max', + new BeforeSet(new IsArray(), new Count(1, 1)), ), - new FieldSet('', new BeforeSet(new IsArray(), new Count(1, 1))), + '3.0-min-max', + $keywordsV30(maxProperties: 1, minProperties: 1), ], '3.0 integer property' => [ - new Specification\Objects( - '', - (new V30\Schema( - new Identifier(''), new Partial\Schema( - type: 'object', - properties: ['a' => new Partial\Schema(type: 'integer')], - ) - ))->value, + new FieldSet( + '3.0-prop-int', + new BeforeSet(new IsArray()), + new Field('a', new IsInt()), + ), + '3.0-prop-int', + $keywordsV30( + properties: ['a' => new Partial\Schema(type: 'integer')], ), - new FieldSet('', new BeforeSet(new IsArray()), new Field('a', new IsInt())), ], '3.0 additionalProperties:false' => [ - new Specification\Objects( - '', - (new V30\Schema( - new Identifier(''), new Partial\Schema( - type: 'object', - properties: ['a' => new Partial\Schema(type: 'integer')], - additionalProperties: false, - ) - ))->value, + new FieldSet( + '3.0-additional', + new BeforeSet(new IsArray(), new FixedFields('a')), + new Field('a', new IsInt()), + ), + '3.0-additional', + $keywordsV30( + properties: ['a' => new Partial\Schema(type: 'integer')], + additionalProperties: false, ), - new FieldSet('', new BeforeSet(new IsArray(), new FixedFields('a')), new Field('a', new IsInt())), ], '3.0 property of string|integer' => [ - new Specification\Objects( - '', - (new V30\Schema( - new Identifier(''), new Partial\Schema( - type: 'object', - properties: ['a' => new Partial\Schema(anyOf: [ - new Partial\Schema(type: 'string'), - new Partial\Schema(type: 'integer'), - ])], - ) - ))->value, - ), new FieldSet( - '', + '3.0-prop-stringint', new BeforeSet(new IsArray()), - new AnyOf('a', new Field('Branch-1', new IsString()), new Field('Branch-2', new IsInt())), + new AnyOf( + 'a', + new Field('Branch-1', new IsString()), + new Field('Branch-2', new IsInt()), + ), + ), + '3.0-prop-stringint', + $keywordsV30( + properties: ['a' => new Partial\Schema(anyOf: [ + new Partial\Schema(type: 'string'), + new Partial\Schema(type: 'integer'), + ])], ), ], '3.1 property of string|integer' => [ - new Specification\Objects( - '', - (new V31\Schema( - new Identifier(''), new Partial\Schema( - type: 'object', - properties: ['a' => new Partial\Schema(type: ['string', 'integer'])], - ) - ))->value, - ), new FieldSet( - '', + '3.1-prop-int', new BeforeSet(new IsArray()), - new AnyOf('a', new Field('a', new IsString()), new Field('a', new IsInt())), + new AnyOf( + 'a', + new Field('a', new IsString()), + new Field('a', new IsInt()), + ), + ), + '3.1-prop-int', + $keywordsV31( + properties: ['a' => new Partial\Schema( + type: ['string', 'integer'], + )], ), ], 'complex additional properties' => [ - new Specification\Objects( - '', - (new V30\Schema( - new Identifier(''), new Partial\Schema( - type: 'object', - maxProperties: 5, - minProperties: 2, - additionalProperties: new Partial\Schema(oneOf: [ - new Partial\Schema(type: 'boolean'), - new Partial\Schema(type: 'integer'), - ]), - ) - ))->value, - ), new FieldSet( - '', + '3.0-additional-schema', new BeforeSet(new IsArray(), new Count(2, 5)), new DefaultProcessor( new OneOf( @@ -193,26 +190,19 @@ public static function specificationsToBuild(): array ) ) ), + '3.0-additional-schema', + $keywordsV30( + maxProperties: 5, + minProperties: 2, + additionalProperties: new Partial\Schema(oneOf: [ + new Partial\Schema(type: 'boolean'), + new Partial\Schema(type: 'integer'), + ]), + ), ], 'detailed input' => [ - new Specification\Objects( - '', - (new V30\Schema( - new Identifier(''), new Partial\Schema( - type: 'object', - enum: [new Value(['id' => 5, 'name' => 'Blink']), new Value(null)], - required: ['id', 'name'], - properties: [ - 'id' => new Partial\Schema(type: 'integer'), - 'name' => new Partial\Schema(type: 'string'), - ], - additionalProperties: new Partial\Schema(type: 'string'), - format: 'pet', - ) - ))->value, - ), new FieldSet( - '', + 'max-v-p', new BeforeSet( new IsArray(), new Contained([['id' => 5, 'name' => 'Blink'], null]), @@ -221,19 +211,42 @@ enum: [new Value(['id' => 5, 'name' => 'Blink']), new Value(null)], DefaultProcessor::fromFiltersAndValidators(new IsString()), new Field('id', new IsInt()), new Field('name', new IsString()) - ) + ), + 'max-v-p', + $keywordsV30( + enum: [new Value(['id' => 5, 'name' => 'Blink']), new Value(null)], + required: ['id', 'name'], + properties: [ + 'id' => new Partial\Schema(type: 'integer'), + 'name' => new Partial\Schema(type: 'string'), + ], + additionalProperties: new Partial\Schema(type: 'string'), + ), ], ]; } #[Test] #[DataProvider('specificationsToBuild')] - public function buildTest(Specification\Objects $specification, Processor $expected): void - { - $sut = new Objects(); - - $actual = $sut->build($specification); - - self::assertEquals($expected, $actual); + public function buildTest( + Processor $expected, + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString = false, + bool $convertFromArray = false, + ?string $style = null, + bool $explode = false, + ): void { + self::assertEquals( + $expected, + new Objects()->build( + $fieldName, + $keywords, + $convertFromString, + $convertFromArray, + $style, + $explode, + ), + ); } } diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php index 80e2a320..8d8715ec 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php @@ -105,7 +105,6 @@ #[UsesClass(AnyOf::class)] #[UsesClass(OneOf::class)] #[UsesClass(\Membrane\OpenAPI\Specification\Arrays::class)] -#[UsesClass(\Membrane\OpenAPI\Specification\Objects::class)] #[UsesClass(Explode::class)] #[UsesClass(Implode::class)] #[UsesClass(Tokenize::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php index be5404e9..f3fe1d97 100644 --- a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php @@ -14,7 +14,6 @@ use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; use Membrane\OpenAPI\Specification\APISchema; use Membrane\OpenAPI\Specification\Arrays; -use Membrane\OpenAPI\Specification\Objects; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPI\Specification\Response; use Membrane\OpenAPIReader\MembraneReader; @@ -73,7 +72,6 @@ #[UsesClass(OneOf::class)] #[UsesClass(APISchema::class)] #[UsesClass(Arrays::class)] -#[UsesClass(Objects::class)] #[UsesClass(Response::class)] #[UsesClass(BeforeSet::class)] #[UsesClass(Collection::class)] diff --git a/tests/OpenAPI/Builder/ResponseBuilderTest.php b/tests/OpenAPI/Builder/ResponseBuilderTest.php index 4a9c70ca..21ce27a4 100644 --- a/tests/OpenAPI/Builder/ResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/ResponseBuilderTest.php @@ -15,7 +15,6 @@ use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; use Membrane\OpenAPI\Specification\APISchema; use Membrane\OpenAPI\Specification\Arrays; -use Membrane\OpenAPI\Specification\Objects; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPI\Specification\Response; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method; From c499572e86de4fdee0b07ce96ed52089a33128e7 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 12:01:22 +0100 Subject: [PATCH 22/47] Remove Arrays Specification --- src/OpenAPI/Builder/APIBuilder.php | 7 +- src/OpenAPI/Builder/Arrays.php | 54 +++---- src/OpenAPI/Specification/Arrays.php | 21 --- tests/OpenAPI/Builder/ArraysTest.php | 231 ++++++++++++++------------- 4 files changed, 147 insertions(+), 166 deletions(-) delete mode 100644 src/OpenAPI/Specification/Arrays.php diff --git a/src/OpenAPI/Builder/APIBuilder.php b/src/OpenAPI/Builder/APIBuilder.php index 8ef3fab5..6d68ada6 100644 --- a/src/OpenAPI/Builder/APIBuilder.php +++ b/src/OpenAPI/Builder/APIBuilder.php @@ -83,15 +83,16 @@ public function fromSchema( $typeSpecificProcessors = array_map( fn ($t) => match ($t) { - Type::Array => $this->getArrayBuilder() - ->build(new OpenAPI\Specification\Arrays( + Type::Array => $this + ->getArrayBuilder() + ->build( $fieldName, $schema->value, $convertFromString, $convertFromArray, $style, $explode, - )), + ), Type::Boolean => $this ->getTrueFalseBuilder() diff --git a/src/OpenAPI/Builder/Arrays.php b/src/OpenAPI/Builder/Arrays.php index a1d8286b..538d9e47 100644 --- a/src/OpenAPI/Builder/Arrays.php +++ b/src/OpenAPI/Builder/Arrays.php @@ -10,6 +10,7 @@ use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPI\Filter\FormatStyle\PipeDelimited; use Membrane\OpenAPI\Filter\FormatStyle\SpaceDelimited; +use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Style; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Type; use Membrane\OpenAPIReader\ValueObject\Value; @@ -23,46 +24,45 @@ class Arrays extends APIBuilder { - public function supports(Specification $specification): bool - { - return $specification instanceof \Membrane\OpenAPI\Specification\Arrays; - } - - public function build(Specification $specification): Processor - { - assert($specification instanceof \Membrane\OpenAPI\Specification\Arrays); - if (!in_array(Type::Array, $specification->keywords->types)) { + public function build( + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString, + bool $convertFromArray, + ?string $style, + ?bool $explode, + ): Processor { + if (!in_array(Type::Array, $keywords->types)) { throw new \DomainException(sprintf( 'arrays builder expected array types, received: %s', implode(', ', array_map( fn($t) => $t->value, - $specification->keywords->types, + $keywords->types, )), )); } - - $beforeChain = $specification->convertFromArray ? + $beforeChain = $convertFromArray ? [new Filter\String\Implode(',')] : []; - if (isset($specification->style)) { + if (isset($style)) { $beforeChain = array_merge( $beforeChain, - match (Style::from($specification->style)) { + match (Style::from($style)) { Style::Matrix => [ - new Matrix('array', $specification->explode ?? false), + new Matrix('array', $explode ?? false), ], Style::Label => [ new Filter\String\LeftTrim('.'), new Filter\String\Explode( - $specification->explode ?? false ? + $explode ?? false ? '.' : ',' ), ], Style::Form => [ - new Form('array', $specification->explode ?? true), + new Form('array', $explode ?? true), ], Style::Simple => [ new Filter\String\Explode(',') @@ -77,37 +77,37 @@ public function build(Specification $specification): Processor $beforeChain[] = new IsList(); if ( - $specification->keywords->enum !== null + $keywords->enum !== null ) { $beforeChain[] = new Contained(array_map( fn(Value $v) => $v->value, - $specification->keywords->enum, + $keywords->enum, )); } if ( - $specification->keywords->minItems > 0 - || $specification->keywords->maxItems !== null + $keywords->minItems > 0 + || $keywords->maxItems !== null ) { $beforeChain[] = new Count( - $specification->keywords->minItems, - $specification->keywords->maxItems, + $keywords->minItems, + $keywords->maxItems, ); } - if ($specification->keywords->uniqueItems === true) { + if ($keywords->uniqueItems === true) { $beforeChain[] = new Unique(); } $beforeSet = new BeforeSet(...$beforeChain); $collection = new Collection( - $specification->fieldName, + $fieldName, $beforeSet, $this->fromSchema( - $specification->keywords->items, + $keywords->items, '', - $specification->convertFromString, + $convertFromString, ) ); diff --git a/src/OpenAPI/Specification/Arrays.php b/src/OpenAPI/Specification/Arrays.php deleted file mode 100644 index 4bf73f0d..00000000 --- a/src/OpenAPI/Specification/Arrays.php +++ /dev/null @@ -1,21 +0,0 @@ -supports($specification)); - } - - #[Test] - public function doesNotSupportNonArraysSpecification(): void - { - $specification = self::createStub(\Membrane\Builder\Specification::class); - $sut = new Arrays(); - - self::assertFalse($sut->supports($specification)); - } - public static function specificationsToBuild(): array { return [ '3.0 minimum input' => [ - new Specification\Arrays( - '', - (new V30\Schema( - new Identifier('test'), new Partial\Schema( - type: 'array', - ) - ))->value, + new Collection( + '3.0-mvp', + new BeforeSet(new IsList()), + new Field('', new Passes()), ), - new Collection('', new BeforeSet(new IsList()), new Field('', new Passes())), + '3.0-mvp', + self::keywordsV30(), ], - '3.1 minimum input' => [ - new Specification\Arrays( - '', - (new V31\Schema( - new Identifier('test'), new Partial\Schema( - type: 'array', - ) - ))->value, + '3.1-mvp' => [ + new Collection( + '3.1-mvp', + new BeforeSet(new IsList()), + new Field('', new Passes()), ), - new Collection('', new BeforeSet(new IsList()), new Field('', new Passes())), + '3.1-mvp', + self::keywordsV31(), ], '3.0 array' => [ - new Specification\Arrays( - '', - (new V30\Schema( - new Identifier('test'), new Partial\Schema( - type: 'array', - items: new Partial\Schema( - anyOf: [ - new Partial\Schema(type: 'string'), - new Partial\Schema(type: 'integer'), - ] - ) - ) - ))->value, - ), new Collection( - '', + '3.0-items-stringint', new BeforeSet(new IsList()), - new AnyOf('', new Field('Branch-1', new IsString()), new Field('Branch-2', new IsInt())), + new AnyOf( + '', + new Field('Branch-1', new IsString()), + new Field('Branch-2', new IsInt()), + ), ), - ], - '3.1 array' => [ - new Specification\Arrays( - '', - (new V31\Schema( - new Identifier('test'), new Partial\Schema( - type: 'array', - items: new Partial\Schema(type: ['string', 'integer']), + '3.0-items-stringint', + self::keywordsV30( + items: new Partial\Schema( + anyOf: [ + new Partial\Schema(type: 'string'), + new Partial\Schema(type: 'integer'), + ] ) - ))->value, ), + ], + '3.1 array' => [ new Collection( - '', + '3.1-items-stringint', new BeforeSet(new IsList()), - new AnyOf('', new Field('', new IsString()), new Field('', new IsInt())), + new AnyOf( + '', + new Field('', new IsString()), + new Field('', new IsInt()), + ), + ), + '3.1-items-stringint', + self::keywordsV31( + items: new Partial\Schema( + type: ['string', 'integer'], + ), ), ], '3.1 detailed input' => [ - new Specification\Arrays( - '', - (new V31\Schema( - new Identifier(''), new Partial\Schema( - type: 'array', - enum: [new Value([1, 2, 3]), new Value(null)], - nullable: false, - maxItems: 3, - minItems: 1, - uniqueItems: true, - items: new Partial\Schema(type: 'integer'), - format: 'array of ints', - ) - ))->value, - ), new Collection( - '', + '3.1-maxvp', new BeforeSet(new IsList(), new Contained([[1, 2, 3], null]), new Count(1, 3), new Unique()), new Field('', new IsInt()), ), + '3.1-maxvp', + self::keywordsV31( + enum: [new Value([1, 2, 3]), new Value(null)], + maxItems: 3, + minItems: 1, + uniqueItems: true, + items: new Partial\Schema(type: 'integer'), + ), ], '3.0 nullable items' => [ - new Specification\Arrays( - '', - (new V30\Schema( - new Identifier(''), new Partial\Schema( - type: 'array', - enum: [new Value([1, 2, 3]), new Value(null)], - maxItems: 3, - minItems: 1, - uniqueItems: true, - items: new Partial\Schema(type: 'integer', nullable: true), - format: 'array of ints', - ) - ))->value, - ), new Collection( - '', + '3.0-items-nullable', new BeforeSet(new IsList(), new Contained([[1, 2, 3], null]), new Count(1, 3), new Unique()), new AnyOf('', new Field('', new IsInt()), new Field('', new IsNull())), ), + '3.0-items-nullable', + self::keywordsV30( + enum: [new Value([1, 2, 3]), new Value(null)], + maxItems: 3, + minItems: 1, + uniqueItems: true, + items: new Partial\Schema(type: 'integer', nullable: true), + ), ], '3.1 nullable items' => [ - new Specification\Arrays( - '', - (new V31\Schema( - new Identifier(''), new Partial\Schema( - type: 'array', - enum: [new Value([1, 2, 3]), new Value(null)], - maxItems: 3, - minItems: 1, - uniqueItems: true, - items: new Partial\Schema(type: ['integer', 'null']), - format: 'array of ints', - ) - ))->value, - ), new Collection( - '', + '3.1-items-nullable', new BeforeSet(new IsList(), new Contained([[1, 2, 3], null]), new Count(1, 3), new Unique()), new AnyOf('', new Field('', new IsInt()), new Field('', new IsNull())), ), + '3.1-items-nullable', + self::keywordsV31( + enum: [new Value([1, 2, 3]), new Value(null)], + maxItems: 3, + minItems: 1, + uniqueItems: true, + items: new Partial\Schema(type: ['integer', 'null']), + ), ], ]; } #[Test] #[DataProvider('specificationsToBuild')] - public function buildTest(Specification\Arrays $specification, Processor $expected): void - { - $sut = new Arrays(); + public function buildTest( + Processor $expected, + string $fieldName, + V30\Keywords | V31\Keywords $keywords, + bool $convertFromString = false, + bool $convertFromArray = false, + ?string $style = null, + ?bool $explode = null, + ): void { + self::assertEquals($expected, new Arrays()->build( + $fieldName, + $keywords, + $convertFromString, + $convertFromArray, + $style, + $explode, + )); + } - $actual = $sut->build($specification); + private static function keywordsV30( + ?array $enum = null, + ?int $maxItems = null, + int $minItems = 0, + bool $uniqueItems = false, + ?Partial\Schema $items = null, + ): V30\Keywords { + return new V30\Schema(new Identifier(''), new Partial\Schema( + type: 'array', + enum: $enum, + maxItems: $maxItems, + minItems: $minItems, + uniqueItems: $uniqueItems, + items: $items, + ))->value; + } - self::assertEquals($expected, $actual); + private static function keywordsV31( + ?array $enum = null, + ?int $maxItems = null, + int $minItems = 0, + bool $uniqueItems = false, + ?Partial\Schema $items = null, + ): V31\Keywords { + return new V31\Schema(new Identifier(''), new Partial\Schema( + type: 'array', + enum: $enum, + maxItems: $maxItems, + minItems: $minItems, + uniqueItems: $uniqueItems, + items: $items, + ))->value; } } From 1bbe47c4b43f6abc2aa38707eb0487d2a7f79e49 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 12:13:43 +0100 Subject: [PATCH 23/47] Refactor ObjectsTest --- tests/OpenAPI/Builder/ObjectsTest.php | 103 +++++++++++++++----------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/tests/OpenAPI/Builder/ObjectsTest.php b/tests/OpenAPI/Builder/ObjectsTest.php index 57936244..253123ab 100644 --- a/tests/OpenAPI/Builder/ObjectsTest.php +++ b/tests/OpenAPI/Builder/ObjectsTest.php @@ -54,47 +54,12 @@ class ObjectsTest extends TestCase { public static function specificationsToBuild(): array { - /** - * @param array $enum - * @param array $required - * @param array $properties - */ - $keywordsV30 = static fn ( - ?array $enum = null, - ?int $maxProperties = null, - int $minProperties = 0, - array $required = [], - array $properties = [], - bool|Partial\Schema $additionalProperties = true, - ) => new V30\Schema(new Identifier(''), new Partial\Schema( - type: 'object', - enum: $enum, - maxProperties: $maxProperties, - minProperties: $minProperties, - required: $required, - properties: $properties, - additionalProperties: $additionalProperties, - ))->value; - - /** @param array $properties */ - $keywordsV31 = static fn ( - ?int $maxProperties = null, - int $minProperties = 0, - array $properties = [], - bool|Partial\Schema $additionalProperties = true, - ) => new V31\Schema(new Identifier(''), new Partial\Schema( - type: 'object', - maxProperties: $maxProperties, - minProperties: $minProperties, - properties: $properties, - additionalProperties: $additionalProperties, - ))->value; return [ '3.0 mvp' => [ new FieldSet('3.0-mvp', new BeforeSet(new IsArray())), '3.0-mvp', - $keywordsV30(), + self::keywordsV30(), ], '3.0 minProperties' => [ new FieldSet( @@ -102,7 +67,7 @@ enum: $enum, new BeforeSet(new IsArray(), new Count(1)), ), '3.0-min', - $keywordsV30(minProperties: 1), + self::keywordsV30(minProperties: 1), ], '3.0 maxProperties' => [ new FieldSet( @@ -110,7 +75,7 @@ enum: $enum, new BeforeSet(new IsArray(), new Count(0, 1)), ), '3.0-max', - $keywordsV30(maxProperties: 1), + self::keywordsV30(maxProperties: 1), ], '3.0 minProperties and maxProperties' => [ new FieldSet( @@ -118,7 +83,7 @@ enum: $enum, new BeforeSet(new IsArray(), new Count(1, 1)), ), '3.0-min-max', - $keywordsV30(maxProperties: 1, minProperties: 1), + self::keywordsV30(maxProperties: 1, minProperties: 1), ], '3.0 integer property' => [ new FieldSet( @@ -127,7 +92,7 @@ enum: $enum, new Field('a', new IsInt()), ), '3.0-prop-int', - $keywordsV30( + self::keywordsV30( properties: ['a' => new Partial\Schema(type: 'integer')], ), ], @@ -138,7 +103,7 @@ enum: $enum, new Field('a', new IsInt()), ), '3.0-additional', - $keywordsV30( + self::keywordsV30( properties: ['a' => new Partial\Schema(type: 'integer')], additionalProperties: false, ), @@ -154,7 +119,7 @@ enum: $enum, ), ), '3.0-prop-stringint', - $keywordsV30( + self::keywordsV30( properties: ['a' => new Partial\Schema(anyOf: [ new Partial\Schema(type: 'string'), new Partial\Schema(type: 'integer'), @@ -172,7 +137,7 @@ enum: $enum, ), ), '3.1-prop-int', - $keywordsV31( + self::keywordsV31( properties: ['a' => new Partial\Schema( type: ['string', 'integer'], )], @@ -191,7 +156,7 @@ enum: $enum, ) ), '3.0-additional-schema', - $keywordsV30( + self::keywordsV30( maxProperties: 5, minProperties: 2, additionalProperties: new Partial\Schema(oneOf: [ @@ -213,7 +178,7 @@ enum: $enum, new Field('name', new IsString()) ), 'max-v-p', - $keywordsV30( + self::keywordsV30( enum: [new Value(['id' => 5, 'name' => 'Blink']), new Value(null)], required: ['id', 'name'], properties: [ @@ -249,4 +214,52 @@ public function buildTest( ), ); } + + /** + * @param array $enum + * @param array $required + * @param array $properties + */ + private static function keywordsV30( + ?array $enum = null, + ?int $maxProperties = null, + int $minProperties = 0, + array $required = [], + array $properties = [], + bool|Partial\Schema $additionalProperties = true, + ): V30\Keywords { + return new V30\Schema(new Identifier(''), new Partial\Schema( + type: 'object', + enum: $enum, + maxProperties: $maxProperties, + minProperties: $minProperties, + required: $required, + properties: $properties, + additionalProperties: $additionalProperties, + ))->value; + } + + /** + * @param array $enum + * @param array $required + * @param array $properties + */ + private static function keywordsV31( + ?array $enum = null, + ?int $maxProperties = null, + int $minProperties = 0, + array $required = [], + array $properties = [], + bool|Partial\Schema $additionalProperties = true, + ): V31\Keywords { + return new V31\Schema(new Identifier(''), new Partial\Schema( + type: 'object', + enum: $enum, + maxProperties: $maxProperties, + minProperties: $minProperties, + required: $required, + properties: $properties, + additionalProperties: $additionalProperties, + ))->value; + } } From c70afea49249acca777ede4d03bec517f23a38b1 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 12:16:59 +0100 Subject: [PATCH 24/47] Optimize imports in Objects test --- src/OpenAPI/Builder/APIBuilder.php | 1 - tests/OpenAPI/Builder/ObjectsTest.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/OpenAPI/Builder/APIBuilder.php b/src/OpenAPI/Builder/APIBuilder.php index 6d68ada6..2bf23cc8 100644 --- a/src/OpenAPI/Builder/APIBuilder.php +++ b/src/OpenAPI/Builder/APIBuilder.php @@ -4,7 +4,6 @@ namespace Membrane\OpenAPI\Builder; -use Membrane\Builder\Builder; use Membrane\OpenAPI; use Membrane\OpenAPIReader\ValueObject\Valid\{Enum\Type, V30, V31}; use Membrane\Processor; diff --git a/tests/OpenAPI/Builder/ObjectsTest.php b/tests/OpenAPI/Builder/ObjectsTest.php index 253123ab..29eb9796 100644 --- a/tests/OpenAPI/Builder/ObjectsTest.php +++ b/tests/OpenAPI/Builder/ObjectsTest.php @@ -9,7 +9,6 @@ use Membrane\OpenAPI\Builder\Objects; use Membrane\OpenAPI\Builder\Strings; use Membrane\OpenAPI\Builder\TrueFalse; -use Membrane\OpenAPI\Specification; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; use Membrane\OpenAPIReader\ValueObject\Value; @@ -40,7 +39,6 @@ #[UsesClass(TrueFalse::class)] #[UsesClass(Strings::class)] #[UsesClass(AnyOf::class)] -#[UsesClass(Specification\APISchema::class)] #[UsesClass(BeforeSet::class)] #[UsesClass(DefaultProcessor::class)] #[UsesClass(Field::class)] From 268d85c74f891173a86b6351e6e0e8fda01e09b9 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 12:27:56 +0100 Subject: [PATCH 25/47] Reimplement old exception messages in build --- src/OpenAPI/Builder/Arrays.php | 12 +++++------- src/OpenAPI/Builder/Numeric.php | 9 +++++---- src/OpenAPI/Builder/Objects.php | 12 +++++------- src/OpenAPI/Builder/Strings.php | 12 +++++------- src/OpenAPI/Builder/TrueFalse.php | 12 +++++------- 5 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/OpenAPI/Builder/Arrays.php b/src/OpenAPI/Builder/Arrays.php index 538d9e47..17d215cf 100644 --- a/src/OpenAPI/Builder/Arrays.php +++ b/src/OpenAPI/Builder/Arrays.php @@ -6,6 +6,7 @@ use Membrane\Builder\Specification; use Membrane\Filter; +use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPI\Filter\FormatStyle\PipeDelimited; @@ -33,13 +34,10 @@ public function build( ?bool $explode, ): Processor { if (!in_array(Type::Array, $keywords->types)) { - throw new \DomainException(sprintf( - 'arrays builder expected array types, received: %s', - implode(', ', array_map( - fn($t) => $t->value, - $keywords->types, - )), - )); + throw CannotProcessSpecification::mismatchedType( + ['array'], + array_map(fn($t) => $t->value, $keywords->types), + ); } $beforeChain = $convertFromArray ? diff --git a/src/OpenAPI/Builder/Numeric.php b/src/OpenAPI/Builder/Numeric.php index 676ad949..ddf7b284 100644 --- a/src/OpenAPI/Builder/Numeric.php +++ b/src/OpenAPI/Builder/Numeric.php @@ -9,6 +9,7 @@ use Membrane\Filter\Type\ToFloat; use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; +use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; @@ -43,10 +44,10 @@ public function build( } elseif (in_array(Type::Number, $types)) { $type = Type::Number->value; } else { - throw new \DomainException(sprintf( - 'numeric builder expected integer or number types, received: %s', - implode(', ', array_map(fn($t) => $t->value, $types)), - )); + throw CannotProcessSpecification::mismatchedType( + ['number', 'integer'], + array_map(fn($t) => $t->value, $keywords->types), + ); } $chain = $convertFromArray ? diff --git a/src/OpenAPI/Builder/Objects.php b/src/OpenAPI/Builder/Objects.php index 9b5ad669..fd206c71 100644 --- a/src/OpenAPI/Builder/Objects.php +++ b/src/OpenAPI/Builder/Objects.php @@ -6,6 +6,7 @@ use Membrane\Builder\Specification; use Membrane\Filter; +use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\Filter\FormatStyle\DeepObject; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; @@ -36,13 +37,10 @@ public function build( ?bool $explode = null, ): Processor { if (!in_array(Type::Object, $keywords->types)) { - throw new \DomainException(sprintf( - 'object builder expected object types, received: %s', - implode(', ', array_map( - fn($t) => $t->value, - $keywords->types, - )), - )); + throw CannotProcessSpecification::mismatchedType( + ['object'], + array_map(fn($t) => $t->value, $keywords->types), + ); } $beforeChain = []; diff --git a/src/OpenAPI/Builder/Strings.php b/src/OpenAPI/Builder/Strings.php index e81a46dc..560bf579 100644 --- a/src/OpenAPI/Builder/Strings.php +++ b/src/OpenAPI/Builder/Strings.php @@ -7,6 +7,7 @@ use Membrane\Filter\String\Implode; use Membrane\Filter\String\LeftTrim; use Membrane\Filter\String\ToUpperCase; +use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; @@ -31,13 +32,10 @@ public function build( ?string $style = null, ): Processor { if (!in_array(Type::String, $keywords->types)) { - throw new \DomainException(sprintf( - 'strings builder expected string types, received: %s', - implode(', ', array_map( - fn($t) => $t->value, - $keywords->types, - )), - )); + throw CannotProcessSpecification::mismatchedType( + ['string'], + array_map(fn($t) => $t->value, $keywords->types), + ); } diff --git a/src/OpenAPI/Builder/TrueFalse.php b/src/OpenAPI/Builder/TrueFalse.php index b71ce94a..b0d79a0c 100644 --- a/src/OpenAPI/Builder/TrueFalse.php +++ b/src/OpenAPI/Builder/TrueFalse.php @@ -8,6 +8,7 @@ use Membrane\Filter\String\Implode; use Membrane\Filter\String\LeftTrim; use Membrane\Filter\Type\ToBool; +use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; @@ -30,13 +31,10 @@ public function build( ?string $style = null, ): Processor { if (!in_array(Type::Boolean, $keywords->types)) { - throw new \DomainException(sprintf( - 'truefalse builder expected boolean types, received: %s', - implode(', ', array_map( - fn($t) => $t->value, - $keywords->types, - )), - )); + throw CannotProcessSpecification::mismatchedType( + ['boolean'], + array_map(fn($t) => $t->value, $keywords->types), + ); } $chain = $convertFromArray ? From fb280a03edc0598f347427054d74bf2b58bc9382 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 12:35:23 +0100 Subject: [PATCH 26/47] Remove PHP UsesClass attributes for removed Specifications --- src/OpenAPI/Specification/APISchema.php | 17 ----------------- .../Command/CacheOpenAPIProcessorsTest.php | 2 -- .../Service/CacheOpenAPIProcessorsTest.php | 2 -- tests/MembraneTest.php | 2 -- .../Builder/OpenAPIResponseBuilderTest.php | 4 ---- tests/OpenAPI/Builder/ParameterBuilderTest.php | 2 -- tests/OpenAPI/Builder/RequestBuilderTest.php | 3 --- tests/OpenAPI/Builder/ResponseBuilderTest.php | 5 ----- 8 files changed, 37 deletions(-) delete mode 100644 src/OpenAPI/Specification/APISchema.php diff --git a/src/OpenAPI/Specification/APISchema.php b/src/OpenAPI/Specification/APISchema.php deleted file mode 100644 index 7dcd3d5c..00000000 --- a/src/OpenAPI/Specification/APISchema.php +++ /dev/null @@ -1,17 +0,0 @@ - Date: Tue, 22 Sep 2026 13:00:45 +0100 Subject: [PATCH 27/47] Remove ParameterBuilder --- src/OpenAPI/Builder/APIBuilder.php | 2 +- src/OpenAPI/Builder/OpenAPIRequestBuilder.php | 52 +++++---- src/OpenAPI/Builder/ParameterBuilder.php | 41 ------- .../Command/CacheOpenAPIProcessorsTest.php | 1 - .../Service/CacheOpenAPIProcessorsTest.php | 1 - tests/MembraneTest.php | 1 - .../Builder/OpenAPIRequestBuilderTest.php | 19 +++- .../OpenAPI/Builder/ParameterBuilderTest.php | 100 ------------------ tests/OpenAPI/Builder/RequestBuilderTest.php | 2 - tests/OpenAPI/Specification/ParameterTest.php | 12 --- 10 files changed, 49 insertions(+), 182 deletions(-) delete mode 100644 src/OpenAPI/Builder/ParameterBuilder.php delete mode 100644 tests/OpenAPI/Builder/ParameterBuilderTest.php diff --git a/src/OpenAPI/Builder/APIBuilder.php b/src/OpenAPI/Builder/APIBuilder.php index 2bf23cc8..6b27383c 100644 --- a/src/OpenAPI/Builder/APIBuilder.php +++ b/src/OpenAPI/Builder/APIBuilder.php @@ -14,7 +14,7 @@ use Membrane\Validator\Type\IsNull; use Membrane\Validator\Utility; -abstract class APIBuilder +class APIBuilder { private Arrays $arrayBuilder; private TrueFalse $trueFalseBuilder; diff --git a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php index 41cba9ad..44100d2d 100644 --- a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php +++ b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php @@ -6,10 +6,10 @@ use Membrane\Builder\Builder; use Membrane\Builder\Specification; +use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Filter; use Membrane\OpenAPI\Processor\Request as RequestProcessor; use Membrane\OpenAPI\Specification\OpenAPIRequest; -use Membrane\OpenAPI\Specification\Parameter; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\In; use Membrane\Processor; use Membrane\Processor\BeforeSet; @@ -20,7 +20,7 @@ class OpenAPIRequestBuilder implements Builder { - private ParameterBuilder $parameterBuilder; + private APIBuilder $apiBuilder; public function supports(Specification $specification): bool { @@ -48,7 +48,7 @@ private function fromRequestBody(OpenAPIRequest $specification): Processor return new Field('requestBody', new Passes()); } - return $this->getParameterBuilder()->fromSchema( + return $this->getApiBuilder()->fromSchema( $specification->requestBodySchema, 'requestBody', ); @@ -57,20 +57,25 @@ private function fromRequestBody(OpenAPIRequest $specification): Processor /** @return Processor[] */ private function fromParameters(OpenAPIRequest $specification): array { - $parameters = array_map( - fn($p) => new Parameter($p), + $queryParameters = array_filter( $specification->parameters, + fn($p) => $p->in === In::Query, ); - $queryParameters = array_filter($parameters, fn($p) => $p->in === 'query'); - - $location = fn(array $chain) => ['required' => [], 'fields' => [], 'beforeSet' => $chain]; + $location = fn(array $chain) => [ + 'required' => [], + 'fields' => [], + 'beforeSet' => $chain, + ]; $locations = [ 'path' => $location([new Filter\PathMatcher($specification->pathParameterExtractor)]), 'query' => $location([new Filter\QueryStringToArray(array_combine( array_map(fn($p) => $p->name, $queryParameters), array_map( - fn($p) => ['style' => $p->style, 'explode' => $p->explode], + fn($p) => [ + 'style' => $p->style->value, + 'explode' => $p->explode, + ], $queryParameters, ) ))]), @@ -78,17 +83,25 @@ private function fromParameters(OpenAPIRequest $specification): array 'cookie' => $location([]), ]; - foreach ($parameters as $parameter) { - $locations[$parameter->in]['fields'][] = $this - ->getParameterBuilder() - ->fromParameter( - $parameter, + foreach ($specification->parameters as $parameter) { + if ($parameter->hasMediaType() && $parameter->getMediaType() !== 'application/json') { + assert($parameter->getMediaType() !== null); + throw CannotProcessOpenAPI::unsupportedMediaTypes($parameter->getMediaType()); + } + + $locations[$parameter->in->value]['fields'][] = $this + ->getApiBuilder() + ->fromSchema( + $parameter->getSchema(), + $parameter->name, true, - $parameter->in === In::Header->value, + $parameter->in === In::Header, + $parameter->style->value, + $parameter->explode, ); if ($parameter->required) { - $locations[$parameter->in]['required'][] = $parameter->name; + $locations[$parameter->in->value]['required'][] = $parameter->name; } } @@ -112,11 +125,8 @@ private function fromParameters(OpenAPIRequest $specification): array return $fieldSets; } - private function getParameterBuilder(): ParameterBuilder + private function getApiBuilder(): APIBuilder { - if (!isset($this->parameterBuilder)) { - $this->parameterBuilder = new ParameterBuilder(); - } - return $this->parameterBuilder; + return $this->apiBuilder ??= new APIBuilder(); } } diff --git a/src/OpenAPI/Builder/ParameterBuilder.php b/src/OpenAPI/Builder/ParameterBuilder.php deleted file mode 100644 index b615b34d..00000000 --- a/src/OpenAPI/Builder/ParameterBuilder.php +++ /dev/null @@ -1,41 +0,0 @@ -fromParameter($specification); - } - - public function fromParameter( - Parameter $specification, - bool $convertFromString = false, - bool $convertFromArray = false, - ): Processor { - $schemaProcessor = $this->fromSchema( - $specification->schema, - $specification->name, - $convertFromString, - $convertFromArray, - $specification->style, - $specification->explode, - ); - - return $schemaProcessor; - } -} diff --git a/tests/Console/Command/CacheOpenAPIProcessorsTest.php b/tests/Console/Command/CacheOpenAPIProcessorsTest.php index dcfb6183..7d740b25 100644 --- a/tests/Console/Command/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Command/CacheOpenAPIProcessorsTest.php @@ -38,7 +38,6 @@ #[UsesClass(Template\ResponseBuilder::class)] #[UsesClass(Template\RequestBuilder::class)] #[UsesClass(Builder\APIBuilder::class)] -#[UsesClass(Builder\ParameterBuilder::class)] #[UsesClass(Builder\Arrays::class)] #[UsesClass(Builder\Numeric::class)] #[UsesClass(Builder\Strings::class)] diff --git a/tests/Console/Service/CacheOpenAPIProcessorsTest.php b/tests/Console/Service/CacheOpenAPIProcessorsTest.php index 3c4ad28f..be092425 100644 --- a/tests/Console/Service/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Service/CacheOpenAPIProcessorsTest.php @@ -38,7 +38,6 @@ #[UsesClass(Builder\Numeric::class)] #[UsesClass(Builder\Strings::class)] #[UsesClass(Builder\Objects::class)] -#[UsesClass(Builder\ParameterBuilder::class)] #[UsesClass(Builder\OpenAPIRequestBuilder::class)] #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(PathMatcher::class)] diff --git a/tests/MembraneTest.php b/tests/MembraneTest.php index a6b247eb..d81075a0 100644 --- a/tests/MembraneTest.php +++ b/tests/MembraneTest.php @@ -37,7 +37,6 @@ #[UsesClass(Builder\Numeric::class)] #[UsesClass(Builder\Strings::class)] #[UsesClass(Builder\Objects::class)] -#[UsesClass(Builder\ParameterBuilder::class)] #[UsesClass(Builder\OpenAPIRequestBuilder::class)] #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(Builder\RequestBuilder::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php index 8d8715ec..46661cde 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php @@ -20,7 +20,6 @@ use Membrane\OpenAPI\Builder\Numeric; use Membrane\OpenAPI\Builder\Objects; use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder; -use Membrane\OpenAPI\Builder\ParameterBuilder; use Membrane\OpenAPI\Builder\RequestBuilder; use Membrane\OpenAPI\Builder\Strings; use Membrane\OpenAPI\ContentType; @@ -81,7 +80,6 @@ use Psr\Http\Message\ServerRequestInterface; #[CoversClass(OpenAPIRequestBuilder::class)] -#[CoversClass(ParameterBuilder::class)] #[CoversClass(APIBuilder::class)] #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] @@ -143,6 +141,23 @@ class OpenAPIRequestBuilderTest extends MembraneTestCase { public const string FIXTURES = __DIR__ . '/../../fixtures/OpenAPI/'; + #[Test, TestDox('Exceptions will be thrown for parameters with unsupported content types')] + public function throwsExceptionForUnsupportedContentTypes(): void + { + self::markTestSkipped(); + + $parameter = new V30\Parameter(new Identifier('test'), new Partial\Parameter( + name: 'test-param', + in: 'query', + content: [new Partial\MediaType(contentType: 'application/pdf', schema: new Partial\Schema())], + )); + + self::expectExceptionObject(CannotProcessOpenAPI::unsupportedMediaTypes('application/pdf')); + + $sut = new OpenAPIRequestBuilder(); + new $sut->build($parameter); + } + #[Test, TestDox('It will support the OpenAPIRequest Specification')] public function supportsRequestSpecification(): void { diff --git a/tests/OpenAPI/Builder/ParameterBuilderTest.php b/tests/OpenAPI/Builder/ParameterBuilderTest.php deleted file mode 100644 index b6b3e33b..00000000 --- a/tests/OpenAPI/Builder/ParameterBuilderTest.php +++ /dev/null @@ -1,100 +0,0 @@ -sut = new OpenAPIBuilder\ParameterBuilder(); - } - - #[Test, TestDox('It does not support any Specifications other than Parameter')] - public function doesNotSupportSpecificationsOtherThanParameter(): void - { - self::assertFalse($this->sut->supports(self::createStub(Specification::class))); - } - - #[Test, TestDox('It supports the Parameter Specification')] - public function supportsTheParameterSpecification(): void - { - self::assertTrue($this->sut->supports(self::createStub(OpenAPISpecification\Parameter::class))); - } - - public static function provideParameterSpecificationsToBuildFrom(): array - { - return [ - [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'path', - required: true, - schema: new Partial\Schema(type: 'integer'), - )), - new Processor\Field('id', new IsInt()) - ], - [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'tags', - in: 'query', - style: 'spaceDelimited', - explode: false, - schema: new Partial\Schema( - type: 'array', - items: new Partial\Schema(type: 'string'), - ), - )), - new Processor\Collection( - 'tags', - new Processor\BeforeSet(new SpaceDelimited(), new IsList()), - new Processor\Field('', new IsString()) - ), - ], - ]; - } - - #[Test, TestDox('It Builds a Processor that can validate against the Parameter Specification')] - #[DataProvider('provideParameterSpecificationsToBuildFrom')] - public function itBuildsProcessorsForParameters( - V30\Parameter|V31\Parameter $parameter, - Processor $expectedProcessor - ): void { - $actualProcessor = $this->sut - ->build(new OpenAPISpecification\Parameter($parameter)); - - self::assertEquals($expectedProcessor, $actualProcessor); - } -} diff --git a/tests/OpenAPI/Builder/RequestBuilderTest.php b/tests/OpenAPI/Builder/RequestBuilderTest.php index 5405f4be..04f4b1a2 100644 --- a/tests/OpenAPI/Builder/RequestBuilderTest.php +++ b/tests/OpenAPI/Builder/RequestBuilderTest.php @@ -13,7 +13,6 @@ use Membrane\OpenAPI\Builder\Arrays; use Membrane\OpenAPI\Builder\Numeric; use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder; -use Membrane\OpenAPI\Builder\ParameterBuilder; use Membrane\OpenAPI\Builder\RequestBuilder; use Membrane\OpenAPI\Builder\Strings; use Membrane\OpenAPI\ContentType; @@ -68,7 +67,6 @@ #[UsesClass(Numeric::class)] #[UsesClass(Strings::class)] #[UsesClass(Parameter::class)] -#[UsesClass(ParameterBuilder::class)] #[UsesClass(QueryStringToArray::class)] #[UsesClass(Form::class)] #[UsesClass(PathMatcher::class)] diff --git a/tests/OpenAPI/Specification/ParameterTest.php b/tests/OpenAPI/Specification/ParameterTest.php index 4447c13f..72f65dff 100644 --- a/tests/OpenAPI/Specification/ParameterTest.php +++ b/tests/OpenAPI/Specification/ParameterTest.php @@ -18,19 +18,7 @@ #[CoversClass(CannotProcessOpenAPI::class)] class ParameterTest extends TestCase { - #[Test, TestDox('Exceptions will be thrown for parameters with unsupported content types')] - public function throwsExceptionForUnsupportedContentTypes(): void - { - $parameter = new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'test-param', - in: 'query', - content: [new Partial\MediaType(contentType: 'application/pdf', schema: new Partial\Schema())], - )); - - self::expectExceptionObject(CannotProcessOpenAPI::unsupportedMediaTypes('application/pdf')); - new Parameter($parameter); - } public static function provideValidParameters(): array { From d2dc52f538789a060c77489a8d61df50edf5b3e3 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 13:08:15 +0100 Subject: [PATCH 28/47] Remove old PHPUnit attributes --- tests/Console/Command/CacheOpenAPIProcessorsTest.php | 1 - tests/Console/Service/CacheOpenAPIProcessorsTest.php | 1 - tests/MembraneTest.php | 1 - tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php | 5 ----- tests/OpenAPI/Builder/RequestBuilderTest.php | 2 -- tests/OpenAPI/Filter/QueryStringToArrayTest.php | 1 - tests/OpenAPI/Specification/OpenAPIRequestTest.php | 2 +- 7 files changed, 1 insertion(+), 12 deletions(-) diff --git a/tests/Console/Command/CacheOpenAPIProcessorsTest.php b/tests/Console/Command/CacheOpenAPIProcessorsTest.php index 7d740b25..9f42989a 100644 --- a/tests/Console/Command/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Command/CacheOpenAPIProcessorsTest.php @@ -58,7 +58,6 @@ #[UsesClass(Explode::class)] #[UsesClass(IntString::class)] #[UsesClass(TypeFilter\ToInt::class)] -#[UsesClass(Specification\Parameter::class)] #[UsesClass(Processor\BeforeSet::class)] #[UsesClass(Processor\Collection::class)] #[UsesClass(Processor\Field::class)] diff --git a/tests/Console/Service/CacheOpenAPIProcessorsTest.php b/tests/Console/Service/CacheOpenAPIProcessorsTest.php index be092425..59e41a6d 100644 --- a/tests/Console/Service/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Service/CacheOpenAPIProcessorsTest.php @@ -54,7 +54,6 @@ #[UsesClass(ToPascalCase::class)] #[UsesClass(Explode::class)] #[UsesClass(TypeFilter\ToInt::class)] -#[UsesClass(Specification\Parameter::class)] #[UsesClass(Processor\BeforeSet::class)] #[UsesClass(Processor\Collection::class)] #[UsesClass(Processor\Field::class)] diff --git a/tests/MembraneTest.php b/tests/MembraneTest.php index d81075a0..60a95cff 100644 --- a/tests/MembraneTest.php +++ b/tests/MembraneTest.php @@ -41,7 +41,6 @@ #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(Builder\RequestBuilder::class)] #[UsesClass(Builder\ResponseBuilder::class)] -#[UsesClass(OpenAPISpecification\Parameter::class)] #[UsesClass(OpenAPISpecification\OpenAPIRequest::class)] #[UsesClass(OpenAPISpecification\OpenAPIResponse::class)] #[UsesClass(Processor\BeforeSet::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php index 46661cde..afe78620 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php @@ -35,9 +35,7 @@ use Membrane\OpenAPI\Filter\PathMatcher; use Membrane\OpenAPI\Filter\QueryStringToArray; use Membrane\OpenAPI\Processor\Request as RequestProcessor; -use Membrane\OpenAPI\Specification\APISchema; use Membrane\OpenAPI\Specification\OpenAPIRequest; -use Membrane\OpenAPI\Specification\Parameter; use Membrane\OpenAPI\Specification\Request; use Membrane\OpenAPIReader\FileFormat; use Membrane\OpenAPIReader\MembraneReader; @@ -97,12 +95,9 @@ #[UsesClass(PathParameterExtractor::class)] #[UsesClass(PathMatcherClass::class)] #[UsesClass(RequestProcessor::class)] -#[UsesClass(Parameter::class)] -#[UsesClass(APISchema::class)] #[UsesClass(AllOf::class)] #[UsesClass(AnyOf::class)] #[UsesClass(OneOf::class)] -#[UsesClass(\Membrane\OpenAPI\Specification\Arrays::class)] #[UsesClass(Explode::class)] #[UsesClass(Implode::class)] #[UsesClass(Tokenize::class)] diff --git a/tests/OpenAPI/Builder/RequestBuilderTest.php b/tests/OpenAPI/Builder/RequestBuilderTest.php index 04f4b1a2..e0cbfadb 100644 --- a/tests/OpenAPI/Builder/RequestBuilderTest.php +++ b/tests/OpenAPI/Builder/RequestBuilderTest.php @@ -24,7 +24,6 @@ use Membrane\OpenAPI\Filter\QueryStringToArray; use Membrane\OpenAPI\Processor\Request as RequestProcessor; use Membrane\OpenAPI\Specification\OpenAPIRequest; -use Membrane\OpenAPI\Specification\Parameter; use Membrane\OpenAPI\Specification\Request; use Membrane\OpenAPIReader\OpenAPIVersion; use Membrane\OpenAPIReader\Reader; @@ -66,7 +65,6 @@ #[UsesClass(Arrays::class)] #[UsesClass(Numeric::class)] #[UsesClass(Strings::class)] -#[UsesClass(Parameter::class)] #[UsesClass(QueryStringToArray::class)] #[UsesClass(Form::class)] #[UsesClass(PathMatcher::class)] diff --git a/tests/OpenAPI/Filter/QueryStringToArrayTest.php b/tests/OpenAPI/Filter/QueryStringToArrayTest.php index 80bd18c7..d7911231 100644 --- a/tests/OpenAPI/Filter/QueryStringToArrayTest.php +++ b/tests/OpenAPI/Filter/QueryStringToArrayTest.php @@ -6,7 +6,6 @@ use Generator; use Membrane\OpenAPI\Filter\QueryStringToArray; -use Membrane\OpenAPI\Specification\Parameter; use Membrane\Renderer\HumanReadable; use Membrane\Result\Message; use Membrane\Result\MessageSet; diff --git a/tests/OpenAPI/Specification/OpenAPIRequestTest.php b/tests/OpenAPI/Specification/OpenAPIRequestTest.php index 18021a17..ba31f4e9 100644 --- a/tests/OpenAPI/Specification/OpenAPIRequestTest.php +++ b/tests/OpenAPI/Specification/OpenAPIRequestTest.php @@ -31,7 +31,7 @@ class OpenAPIRequestTest extends TestCase protected function setUp(): void { - $this->openApi = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $this->openApi = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath(__DIR__ . '/../../fixtures/OpenAPI/docs/petstore-expanded.json'); $this->pathParameterExtractor = new PathParameterExtractor('/pets'); } From b1814eb08f8e34226d676f06a3d480a2b2487183 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 13:10:29 +0100 Subject: [PATCH 29/47] Remove parameter specification --- .../Specification/OpenAPIRequestTest.php | 164 +++++++++++++++- tests/OpenAPI/Specification/ParameterTest.php | 183 ------------------ 2 files changed, 163 insertions(+), 184 deletions(-) delete mode 100644 tests/OpenAPI/Specification/ParameterTest.php diff --git a/tests/OpenAPI/Specification/OpenAPIRequestTest.php b/tests/OpenAPI/Specification/OpenAPIRequestTest.php index ba31f4e9..d284b36a 100644 --- a/tests/OpenAPI/Specification/OpenAPIRequestTest.php +++ b/tests/OpenAPI/Specification/OpenAPIRequestTest.php @@ -12,7 +12,10 @@ use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method; -use Membrane\OpenAPIReader\ValueObject\Valid\V30; +use Membrane\OpenAPIReader\ValueObject\Valid\Identifier; +use PHPUnit\Framework\Attributes\DataProvider; +use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; +use Membrane\OpenAPIReader\ValueObject\Partial; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\TestDox; @@ -36,6 +39,165 @@ protected function setUp(): void $this->pathParameterExtractor = new PathParameterExtractor('/pets'); } + + + public static function provideValidParameters(): array + { + return [ + '"style": "matrix" in "path"' => [ + new V30\Parameter(new Identifier('test'), new Partial\Parameter( + name: 'id', + in: 'path', + required: true, + style: 'matrix', + schema: new Partial\Schema(type: 'integer'), + )), + [ + 'name' => 'id', + 'in' => 'path', + 'required' => true, + 'style' => 'matrix', + 'explode' => false, + 'schema' => new V30\Schema( + new Identifier('test', 'id(path)', 'schema'), + new Partial\Schema(type: 'integer'), + ), + ], + ], + '"style": "label" in "path"' => [ + new V30\Parameter(new Identifier('test'), new Partial\Parameter( + name: 'id', + in: 'path', + required: true, + style: 'label', + schema: new Partial\Schema(type: 'integer'), + )), + [ + 'name' => 'id', + 'in' => 'path', + 'required' => true, + 'style' => 'label', + 'explode' => false, + 'schema' => new V30\Schema( + new Identifier('test', 'id(path)', 'schema'), + new Partial\Schema(type: 'integer'), + ), + ], + ], + '"style":"form" in "query"' => [ + new V30\Parameter(new Identifier('test'), new Partial\Parameter( + name: 'id', + in: 'query', + required: false, + style: 'form', + schema: new Partial\Schema(type: 'integer'), + )), + [ + 'name' => 'id', + 'in' => 'query', + 'required' => false, + 'style' => 'form', + 'explode' => true, + 'schema' => new V30\Schema( + new Identifier('test', 'id(query)', 'schema'), + new Partial\Schema(type: 'integer'), + ), + ], + ], + '"style":"simple" in "path"' => [ + new V30\Parameter(new Identifier('test'), new Partial\Parameter( + name: 'id', + in: 'path', + required: true, + style: 'simple', + schema: new Partial\Schema(type: 'integer'), + )), + [ + 'name' => 'id', + 'in' => 'path', + 'required' => true, + 'style' => 'simple', + 'explode' => false, + 'schema' => new V30\Schema( + new Identifier('test', 'id(path)', 'schema'), + new Partial\Schema(type: 'integer'), + ), + ], + ], + '"style":"spaceDelimited" in "query"' => [ + new V30\Parameter(new Identifier('test'), new Partial\Parameter( + name: 'id', + in: 'query', + required: false, + style: 'spaceDelimited', + schema: new Partial\Schema(type: 'integer'), + )), + [ + 'name' => 'id', + 'in' => 'query', + 'required' => false, + 'style' => 'spaceDelimited', + 'explode' => false, + 'schema' => new V30\Schema( + new Identifier('test', 'id(query)', 'schema'), + new Partial\Schema(type: 'integer'), + ), + ], + ], + '"style":"pipeDelimited" in "query"' => [ + new V30\Parameter(new Identifier('test'), new Partial\Parameter( + name: 'id', + in: 'query', + required: false, + style: 'pipeDelimited', + schema: new Partial\Schema(type: 'integer'), + )), + [ + 'name' => 'id', + 'in' => 'query', + 'required' => false, + 'style' => 'pipeDelimited', + 'explode' => false, + 'schema' => new V30\Schema( + new Identifier('test', 'id(query)', 'schema'), + new Partial\Schema(type: 'integer'), + ), + ], + ], + '"style":"deepObject" in "query"' => [ + new V30\Parameter(new Identifier('test'), new Partial\Parameter( + name: 'id', + in: 'query', + required: false, + style: 'deepObject', + explode: false, + schema: new Partial\Schema(type: 'object'), + )), + [ + 'name' => 'id', + 'in' => 'query', + 'required' => false, + 'style' => 'deepObject', + 'explode' => false, + 'schema' => new V30\Schema( + new Identifier('test', 'id(query)', 'schema'), + new Partial\Schema(type: 'object'), + ), + ], + ], + ]; + } + + #[Test] + #[TestDox('It will construct itself from valid Parameters')] + #[DataProvider('provideValidParameters')] + public function itConstructsFromValidParameters( + V30\Parameter|V31\Parameter $parameter, + array $expectedProperties + ): void { + self::markTestSkipped(); + } + #[Test, TestDox('If the method given is not specified on the PathItem then an exception will be thrown')] public function throwsExceptionIfMethodNotFound(): void { diff --git a/tests/OpenAPI/Specification/ParameterTest.php b/tests/OpenAPI/Specification/ParameterTest.php deleted file mode 100644 index 72f65dff..00000000 --- a/tests/OpenAPI/Specification/ParameterTest.php +++ /dev/null @@ -1,183 +0,0 @@ - [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'path', - required: true, - style: 'matrix', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'style' => 'matrix', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(path)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style": "label" in "path"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'path', - required: true, - style: 'label', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'style' => 'label', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(path)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"form" in "query"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'query', - required: false, - style: 'form', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'query', - 'required' => false, - 'style' => 'form', - 'explode' => true, - 'schema' => new V30\Schema( - new Identifier('test', 'id(query)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"simple" in "path"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'path', - required: true, - style: 'simple', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'style' => 'simple', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(path)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"spaceDelimited" in "query"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'query', - required: false, - style: 'spaceDelimited', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'query', - 'required' => false, - 'style' => 'spaceDelimited', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(query)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"pipeDelimited" in "query"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'query', - required: false, - style: 'pipeDelimited', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'query', - 'required' => false, - 'style' => 'pipeDelimited', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(query)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"deepObject" in "query"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'query', - required: false, - style: 'deepObject', - explode: false, - schema: new Partial\Schema(type: 'object'), - )), - [ - 'name' => 'id', - 'in' => 'query', - 'required' => false, - 'style' => 'deepObject', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(query)', 'schema'), - new Partial\Schema(type: 'object'), - ), - ], - ], - ]; - } - - #[Test] - #[TestDox('It will construct itself from valid Parameters')] - #[DataProvider('provideValidParameters')] - public function itConstructsFromValidParameters( - V30\Parameter|V31\Parameter $parameter, - array $expectedProperties - ): void { - $sut = new Parameter($parameter); - - foreach ($expectedProperties as $key => $value) { - self::assertEquals($value, $sut->$key, sprintf("'%s' doesn't match expected", $key)); - } - } -} From 48cee733880286de028bfd7a896e8593df8c49ff Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 13:29:27 +0100 Subject: [PATCH 30/47] Move all of internal type-specific schema classes to Internal namespace --- src/OpenAPI/Builder/APIBuilder.php | 9 +++++++-- src/OpenAPI/Builder/{ => Internal}/Arrays.php | 7 +++++-- src/OpenAPI/Builder/{ => Internal}/Numeric.php | 5 ++++- src/OpenAPI/Builder/{ => Internal}/Objects.php | 7 +++++-- src/OpenAPI/Builder/Internal/README.md | 5 +++++ src/OpenAPI/Builder/{ => Internal}/Strings.php | 5 ++++- src/OpenAPI/Builder/{ => Internal}/TrueFalse.php | 6 ++++-- tests/Console/Command/CacheOpenAPIProcessorsTest.php | 8 ++++---- tests/Console/Service/CacheOpenAPIProcessorsTest.php | 8 ++++---- tests/MembraneTest.php | 8 ++++---- tests/OpenAPI/Builder/{ => Internal}/ArraysTest.php | 6 +++--- tests/OpenAPI/Builder/{ => Internal}/NumericTest.php | 5 ++--- tests/OpenAPI/Builder/{ => Internal}/ObjectsTest.php | 10 +++++----- tests/OpenAPI/Builder/{ => Internal}/StringsTest.php | 4 ++-- tests/OpenAPI/Builder/{ => Internal}/TrueFalseTest.php | 4 ++-- tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php | 10 +++++----- tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php | 10 +++++----- tests/OpenAPI/Builder/RequestBuilderTest.php | 6 +++--- tests/OpenAPI/Builder/ResponseBuilderTest.php | 10 +++++----- 19 files changed, 78 insertions(+), 55 deletions(-) rename src/OpenAPI/Builder/{ => Internal}/Arrays.php (96%) rename src/OpenAPI/Builder/{ => Internal}/Numeric.php (98%) rename src/OpenAPI/Builder/{ => Internal}/Objects.php (97%) create mode 100644 src/OpenAPI/Builder/Internal/README.md rename src/OpenAPI/Builder/{ => Internal}/Strings.php (97%) rename src/OpenAPI/Builder/{ => Internal}/TrueFalse.php (96%) rename tests/OpenAPI/Builder/{ => Internal}/ArraysTest.php (97%) rename tests/OpenAPI/Builder/{ => Internal}/NumericTest.php (97%) rename tests/OpenAPI/Builder/{ => Internal}/ObjectsTest.php (97%) rename tests/OpenAPI/Builder/{ => Internal}/StringsTest.php (97%) rename tests/OpenAPI/Builder/{ => Internal}/TrueFalseTest.php (96%) diff --git a/src/OpenAPI/Builder/APIBuilder.php b/src/OpenAPI/Builder/APIBuilder.php index 6b27383c..4fbe0f35 100644 --- a/src/OpenAPI/Builder/APIBuilder.php +++ b/src/OpenAPI/Builder/APIBuilder.php @@ -5,6 +5,11 @@ namespace Membrane\OpenAPI\Builder; use Membrane\OpenAPI; +use Membrane\OpenAPI\Builder\Internal\Arrays; +use Membrane\OpenAPI\Builder\Internal\Numeric; +use Membrane\OpenAPI\Builder\Internal\Objects; +use Membrane\OpenAPI\Builder\Internal\Strings; +use Membrane\OpenAPI\Builder\Internal\TrueFalse; use Membrane\OpenAPIReader\ValueObject\Valid\{Enum\Type, V30, V31}; use Membrane\Processor; use Membrane\Processor\AllOf; @@ -225,7 +230,7 @@ private function getObjectBuilder(): Objects private function getNumericBuilder(): Numeric { if (!isset($this->numericBuilder)) { - $this->numericBuilder = new OpenAPI\Builder\Numeric(); + $this->numericBuilder = new Internal\Numeric(); } return $this->numericBuilder; @@ -234,7 +239,7 @@ private function getNumericBuilder(): Numeric private function getStringBuilder(): Strings { if (!isset($this->stringBuilder)) { - $this->stringBuilder = new OpenAPI\Builder\Strings(); + $this->stringBuilder = new Internal\Strings(); } return $this->stringBuilder; } diff --git a/src/OpenAPI/Builder/Arrays.php b/src/OpenAPI/Builder/Internal/Arrays.php similarity index 96% rename from src/OpenAPI/Builder/Arrays.php rename to src/OpenAPI/Builder/Internal/Arrays.php index 17d215cf..57a6d6c4 100644 --- a/src/OpenAPI/Builder/Arrays.php +++ b/src/OpenAPI/Builder/Internal/Arrays.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Membrane\OpenAPI\Builder; +namespace Membrane\OpenAPI\Builder\Internal; -use Membrane\Builder\Specification; use Membrane\Filter; +use Membrane\OpenAPI\Builder\APIBuilder; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; @@ -23,6 +23,9 @@ use Membrane\Validator\Collection\Unique; use Membrane\Validator\Type\IsList; +/** + * @internal see README.md + */ class Arrays extends APIBuilder { public function build( diff --git a/src/OpenAPI/Builder/Numeric.php b/src/OpenAPI/Builder/Internal/Numeric.php similarity index 98% rename from src/OpenAPI/Builder/Numeric.php rename to src/OpenAPI/Builder/Internal/Numeric.php index ddf7b284..fbe3517b 100644 --- a/src/OpenAPI/Builder/Numeric.php +++ b/src/OpenAPI/Builder/Internal/Numeric.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Membrane\OpenAPI\Builder; +namespace Membrane\OpenAPI\Builder\Internal; use Membrane\Filter; use Membrane\Filter\String\LeftTrim; @@ -29,6 +29,9 @@ use Membrane\Validator\Type\IsInt; use Membrane\Validator\Type\IsNumber; +/** + * @internal see README.md + */ final readonly class Numeric { public function build( diff --git a/src/OpenAPI/Builder/Objects.php b/src/OpenAPI/Builder/Internal/Objects.php similarity index 97% rename from src/OpenAPI/Builder/Objects.php rename to src/OpenAPI/Builder/Internal/Objects.php index fd206c71..f1432051 100644 --- a/src/OpenAPI/Builder/Objects.php +++ b/src/OpenAPI/Builder/Internal/Objects.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace Membrane\OpenAPI\Builder; +namespace Membrane\OpenAPI\Builder\Internal; -use Membrane\Builder\Specification; use Membrane\Filter; +use Membrane\OpenAPI\Builder\APIBuilder; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\Filter\FormatStyle\DeepObject; use Membrane\OpenAPI\Filter\FormatStyle\Form; @@ -26,6 +26,9 @@ use Membrane\Validator\FieldSet\RequiredFields; use Membrane\Validator\Type\IsArray; +/** + * @internal see README.md + */ class Objects extends APIBuilder { public function build( diff --git a/src/OpenAPI/Builder/Internal/README.md b/src/OpenAPI/Builder/Internal/README.md new file mode 100644 index 00000000..6b456147 --- /dev/null +++ b/src/OpenAPI/Builder/Internal/README.md @@ -0,0 +1,5 @@ +# Internal Classes Only + +Warning, classes in this directory are **not** intended for use outside of this library. + +They will not be protected by any backwards compatibility guarantee, or subject to semver. diff --git a/src/OpenAPI/Builder/Strings.php b/src/OpenAPI/Builder/Internal/Strings.php similarity index 97% rename from src/OpenAPI/Builder/Strings.php rename to src/OpenAPI/Builder/Internal/Strings.php index 560bf579..2c1b02d3 100644 --- a/src/OpenAPI/Builder/Strings.php +++ b/src/OpenAPI/Builder/Internal/Strings.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Membrane\OpenAPI\Builder; +namespace Membrane\OpenAPI\Builder\Internal; use Membrane\Filter\String\Implode; use Membrane\Filter\String\LeftTrim; @@ -23,6 +23,9 @@ use Membrane\Validator\Type\IsString; use Membrane\Validator\Utility\AnyOf; +/** + * @internal see README.md + */ final readonly class Strings { public function build( diff --git a/src/OpenAPI/Builder/TrueFalse.php b/src/OpenAPI/Builder/Internal/TrueFalse.php similarity index 96% rename from src/OpenAPI/Builder/TrueFalse.php rename to src/OpenAPI/Builder/Internal/TrueFalse.php index b0d79a0c..36f233fe 100644 --- a/src/OpenAPI/Builder/TrueFalse.php +++ b/src/OpenAPI/Builder/Internal/TrueFalse.php @@ -2,9 +2,8 @@ declare(strict_types=1); -namespace Membrane\OpenAPI\Builder; +namespace Membrane\OpenAPI\Builder\Internal; -use Membrane\Builder\Specification; use Membrane\Filter\String\Implode; use Membrane\Filter\String\LeftTrim; use Membrane\Filter\Type\ToBool; @@ -21,6 +20,9 @@ use Membrane\Validator\String\BoolString; use Membrane\Validator\Type\IsBool; +/** + * @internal see README.md + */ final readonly class TrueFalse { public function build( diff --git a/tests/Console/Command/CacheOpenAPIProcessorsTest.php b/tests/Console/Command/CacheOpenAPIProcessorsTest.php index 9f42989a..2ddf4f1e 100644 --- a/tests/Console/Command/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Command/CacheOpenAPIProcessorsTest.php @@ -38,10 +38,10 @@ #[UsesClass(Template\ResponseBuilder::class)] #[UsesClass(Template\RequestBuilder::class)] #[UsesClass(Builder\APIBuilder::class)] -#[UsesClass(Builder\Arrays::class)] -#[UsesClass(Builder\Numeric::class)] -#[UsesClass(Builder\Strings::class)] -#[UsesClass(Builder\Objects::class)] +#[UsesClass(Builder\Internal\Arrays::class)] +#[UsesClass(Builder\Internal\Numeric::class)] +#[UsesClass(Builder\Internal\Strings::class)] +#[UsesClass(Builder\Internal\Objects::class)] #[UsesClass(Builder\OpenAPIRequestBuilder::class)] #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(PathMatcher::class)] diff --git a/tests/Console/Service/CacheOpenAPIProcessorsTest.php b/tests/Console/Service/CacheOpenAPIProcessorsTest.php index 59e41a6d..65456d21 100644 --- a/tests/Console/Service/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Service/CacheOpenAPIProcessorsTest.php @@ -34,10 +34,10 @@ #[UsesClass(Template\ResponseBuilder::class)] #[UsesClass(Template\RequestBuilder::class)] #[UsesClass(Builder\APIBuilder::class)] -#[UsesClass(Builder\Arrays::class)] -#[UsesClass(Builder\Numeric::class)] -#[UsesClass(Builder\Strings::class)] -#[UsesClass(Builder\Objects::class)] +#[UsesClass(Builder\Internal\Arrays::class)] +#[UsesClass(Builder\Internal\Numeric::class)] +#[UsesClass(Builder\Internal\Strings::class)] +#[UsesClass(Builder\Internal\Objects::class)] #[UsesClass(Builder\OpenAPIRequestBuilder::class)] #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(PathMatcher::class)] diff --git a/tests/MembraneTest.php b/tests/MembraneTest.php index 60a95cff..dec72aa1 100644 --- a/tests/MembraneTest.php +++ b/tests/MembraneTest.php @@ -33,10 +33,10 @@ #[CoversClass(Membrane::class)] #[UsesClass(AttributeBuilder::class)] #[UsesClass(Builder\APIBuilder::class)] -#[UsesClass(Builder\Arrays::class)] -#[UsesClass(Builder\Numeric::class)] -#[UsesClass(Builder\Strings::class)] -#[UsesClass(Builder\Objects::class)] +#[UsesClass(Builder\Internal\Arrays::class)] +#[UsesClass(Builder\Internal\Numeric::class)] +#[UsesClass(Builder\Internal\Strings::class)] +#[UsesClass(Builder\Internal\Objects::class)] #[UsesClass(Builder\OpenAPIRequestBuilder::class)] #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(Builder\RequestBuilder::class)] diff --git a/tests/OpenAPI/Builder/ArraysTest.php b/tests/OpenAPI/Builder/Internal/ArraysTest.php similarity index 97% rename from tests/OpenAPI/Builder/ArraysTest.php rename to tests/OpenAPI/Builder/Internal/ArraysTest.php index 792df0ea..76659b7a 100644 --- a/tests/OpenAPI/Builder/ArraysTest.php +++ b/tests/OpenAPI/Builder/Internal/ArraysTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Membrane\Tests\OpenAPI\Builder; +namespace Membrane\Tests\OpenAPI\Builder\Internal; use Membrane\OpenAPI\Builder\APIBuilder; -use Membrane\OpenAPI\Builder\Arrays; -use Membrane\OpenAPI\Builder\Numeric; +use Membrane\OpenAPI\Builder\Internal\Arrays; +use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; use Membrane\OpenAPIReader\ValueObject\Value; diff --git a/tests/OpenAPI/Builder/NumericTest.php b/tests/OpenAPI/Builder/Internal/NumericTest.php similarity index 97% rename from tests/OpenAPI/Builder/NumericTest.php rename to tests/OpenAPI/Builder/Internal/NumericTest.php index 058138ba..54e66c4c 100644 --- a/tests/OpenAPI/Builder/NumericTest.php +++ b/tests/OpenAPI/Builder/Internal/NumericTest.php @@ -2,14 +2,13 @@ declare(strict_types=1); -namespace Membrane\Tests\OpenAPI\Builder; +namespace Membrane\Tests\OpenAPI\Builder\Internal; use Membrane\Filter\Type\ToFloat; use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; use Membrane\OpenAPI\Builder\APIBuilder; -use Membrane\OpenAPI\Builder\Numeric; -use Membrane\OpenAPI\Specification; +use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; use Membrane\OpenAPIReader\ValueObject\Value; diff --git a/tests/OpenAPI/Builder/ObjectsTest.php b/tests/OpenAPI/Builder/Internal/ObjectsTest.php similarity index 97% rename from tests/OpenAPI/Builder/ObjectsTest.php rename to tests/OpenAPI/Builder/Internal/ObjectsTest.php index 29eb9796..0a40c711 100644 --- a/tests/OpenAPI/Builder/ObjectsTest.php +++ b/tests/OpenAPI/Builder/Internal/ObjectsTest.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace Membrane\Tests\OpenAPI\Builder; +namespace Membrane\Tests\OpenAPI\Builder\Internal; use Membrane\OpenAPI\Builder\APIBuilder; -use Membrane\OpenAPI\Builder\Numeric; -use Membrane\OpenAPI\Builder\Objects; -use Membrane\OpenAPI\Builder\Strings; -use Membrane\OpenAPI\Builder\TrueFalse; +use Membrane\OpenAPI\Builder\Internal\Numeric; +use Membrane\OpenAPI\Builder\Internal\Objects; +use Membrane\OpenAPI\Builder\Internal\Strings; +use Membrane\OpenAPI\Builder\Internal\TrueFalse; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; use Membrane\OpenAPIReader\ValueObject\Value; diff --git a/tests/OpenAPI/Builder/StringsTest.php b/tests/OpenAPI/Builder/Internal/StringsTest.php similarity index 97% rename from tests/OpenAPI/Builder/StringsTest.php rename to tests/OpenAPI/Builder/Internal/StringsTest.php index 8ae09b89..2fc62347 100644 --- a/tests/OpenAPI/Builder/StringsTest.php +++ b/tests/OpenAPI/Builder/Internal/StringsTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Membrane\Tests\OpenAPI\Builder; +namespace Membrane\Tests\OpenAPI\Builder\Internal; use Membrane\Filter\String\ToUpperCase; use Membrane\OpenAPI\Builder\APIBuilder; -use Membrane\OpenAPI\Builder\Strings; +use Membrane\OpenAPI\Builder\Internal\Strings; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; use Membrane\OpenAPIReader\ValueObject\Value; diff --git a/tests/OpenAPI/Builder/TrueFalseTest.php b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php similarity index 96% rename from tests/OpenAPI/Builder/TrueFalseTest.php rename to tests/OpenAPI/Builder/Internal/TrueFalseTest.php index ca67e812..f4fce974 100644 --- a/tests/OpenAPI/Builder/TrueFalseTest.php +++ b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Membrane\Tests\OpenAPI\Builder; +namespace Membrane\Tests\OpenAPI\Builder\Internal; use Membrane\Filter\Type\ToBool; use Membrane\OpenAPI\Builder\APIBuilder; -use Membrane\OpenAPI\Builder\TrueFalse; +use Membrane\OpenAPI\Builder\Internal\TrueFalse; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; use Membrane\OpenAPIReader\ValueObject\Value; diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php index afe78620..5d815b26 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php @@ -16,12 +16,12 @@ use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; use Membrane\OpenAPI\Builder\APIBuilder; -use Membrane\OpenAPI\Builder\Arrays; -use Membrane\OpenAPI\Builder\Numeric; -use Membrane\OpenAPI\Builder\Objects; +use Membrane\OpenAPI\Builder\Internal\Arrays; +use Membrane\OpenAPI\Builder\Internal\Numeric; +use Membrane\OpenAPI\Builder\Internal\Objects; +use Membrane\OpenAPI\Builder\Internal\Strings; use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder; use Membrane\OpenAPI\Builder\RequestBuilder; -use Membrane\OpenAPI\Builder\Strings; use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Exception\CannotProcessSpecification; @@ -83,7 +83,7 @@ #[CoversClass(CannotProcessOpenAPI::class)] #[UsesClass(HumanReadable::class)] // to render test failure messages #[UsesClass(RequestBuilder::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\TrueFalse::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] #[UsesClass(OpenAPIRequest::class)] #[UsesClass(Request::class)] #[UsesClass(Arrays::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php index a8673b37..1b314370 100644 --- a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php @@ -59,11 +59,11 @@ #[CoversClass(CannotProcessOpenAPI::class)] #[CoversClass(APIBuilder::class)] #[UsesClass(OpenAPIResponse::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Arrays::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\TrueFalse::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Numeric::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Objects::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Strings::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Arrays::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Numeric::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Objects::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Strings::class)] #[UsesClass(PathMatcher::class)] #[UsesClass(AllOf::class)] #[UsesClass(AnyOf::class)] diff --git a/tests/OpenAPI/Builder/RequestBuilderTest.php b/tests/OpenAPI/Builder/RequestBuilderTest.php index e0cbfadb..797c33fd 100644 --- a/tests/OpenAPI/Builder/RequestBuilderTest.php +++ b/tests/OpenAPI/Builder/RequestBuilderTest.php @@ -10,11 +10,11 @@ use Membrane\Filter\String\Implode; use Membrane\Filter\Type\ToInt; use Membrane\OpenAPI\Builder\APIBuilder; -use Membrane\OpenAPI\Builder\Arrays; -use Membrane\OpenAPI\Builder\Numeric; +use Membrane\OpenAPI\Builder\Internal\Arrays; +use Membrane\OpenAPI\Builder\Internal\Numeric; +use Membrane\OpenAPI\Builder\Internal\Strings; use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder; use Membrane\OpenAPI\Builder\RequestBuilder; -use Membrane\OpenAPI\Builder\Strings; use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Exception\CannotProcessSpecification; diff --git a/tests/OpenAPI/Builder/ResponseBuilderTest.php b/tests/OpenAPI/Builder/ResponseBuilderTest.php index e48914be..9e82b522 100644 --- a/tests/OpenAPI/Builder/ResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/ResponseBuilderTest.php @@ -61,11 +61,11 @@ #[UsesClass(APIBuilder::class)] #[UsesClass(OpenAPIResponseBuilder::class)] #[UsesClass(OpenAPIResponse::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Arrays::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\TrueFalse::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Numeric::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Objects::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Strings::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Arrays::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Numeric::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Objects::class)] +#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Strings::class)] #[UsesClass(PathMatcher::class)] #[UsesClass(AllOf::class)] #[UsesClass(AnyOf::class)] From 5da9b856d65dcf47ce63380514483d988c7e48a4 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 13:33:30 +0100 Subject: [PATCH 31/47] Remove useless lines --- tests/OpenAPI/Builder/Internal/TrueFalseTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/OpenAPI/Builder/Internal/TrueFalseTest.php b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php index f4fce974..61741a9c 100644 --- a/tests/OpenAPI/Builder/Internal/TrueFalseTest.php +++ b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php @@ -57,7 +57,6 @@ public static function specificationsToBuild(): array new Partial\Schema( type: 'boolean', enum: [new Value(true), new Value(null)], - format: 'rather pointless boolean', ) )->value, ], @@ -69,12 +68,10 @@ enum: [new Value(true), new Value(null)], new Partial\Schema( type: 'boolean', enum: [new Value(true), new Value(null)], - format: 'rather pointless boolean', ) )->value, true, ], - ]; } From e7af23e29564ef35828949f0bd33ea3da96b7781 Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 13:38:42 +0100 Subject: [PATCH 32/47] Remove Parameter Specification class --- src/OpenAPI/Specification/Parameter.php | 36 ------------------------- 1 file changed, 36 deletions(-) delete mode 100644 src/OpenAPI/Specification/Parameter.php diff --git a/src/OpenAPI/Specification/Parameter.php b/src/OpenAPI/Specification/Parameter.php deleted file mode 100644 index 3d990c97..00000000 --- a/src/OpenAPI/Specification/Parameter.php +++ /dev/null @@ -1,36 +0,0 @@ -name = $parameter->name; - $this->in = $parameter->in->value; - $this->style = $parameter->style->value; - $this->explode = $parameter->explode; - - if ($parameter->hasMediaType() && $parameter->getMediaType() !== 'application/json') { - assert($parameter->getMediaType() !== null); - throw CannotProcessOpenAPI::unsupportedMediaTypes($parameter->getMediaType()); - } - - $this->schema = $parameter->getSchema(); - - $this->required = $parameter->required; - } -} From 2e7d3b754f207494eb66e4b69b402a398da1b01c Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 13:42:13 +0100 Subject: [PATCH 33/47] Add trailing comma --- tests/OpenAPI/Builder/Internal/TrueFalseTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OpenAPI/Builder/Internal/TrueFalseTest.php b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php index 61741a9c..3b954d56 100644 --- a/tests/OpenAPI/Builder/Internal/TrueFalseTest.php +++ b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php @@ -68,7 +68,7 @@ enum: [new Value(true), new Value(null)], new Partial\Schema( type: 'boolean', enum: [new Value(true), new Value(null)], - ) + ), )->value, true, ], From b59a7379f4cec350a5790ac336a5cf574c2c568f Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 13:52:20 +0100 Subject: [PATCH 34/47] Move APIBuilder --- .../Builder/{ => Internal}/APIBuilder.php | 38 ++++--------------- src/OpenAPI/Builder/Internal/Arrays.php | 5 +-- src/OpenAPI/Builder/Internal/Objects.php | 5 +-- src/OpenAPI/Builder/OpenAPIRequestBuilder.php | 7 ++-- .../Builder/OpenAPIResponseBuilder.php | 1 + .../Command/CacheOpenAPIProcessorsTest.php | 2 +- .../Service/CacheOpenAPIProcessorsTest.php | 2 +- tests/MembraneTest.php | 2 +- tests/OpenAPI/Builder/Internal/ArraysTest.php | 2 +- .../OpenAPI/Builder/Internal/NumericTest.php | 2 +- .../OpenAPI/Builder/Internal/ObjectsTest.php | 2 +- .../OpenAPI/Builder/Internal/StringsTest.php | 2 +- .../Builder/Internal/TrueFalseTest.php | 2 +- .../Builder/OpenAPIRequestBuilderTest.php | 2 +- .../Builder/OpenAPIResponseBuilderTest.php | 2 +- tests/OpenAPI/Builder/RequestBuilderTest.php | 2 +- tests/OpenAPI/Builder/ResponseBuilderTest.php | 2 +- 17 files changed, 26 insertions(+), 54 deletions(-) rename src/OpenAPI/Builder/{ => Internal}/APIBuilder.php (85%) diff --git a/src/OpenAPI/Builder/APIBuilder.php b/src/OpenAPI/Builder/Internal/APIBuilder.php similarity index 85% rename from src/OpenAPI/Builder/APIBuilder.php rename to src/OpenAPI/Builder/Internal/APIBuilder.php index 4fbe0f35..d9ff520f 100644 --- a/src/OpenAPI/Builder/APIBuilder.php +++ b/src/OpenAPI/Builder/Internal/APIBuilder.php @@ -2,14 +2,9 @@ declare(strict_types=1); -namespace Membrane\OpenAPI\Builder; +namespace Membrane\OpenAPI\Builder\Internal; use Membrane\OpenAPI; -use Membrane\OpenAPI\Builder\Internal\Arrays; -use Membrane\OpenAPI\Builder\Internal\Numeric; -use Membrane\OpenAPI\Builder\Internal\Objects; -use Membrane\OpenAPI\Builder\Internal\Strings; -use Membrane\OpenAPI\Builder\Internal\TrueFalse; use Membrane\OpenAPIReader\ValueObject\Valid\{Enum\Type, V30, V31}; use Membrane\Processor; use Membrane\Processor\AllOf; @@ -160,7 +155,7 @@ public function fromSchema( /** * @param class-string $complexSchemaClass - * @param non-empty-list $subSchemas + * @param non-empty-list | non-empty-list $subSchemas */ private function fromComplexSchema( string $complexSchemaClass, @@ -202,45 +197,26 @@ private function fromComplexSchema( private function getArrayBuilder(): Arrays { - if (!isset($this->arrayBuilder)) { - $this->arrayBuilder = new Arrays(); - } - - return $this->arrayBuilder; + return $this->arrayBuilder ??= new Arrays(); } private function getTrueFalseBuilder(): TrueFalse { - if (!isset($this->trueFalseBuilder)) { - $this->trueFalseBuilder = new TrueFalse(); - } - - return $this->trueFalseBuilder; + return $this->trueFalseBuilder ??= new TrueFalse(); } private function getObjectBuilder(): Objects { - if (!isset($this->objectBuilder)) { - $this->objectBuilder = new Objects(); - } - - return $this->objectBuilder; + return $this->objectBuilder ??= new Objects(); } private function getNumericBuilder(): Numeric { - if (!isset($this->numericBuilder)) { - $this->numericBuilder = new Internal\Numeric(); - } - - return $this->numericBuilder; + return $this->numericBuilder ??= new Numeric(); } private function getStringBuilder(): Strings { - if (!isset($this->stringBuilder)) { - $this->stringBuilder = new Internal\Strings(); - } - return $this->stringBuilder; + return $this->stringBuilder ??= new Strings(); } } diff --git a/src/OpenAPI/Builder/Internal/Arrays.php b/src/OpenAPI/Builder/Internal/Arrays.php index 57a6d6c4..ae42b08a 100644 --- a/src/OpenAPI/Builder/Internal/Arrays.php +++ b/src/OpenAPI/Builder/Internal/Arrays.php @@ -5,7 +5,6 @@ namespace Membrane\OpenAPI\Builder\Internal; use Membrane\Filter; -use Membrane\OpenAPI\Builder\APIBuilder; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\Filter\FormatStyle\Form; use Membrane\OpenAPI\Filter\FormatStyle\Matrix; @@ -102,7 +101,7 @@ public function build( $beforeSet = new BeforeSet(...$beforeChain); - $collection = new Collection( + return new Collection( $fieldName, $beforeSet, $this->fromSchema( @@ -111,7 +110,5 @@ public function build( $convertFromString, ) ); - - return $collection; } } diff --git a/src/OpenAPI/Builder/Internal/Objects.php b/src/OpenAPI/Builder/Internal/Objects.php index f1432051..f50526aa 100644 --- a/src/OpenAPI/Builder/Internal/Objects.php +++ b/src/OpenAPI/Builder/Internal/Objects.php @@ -5,7 +5,6 @@ namespace Membrane\OpenAPI\Builder\Internal; use Membrane\Filter; -use Membrane\OpenAPI\Builder\APIBuilder; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\Filter\FormatStyle\DeepObject; use Membrane\OpenAPI\Filter\FormatStyle\Form; @@ -137,8 +136,6 @@ public function build( ); } - $processor = new FieldSet($fieldName, $beforeSet, ...$fields); - - return $processor; + return new FieldSet($fieldName, $beforeSet, ...$fields); } } diff --git a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php index 44100d2d..e999963b 100644 --- a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php +++ b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php @@ -6,6 +6,7 @@ use Membrane\Builder\Builder; use Membrane\Builder\Specification; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Filter; use Membrane\OpenAPI\Processor\Request as RequestProcessor; @@ -66,7 +67,7 @@ private function fromParameters(OpenAPIRequest $specification): array 'required' => [], 'fields' => [], 'beforeSet' => $chain, - ]; + ]; $locations = [ 'path' => $location([new Filter\PathMatcher($specification->pathParameterExtractor)]), 'query' => $location([new Filter\QueryStringToArray(array_combine( @@ -108,8 +109,8 @@ private function fromParameters(OpenAPIRequest $specification): array $fieldSets = []; foreach ($locations as $in => $location) { $fields = $location['fields']; - $required = $location['required'] ?? []; - $beforeSet = $location['beforeSet'] ?? []; + $required = $location['required']; + $beforeSet = $location['beforeSet']; if (!empty($required)) { $beforeSet[] = new RequiredFields(...$required); diff --git a/src/OpenAPI/Builder/OpenAPIResponseBuilder.php b/src/OpenAPI/Builder/OpenAPIResponseBuilder.php index dbd12e8a..92ebc5ee 100644 --- a/src/OpenAPI/Builder/OpenAPIResponseBuilder.php +++ b/src/OpenAPI/Builder/OpenAPIResponseBuilder.php @@ -5,6 +5,7 @@ namespace Membrane\OpenAPI\Builder; use Membrane\Builder\Specification; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\Processor; use Membrane\Processor\Field; diff --git a/tests/Console/Command/CacheOpenAPIProcessorsTest.php b/tests/Console/Command/CacheOpenAPIProcessorsTest.php index 2ddf4f1e..7c08ce55 100644 --- a/tests/Console/Command/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Command/CacheOpenAPIProcessorsTest.php @@ -37,7 +37,7 @@ #[UsesClass(Template\Processor::class)] #[UsesClass(Template\ResponseBuilder::class)] #[UsesClass(Template\RequestBuilder::class)] -#[UsesClass(Builder\APIBuilder::class)] +#[UsesClass(Builder\Internal\APIBuilder::class)] #[UsesClass(Builder\Internal\Arrays::class)] #[UsesClass(Builder\Internal\Numeric::class)] #[UsesClass(Builder\Internal\Strings::class)] diff --git a/tests/Console/Service/CacheOpenAPIProcessorsTest.php b/tests/Console/Service/CacheOpenAPIProcessorsTest.php index 65456d21..e9adee25 100644 --- a/tests/Console/Service/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Service/CacheOpenAPIProcessorsTest.php @@ -33,7 +33,7 @@ #[UsesClass(Template\Processor::class)] #[UsesClass(Template\ResponseBuilder::class)] #[UsesClass(Template\RequestBuilder::class)] -#[UsesClass(Builder\APIBuilder::class)] +#[UsesClass(Builder\Internal\APIBuilder::class)] #[UsesClass(Builder\Internal\Arrays::class)] #[UsesClass(Builder\Internal\Numeric::class)] #[UsesClass(Builder\Internal\Strings::class)] diff --git a/tests/MembraneTest.php b/tests/MembraneTest.php index dec72aa1..0955a12a 100644 --- a/tests/MembraneTest.php +++ b/tests/MembraneTest.php @@ -32,7 +32,7 @@ #[CoversClass(Membrane::class)] #[UsesClass(AttributeBuilder::class)] -#[UsesClass(Builder\APIBuilder::class)] +#[UsesClass(Builder\Internal\APIBuilder::class)] #[UsesClass(Builder\Internal\Arrays::class)] #[UsesClass(Builder\Internal\Numeric::class)] #[UsesClass(Builder\Internal\Strings::class)] diff --git a/tests/OpenAPI/Builder/Internal/ArraysTest.php b/tests/OpenAPI/Builder/Internal/ArraysTest.php index 76659b7a..216c6af2 100644 --- a/tests/OpenAPI/Builder/Internal/ArraysTest.php +++ b/tests/OpenAPI/Builder/Internal/ArraysTest.php @@ -4,7 +4,7 @@ namespace Membrane\Tests\OpenAPI\Builder\Internal; -use Membrane\OpenAPI\Builder\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Builder\Internal\Arrays; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPIReader\ValueObject\Partial; diff --git a/tests/OpenAPI/Builder/Internal/NumericTest.php b/tests/OpenAPI/Builder/Internal/NumericTest.php index 54e66c4c..e0a1792d 100644 --- a/tests/OpenAPI/Builder/Internal/NumericTest.php +++ b/tests/OpenAPI/Builder/Internal/NumericTest.php @@ -7,7 +7,7 @@ use Membrane\Filter\Type\ToFloat; use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; -use Membrane\OpenAPI\Builder\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; diff --git a/tests/OpenAPI/Builder/Internal/ObjectsTest.php b/tests/OpenAPI/Builder/Internal/ObjectsTest.php index 0a40c711..7b0a38eb 100644 --- a/tests/OpenAPI/Builder/Internal/ObjectsTest.php +++ b/tests/OpenAPI/Builder/Internal/ObjectsTest.php @@ -4,7 +4,7 @@ namespace Membrane\Tests\OpenAPI\Builder\Internal; -use Membrane\OpenAPI\Builder\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPI\Builder\Internal\Objects; use Membrane\OpenAPI\Builder\Internal\Strings; diff --git a/tests/OpenAPI/Builder/Internal/StringsTest.php b/tests/OpenAPI/Builder/Internal/StringsTest.php index 2fc62347..474ad114 100644 --- a/tests/OpenAPI/Builder/Internal/StringsTest.php +++ b/tests/OpenAPI/Builder/Internal/StringsTest.php @@ -5,7 +5,7 @@ namespace Membrane\Tests\OpenAPI\Builder\Internal; use Membrane\Filter\String\ToUpperCase; -use Membrane\OpenAPI\Builder\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Builder\Internal\Strings; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; diff --git a/tests/OpenAPI/Builder/Internal/TrueFalseTest.php b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php index 3b954d56..61382228 100644 --- a/tests/OpenAPI/Builder/Internal/TrueFalseTest.php +++ b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php @@ -5,7 +5,7 @@ namespace Membrane\Tests\OpenAPI\Builder\Internal; use Membrane\Filter\Type\ToBool; -use Membrane\OpenAPI\Builder\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Builder\Internal\TrueFalse; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php index 5d815b26..080cb1a6 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php @@ -15,7 +15,7 @@ use Membrane\Filter\Type\ToBool; use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; -use Membrane\OpenAPI\Builder\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Builder\Internal\Arrays; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPI\Builder\Internal\Objects; diff --git a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php index 1b314370..3726625e 100644 --- a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php @@ -7,7 +7,7 @@ use Generator; use Membrane\Builder\Specification; use Membrane\Filter\String\ToUpperCase; -use Membrane\OpenAPI\Builder\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Exception\CannotProcessSpecification; diff --git a/tests/OpenAPI/Builder/RequestBuilderTest.php b/tests/OpenAPI/Builder/RequestBuilderTest.php index 797c33fd..62720203 100644 --- a/tests/OpenAPI/Builder/RequestBuilderTest.php +++ b/tests/OpenAPI/Builder/RequestBuilderTest.php @@ -9,7 +9,7 @@ use Membrane\Filter\String\Explode; use Membrane\Filter\String\Implode; use Membrane\Filter\Type\ToInt; -use Membrane\OpenAPI\Builder\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Builder\Internal\Arrays; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPI\Builder\Internal\Strings; diff --git a/tests/OpenAPI/Builder/ResponseBuilderTest.php b/tests/OpenAPI/Builder/ResponseBuilderTest.php index 9e82b522..6ba4d4cb 100644 --- a/tests/OpenAPI/Builder/ResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/ResponseBuilderTest.php @@ -6,7 +6,7 @@ use Membrane\Builder\Specification; use Membrane\Filter\String\ToUpperCase; -use Membrane\OpenAPI\Builder\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\APIBuilder; use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder; use Membrane\OpenAPI\Builder\ResponseBuilder; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; From 35c5706302c945f84d1d30c1795f1c5b32b023cd Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 13:54:21 +0100 Subject: [PATCH 35/47] Rename APIBuilder to Schema It's no longer a builder, these are internal for building processors --- src/OpenAPI/Builder/Internal/Arrays.php | 2 +- src/OpenAPI/Builder/Internal/Objects.php | 2 +- .../Builder/Internal/{APIBuilder.php => Schema.php} | 5 ++++- src/OpenAPI/Builder/OpenAPIRequestBuilder.php | 8 ++++---- src/OpenAPI/Builder/OpenAPIResponseBuilder.php | 4 ++-- tests/Console/Command/CacheOpenAPIProcessorsTest.php | 2 +- tests/Console/Service/CacheOpenAPIProcessorsTest.php | 2 +- tests/MembraneTest.php | 2 +- tests/OpenAPI/Builder/Internal/ArraysTest.php | 4 ++-- tests/OpenAPI/Builder/Internal/NumericTest.php | 4 ++-- tests/OpenAPI/Builder/Internal/ObjectsTest.php | 4 ++-- tests/OpenAPI/Builder/Internal/StringsTest.php | 4 ++-- tests/OpenAPI/Builder/Internal/TrueFalseTest.php | 4 ++-- tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php | 4 ++-- tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php | 4 ++-- tests/OpenAPI/Builder/RequestBuilderTest.php | 4 ++-- tests/OpenAPI/Builder/ResponseBuilderTest.php | 4 ++-- 17 files changed, 33 insertions(+), 30 deletions(-) rename src/OpenAPI/Builder/Internal/{APIBuilder.php => Schema.php} (99%) diff --git a/src/OpenAPI/Builder/Internal/Arrays.php b/src/OpenAPI/Builder/Internal/Arrays.php index ae42b08a..2c717de6 100644 --- a/src/OpenAPI/Builder/Internal/Arrays.php +++ b/src/OpenAPI/Builder/Internal/Arrays.php @@ -25,7 +25,7 @@ /** * @internal see README.md */ -class Arrays extends APIBuilder +class Arrays extends Schema { public function build( string $fieldName, diff --git a/src/OpenAPI/Builder/Internal/Objects.php b/src/OpenAPI/Builder/Internal/Objects.php index f50526aa..73172857 100644 --- a/src/OpenAPI/Builder/Internal/Objects.php +++ b/src/OpenAPI/Builder/Internal/Objects.php @@ -28,7 +28,7 @@ /** * @internal see README.md */ -class Objects extends APIBuilder +class Objects extends Schema { public function build( string $fieldName, diff --git a/src/OpenAPI/Builder/Internal/APIBuilder.php b/src/OpenAPI/Builder/Internal/Schema.php similarity index 99% rename from src/OpenAPI/Builder/Internal/APIBuilder.php rename to src/OpenAPI/Builder/Internal/Schema.php index d9ff520f..707ff4be 100644 --- a/src/OpenAPI/Builder/Internal/APIBuilder.php +++ b/src/OpenAPI/Builder/Internal/Schema.php @@ -14,7 +14,10 @@ use Membrane\Validator\Type\IsNull; use Membrane\Validator\Utility; -class APIBuilder +/** + * @internal see README.md + */ +class Schema { private Arrays $arrayBuilder; private TrueFalse $trueFalseBuilder; diff --git a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php index e999963b..fd0562ef 100644 --- a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php +++ b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php @@ -6,7 +6,7 @@ use Membrane\Builder\Builder; use Membrane\Builder\Specification; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Filter; use Membrane\OpenAPI\Processor\Request as RequestProcessor; @@ -21,7 +21,7 @@ class OpenAPIRequestBuilder implements Builder { - private APIBuilder $apiBuilder; + private Schema $apiBuilder; public function supports(Specification $specification): bool { @@ -126,8 +126,8 @@ private function fromParameters(OpenAPIRequest $specification): array return $fieldSets; } - private function getApiBuilder(): APIBuilder + private function getApiBuilder(): Schema { - return $this->apiBuilder ??= new APIBuilder(); + return $this->apiBuilder ??= new Schema(); } } diff --git a/src/OpenAPI/Builder/OpenAPIResponseBuilder.php b/src/OpenAPI/Builder/OpenAPIResponseBuilder.php index 92ebc5ee..d17a9426 100644 --- a/src/OpenAPI/Builder/OpenAPIResponseBuilder.php +++ b/src/OpenAPI/Builder/OpenAPIResponseBuilder.php @@ -5,13 +5,13 @@ namespace Membrane\OpenAPI\Builder; use Membrane\Builder\Specification; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\Processor; use Membrane\Processor\Field; use Membrane\Validator\Utility\Passes; -class OpenAPIResponseBuilder extends APIBuilder +class OpenAPIResponseBuilder extends Schema { public function supports(Specification $specification): bool { diff --git a/tests/Console/Command/CacheOpenAPIProcessorsTest.php b/tests/Console/Command/CacheOpenAPIProcessorsTest.php index 7c08ce55..811ea07e 100644 --- a/tests/Console/Command/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Command/CacheOpenAPIProcessorsTest.php @@ -37,7 +37,7 @@ #[UsesClass(Template\Processor::class)] #[UsesClass(Template\ResponseBuilder::class)] #[UsesClass(Template\RequestBuilder::class)] -#[UsesClass(Builder\Internal\APIBuilder::class)] +#[UsesClass(Builder\Internal\Schema::class)] #[UsesClass(Builder\Internal\Arrays::class)] #[UsesClass(Builder\Internal\Numeric::class)] #[UsesClass(Builder\Internal\Strings::class)] diff --git a/tests/Console/Service/CacheOpenAPIProcessorsTest.php b/tests/Console/Service/CacheOpenAPIProcessorsTest.php index e9adee25..9f9ae9ed 100644 --- a/tests/Console/Service/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Service/CacheOpenAPIProcessorsTest.php @@ -33,7 +33,7 @@ #[UsesClass(Template\Processor::class)] #[UsesClass(Template\ResponseBuilder::class)] #[UsesClass(Template\RequestBuilder::class)] -#[UsesClass(Builder\Internal\APIBuilder::class)] +#[UsesClass(Builder\Internal\Schema::class)] #[UsesClass(Builder\Internal\Arrays::class)] #[UsesClass(Builder\Internal\Numeric::class)] #[UsesClass(Builder\Internal\Strings::class)] diff --git a/tests/MembraneTest.php b/tests/MembraneTest.php index 0955a12a..7e1c4579 100644 --- a/tests/MembraneTest.php +++ b/tests/MembraneTest.php @@ -32,7 +32,7 @@ #[CoversClass(Membrane::class)] #[UsesClass(AttributeBuilder::class)] -#[UsesClass(Builder\Internal\APIBuilder::class)] +#[UsesClass(Builder\Internal\Schema::class)] #[UsesClass(Builder\Internal\Arrays::class)] #[UsesClass(Builder\Internal\Numeric::class)] #[UsesClass(Builder\Internal\Strings::class)] diff --git a/tests/OpenAPI/Builder/Internal/ArraysTest.php b/tests/OpenAPI/Builder/Internal/ArraysTest.php index 216c6af2..aa1c66c5 100644 --- a/tests/OpenAPI/Builder/Internal/ArraysTest.php +++ b/tests/OpenAPI/Builder/Internal/ArraysTest.php @@ -4,7 +4,7 @@ namespace Membrane\Tests\OpenAPI\Builder\Internal; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\Internal\Arrays; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPIReader\ValueObject\Partial; @@ -30,7 +30,7 @@ use PHPUnit\Framework\TestCase; #[CoversClass(Arrays::class)] -#[CoversClass(APIBuilder::class)] +#[CoversClass(Schema::class)] #[UsesClass(Numeric::class)] #[UsesClass(AnyOf::class)] #[UsesClass(BeforeSet::class)] diff --git a/tests/OpenAPI/Builder/Internal/NumericTest.php b/tests/OpenAPI/Builder/Internal/NumericTest.php index e0a1792d..c2ec7000 100644 --- a/tests/OpenAPI/Builder/Internal/NumericTest.php +++ b/tests/OpenAPI/Builder/Internal/NumericTest.php @@ -7,7 +7,7 @@ use Membrane\Filter\Type\ToFloat; use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; @@ -31,7 +31,7 @@ use PHPUnit\Framework\TestCase; #[CoversClass(Numeric::class)] -#[CoversClass(APIBuilder::class)] +#[CoversClass(Schema::class)] #[UsesClass(AnyOf::class)] #[UsesClass(Field::class)] #[UsesClass(Contained::class)] diff --git a/tests/OpenAPI/Builder/Internal/ObjectsTest.php b/tests/OpenAPI/Builder/Internal/ObjectsTest.php index 7b0a38eb..0f6297ae 100644 --- a/tests/OpenAPI/Builder/Internal/ObjectsTest.php +++ b/tests/OpenAPI/Builder/Internal/ObjectsTest.php @@ -4,7 +4,7 @@ namespace Membrane\Tests\OpenAPI\Builder\Internal; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPI\Builder\Internal\Objects; use Membrane\OpenAPI\Builder\Internal\Strings; @@ -34,7 +34,7 @@ use PHPUnit\Framework\TestCase; #[CoversClass(Objects::class)] -#[CoversClass(APIBuilder::class)] +#[CoversClass(Schema::class)] #[UsesClass(Numeric::class)] #[UsesClass(TrueFalse::class)] #[UsesClass(Strings::class)] diff --git a/tests/OpenAPI/Builder/Internal/StringsTest.php b/tests/OpenAPI/Builder/Internal/StringsTest.php index 474ad114..9baa1164 100644 --- a/tests/OpenAPI/Builder/Internal/StringsTest.php +++ b/tests/OpenAPI/Builder/Internal/StringsTest.php @@ -5,7 +5,7 @@ namespace Membrane\Tests\OpenAPI\Builder\Internal; use Membrane\Filter\String\ToUpperCase; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\Internal\Strings; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; @@ -25,7 +25,7 @@ use PHPUnit\Framework\TestCase; #[CoversClass(Strings::class)] -#[CoversClass(APIBuilder::class)] +#[CoversClass(Schema::class)] #[UsesClass(AnyOf::class)] #[UsesClass(Field::class)] #[UsesClass(Contained::class)] diff --git a/tests/OpenAPI/Builder/Internal/TrueFalseTest.php b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php index 61382228..8829265a 100644 --- a/tests/OpenAPI/Builder/Internal/TrueFalseTest.php +++ b/tests/OpenAPI/Builder/Internal/TrueFalseTest.php @@ -5,7 +5,7 @@ namespace Membrane\Tests\OpenAPI\Builder\Internal; use Membrane\Filter\Type\ToBool; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\Internal\TrueFalse; use Membrane\OpenAPIReader\ValueObject\Partial; use Membrane\OpenAPIReader\ValueObject\Valid\{Identifier, V30, V31}; @@ -23,7 +23,7 @@ use PHPUnit\Framework\TestCase; #[CoversClass(TrueFalse::class)] -#[CoversClass(APIBuilder::class)] +#[CoversClass(Schema::class)] #[UsesClass(AnyOf::class)] #[UsesClass(Field::class)] #[UsesClass(Contained::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php index 080cb1a6..ec7af00d 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php @@ -15,7 +15,7 @@ use Membrane\Filter\Type\ToBool; use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\Internal\Arrays; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPI\Builder\Internal\Objects; @@ -78,7 +78,7 @@ use Psr\Http\Message\ServerRequestInterface; #[CoversClass(OpenAPIRequestBuilder::class)] -#[CoversClass(APIBuilder::class)] +#[CoversClass(Schema::class)] #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] #[UsesClass(HumanReadable::class)] // to render test failure messages diff --git a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php index 3726625e..7559a59d 100644 --- a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php @@ -7,7 +7,7 @@ use Generator; use Membrane\Builder\Specification; use Membrane\Filter\String\ToUpperCase; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Exception\CannotProcessSpecification; @@ -57,7 +57,7 @@ #[CoversClass(OpenAPIResponseBuilder::class)] #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] -#[CoversClass(APIBuilder::class)] +#[CoversClass(Schema::class)] #[UsesClass(OpenAPIResponse::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\Arrays::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] diff --git a/tests/OpenAPI/Builder/RequestBuilderTest.php b/tests/OpenAPI/Builder/RequestBuilderTest.php index 62720203..7e654d3d 100644 --- a/tests/OpenAPI/Builder/RequestBuilderTest.php +++ b/tests/OpenAPI/Builder/RequestBuilderTest.php @@ -9,7 +9,7 @@ use Membrane\Filter\String\Explode; use Membrane\Filter\String\Implode; use Membrane\Filter\Type\ToInt; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\Internal\Arrays; use Membrane\OpenAPI\Builder\Internal\Numeric; use Membrane\OpenAPI\Builder\Internal\Strings; @@ -58,7 +58,7 @@ #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] #[UsesClass(HumanReadable::class)] -#[UsesClass(APIBuilder::class)] +#[UsesClass(Schema::class)] #[UsesClass(OpenAPIRequestBuilder::class)] #[UsesClass(OpenAPIRequest::class)] #[UsesClass(Request::class)] diff --git a/tests/OpenAPI/Builder/ResponseBuilderTest.php b/tests/OpenAPI/Builder/ResponseBuilderTest.php index 6ba4d4cb..f6cb9ee7 100644 --- a/tests/OpenAPI/Builder/ResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/ResponseBuilderTest.php @@ -6,7 +6,7 @@ use Membrane\Builder\Specification; use Membrane\Filter\String\ToUpperCase; -use Membrane\OpenAPI\Builder\Internal\APIBuilder; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder; use Membrane\OpenAPI\Builder\ResponseBuilder; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; @@ -58,7 +58,7 @@ #[CoversClass(CannotProcessResponse::class)] #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] -#[UsesClass(APIBuilder::class)] +#[UsesClass(Schema::class)] #[UsesClass(OpenAPIResponseBuilder::class)] #[UsesClass(OpenAPIResponse::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\Arrays::class)] From 44921cbafdbcd87cc1eded86e993bcecf91c5faa Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 16:43:53 +0100 Subject: [PATCH 36/47] Test unsupported media types on parameters --- .../Builder/OpenAPIRequestBuilderTest.php | 25 +-- .../Specification/OpenAPIRequestTest.php | 161 +----------------- tests/fixtures/OpenAPI/Provider.php | 20 +++ .../OpenAPI/ProvidesUnsupportedContent.php | 43 +++++ 4 files changed, 79 insertions(+), 170 deletions(-) create mode 100644 tests/fixtures/OpenAPI/Provider.php create mode 100644 tests/fixtures/OpenAPI/ProvidesUnsupportedContent.php diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php index ec7af00d..72a6a4aa 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php @@ -58,6 +58,7 @@ use Membrane\Result\Message; use Membrane\Result\MessageSet; use Membrane\Result\Result; +use Membrane\Tests\Fixtures; use Membrane\Tests\MembraneTestCase; use Membrane\Validator\FieldSet\RequiredFields; use Membrane\Validator\Numeric\Maximum; @@ -139,18 +140,22 @@ class OpenAPIRequestBuilderTest extends MembraneTestCase #[Test, TestDox('Exceptions will be thrown for parameters with unsupported content types')] public function throwsExceptionForUnsupportedContentTypes(): void { - self::markTestSkipped(); - - $parameter = new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'test-param', - in: 'query', - content: [new Partial\MediaType(contentType: 'application/pdf', schema: new Partial\Schema())], - )); + $path = '/path'; + $method = Method::GET; + $mediaType = 'application/pdf'; + $pathItem = Fixtures\OpenAPI\ProvidesUnsupportedContent::pathItemMediaType( + path: $path, + method: $method, + mediaType: $mediaType, + ); - self::expectExceptionObject(CannotProcessOpenAPI::unsupportedMediaTypes('application/pdf')); + self::expectExceptionObject(CannotProcessOpenAPI::unsupportedMediaTypes($mediaType)); - $sut = new OpenAPIRequestBuilder(); - new $sut->build($parameter); + new OpenAPIRequestBuilder()->build(new OpenAPIRequest( + new PathParameterExtractor($path), + $pathItem, + $method, + )); } #[Test, TestDox('It will support the OpenAPIRequest Specification')] diff --git a/tests/OpenAPI/Specification/OpenAPIRequestTest.php b/tests/OpenAPI/Specification/OpenAPIRequestTest.php index d284b36a..c32973ba 100644 --- a/tests/OpenAPI/Specification/OpenAPIRequestTest.php +++ b/tests/OpenAPI/Specification/OpenAPIRequestTest.php @@ -39,165 +39,6 @@ protected function setUp(): void $this->pathParameterExtractor = new PathParameterExtractor('/pets'); } - - - public static function provideValidParameters(): array - { - return [ - '"style": "matrix" in "path"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'path', - required: true, - style: 'matrix', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'style' => 'matrix', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(path)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style": "label" in "path"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'path', - required: true, - style: 'label', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'style' => 'label', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(path)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"form" in "query"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'query', - required: false, - style: 'form', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'query', - 'required' => false, - 'style' => 'form', - 'explode' => true, - 'schema' => new V30\Schema( - new Identifier('test', 'id(query)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"simple" in "path"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'path', - required: true, - style: 'simple', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'path', - 'required' => true, - 'style' => 'simple', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(path)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"spaceDelimited" in "query"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'query', - required: false, - style: 'spaceDelimited', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'query', - 'required' => false, - 'style' => 'spaceDelimited', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(query)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"pipeDelimited" in "query"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'query', - required: false, - style: 'pipeDelimited', - schema: new Partial\Schema(type: 'integer'), - )), - [ - 'name' => 'id', - 'in' => 'query', - 'required' => false, - 'style' => 'pipeDelimited', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(query)', 'schema'), - new Partial\Schema(type: 'integer'), - ), - ], - ], - '"style":"deepObject" in "query"' => [ - new V30\Parameter(new Identifier('test'), new Partial\Parameter( - name: 'id', - in: 'query', - required: false, - style: 'deepObject', - explode: false, - schema: new Partial\Schema(type: 'object'), - )), - [ - 'name' => 'id', - 'in' => 'query', - 'required' => false, - 'style' => 'deepObject', - 'explode' => false, - 'schema' => new V30\Schema( - new Identifier('test', 'id(query)', 'schema'), - new Partial\Schema(type: 'object'), - ), - ], - ], - ]; - } - - #[Test] - #[TestDox('It will construct itself from valid Parameters')] - #[DataProvider('provideValidParameters')] - public function itConstructsFromValidParameters( - V30\Parameter|V31\Parameter $parameter, - array $expectedProperties - ): void { - self::markTestSkipped(); - } - #[Test, TestDox('If the method given is not specified on the PathItem then an exception will be thrown')] public function throwsExceptionIfMethodNotFound(): void { @@ -209,7 +50,7 @@ public function throwsExceptionIfMethodNotFound(): void #[Test, TestDox('Throws an exception if the request body contains content that is not supported')] public function throwsExceptionIfRequestBodyContentContainsUnsupportedMediaTypes(): void { - $pathItem = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $pathItem = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath(__DIR__ . '/../../fixtures/OpenAPI/noReferences.json') ->paths['/path']; diff --git a/tests/fixtures/OpenAPI/Provider.php b/tests/fixtures/OpenAPI/Provider.php new file mode 100644 index 00000000..c63d047c --- /dev/null +++ b/tests/fixtures/OpenAPI/Provider.php @@ -0,0 +1,20 @@ +value: + operationId: testcase + parameters: + - name: $parameter + in: header + content: + $mediaType: + schema: + type: integer + responses: + 200: + description: Success + YAML; + + return self::reader() + ->readFromString($api, FileFormat::Yaml) + ->paths[$path]; + } +} From 6459a8761531c35c27c434c277c68459ac008f3f Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 17:03:39 +0100 Subject: [PATCH 37/47] Rename OpenAPIRequestBuilder variable --- src/OpenAPI/Builder/OpenAPIRequestBuilder.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php index fd0562ef..eb953c9f 100644 --- a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php +++ b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php @@ -21,7 +21,7 @@ class OpenAPIRequestBuilder implements Builder { - private Schema $apiBuilder; + private Schema $schemaBuilder; public function supports(Specification $specification): bool { @@ -49,7 +49,7 @@ private function fromRequestBody(OpenAPIRequest $specification): Processor return new Field('requestBody', new Passes()); } - return $this->getApiBuilder()->fromSchema( + return $this->getSchemaBuilder()->fromSchema( $specification->requestBodySchema, 'requestBody', ); @@ -91,7 +91,7 @@ private function fromParameters(OpenAPIRequest $specification): array } $locations[$parameter->in->value]['fields'][] = $this - ->getApiBuilder() + ->getSchemaBuilder() ->fromSchema( $parameter->getSchema(), $parameter->name, @@ -126,8 +126,8 @@ private function fromParameters(OpenAPIRequest $specification): array return $fieldSets; } - private function getApiBuilder(): Schema + private function getSchemaBuilder(): Schema { - return $this->apiBuilder ??= new Schema(); + return $this->schemaBuilder ??= new Schema(); } } From 113335a6569d987c57bc81bc8701f0f423ee095b Mon Sep 17 00:00:00 2001 From: John Charman Date: Tue, 22 Sep 2026 17:03:50 +0100 Subject: [PATCH 38/47] Unimplement builder in OpenAPIRequestBuilder --- src/OpenAPI/Builder/OpenAPIRequestBuilder.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php index eb953c9f..344b4ab7 100644 --- a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php +++ b/src/OpenAPI/Builder/OpenAPIRequestBuilder.php @@ -4,7 +4,6 @@ namespace Membrane\OpenAPI\Builder; -use Membrane\Builder\Builder; use Membrane\Builder\Specification; use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; @@ -19,7 +18,7 @@ use Membrane\Validator\FieldSet\RequiredFields; use Membrane\Validator\Utility\Passes; -class OpenAPIRequestBuilder implements Builder +class OpenAPIRequestBuilder { private Schema $schemaBuilder; From ece772486667289c602105cc7d3321efa642f325 Mon Sep 17 00:00:00 2001 From: John Charman Date: Wed, 23 Sep 2026 10:16:53 +0100 Subject: [PATCH 39/47] Rename Builder\OpenAPIRequestBuilder to Internal\Request --- .../Service/YieldsClassDefinitions.php | 13 ++--- .../Request.php} | 13 ++--- src/OpenAPI/Builder/RequestBuilder.php | 15 +++--- .../Command/CacheOpenAPIProcessorsTest.php | 12 ++--- .../Service/CacheOpenAPIProcessorsTest.php | 17 +++--- tests/MembraneTest.php | 2 +- .../RequestTest.php} | 54 ++++++------------- tests/OpenAPI/Builder/RequestBuilderTest.php | 18 +++---- 8 files changed, 54 insertions(+), 90 deletions(-) rename src/OpenAPI/Builder/{OpenAPIRequestBuilder.php => Internal/Request.php} (94%) rename tests/OpenAPI/Builder/{OpenAPIRequestBuilderTest.php => Internal/RequestTest.php} (97%) diff --git a/src/Console/Service/YieldsClassDefinitions.php b/src/Console/Service/YieldsClassDefinitions.php index b46dfe1c..6ec95386 100644 --- a/src/Console/Service/YieldsClassDefinitions.php +++ b/src/Console/Service/YieldsClassDefinitions.php @@ -4,23 +4,20 @@ namespace Membrane\Console\Service; -use Membrane\Filter; use Membrane\Console\Template; -use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder; +use Membrane\Filter; +use Membrane\OpenAPI\Builder\Internal\Request; use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder; use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor; use Membrane\OpenAPI\Specification\OpenAPIRequest; use Membrane\OpenAPI\Specification\OpenAPIResponse; -use Membrane\OpenAPIReader\Exception\CannotRead; -use Membrane\OpenAPIReader\Exception\CannotSupport; -use Membrane\OpenAPIReader\Exception\InvalidOpenAPI; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; use Membrane\OpenAPIReader\ValueObject\Valid\{Enum\Method, V30, V31}; final class YieldsClassDefinitions { - private OpenAPIRequestBuilder $requestBuilder; + private Request $requestBuilder; private OpenAPIResponseBuilder $responseBuilder; public function __construct( @@ -143,10 +140,10 @@ private function createSuitableClassName(string $nameToConvert, array $existingC } - private function getRequestBuilder(): OpenAPIRequestBuilder + private function getRequestBuilder(): Request { if (!isset($this->requestBuilder)) { - $this->requestBuilder = new OpenAPIRequestBuilder(); + $this->requestBuilder = new Request(); return $this->requestBuilder; } diff --git a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php b/src/OpenAPI/Builder/Internal/Request.php similarity index 94% rename from src/OpenAPI/Builder/OpenAPIRequestBuilder.php rename to src/OpenAPI/Builder/Internal/Request.php index 344b4ab7..01d9d361 100644 --- a/src/OpenAPI/Builder/OpenAPIRequestBuilder.php +++ b/src/OpenAPI/Builder/Internal/Request.php @@ -2,10 +2,9 @@ declare(strict_types=1); -namespace Membrane\OpenAPI\Builder; +namespace Membrane\OpenAPI\Builder\Internal; use Membrane\Builder\Specification; -use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Filter; use Membrane\OpenAPI\Processor\Request as RequestProcessor; @@ -18,15 +17,13 @@ use Membrane\Validator\FieldSet\RequiredFields; use Membrane\Validator\Utility\Passes; -class OpenAPIRequestBuilder +/** + * @internal see README.md + */ +class Request { private Schema $schemaBuilder; - public function supports(Specification $specification): bool - { - return $specification instanceof OpenAPIRequest; - } - public function build(Specification $specification): Processor { assert($specification instanceof OpenAPIRequest); diff --git a/src/OpenAPI/Builder/RequestBuilder.php b/src/OpenAPI/Builder/RequestBuilder.php index 2164a460..ba838037 100644 --- a/src/OpenAPI/Builder/RequestBuilder.php +++ b/src/OpenAPI/Builder/RequestBuilder.php @@ -5,6 +5,7 @@ namespace Membrane\OpenAPI\Builder; use Membrane\Builder\{Builder, Specification}; +use Membrane\OpenAPI\Builder\Internal; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; use Membrane\OpenAPI\Specification\OpenAPIRequest; @@ -16,7 +17,7 @@ class RequestBuilder implements Builder { - private OpenAPIRequestBuilder $requestBuilder; + private Internal\Request $requestBuilder; public function supports(Specification $specification): bool { @@ -27,10 +28,10 @@ public function build(Specification $specification): Processor { assert($specification instanceof Request); - $openAPI = (new MembraneReader([ + $openAPI = new MembraneReader([ OpenAPIVersion::Version_3_0, OpenAPIVersion::Version_3_1, - ]))->readFromAbsoluteFilePath($specification->absoluteFilePath); + ])->readFromAbsoluteFilePath($specification->absoluteFilePath); $serverUrl = $this->matchServer($openAPI, $specification->url); foreach ($openAPI->paths as $path => $pathItem) { @@ -54,13 +55,9 @@ public function build(Specification $specification): Processor ); } - private function getOpenAPIRequestBuilder(): OpenAPIRequestBuilder + private function getOpenAPIRequestBuilder(): Internal\Request { - if (!isset($this->requestBuilder)) { - $this->requestBuilder = new OpenAPIRequestBuilder(); - } - - return $this->requestBuilder; + return $this->requestBuilder ??= new Internal\Request(); } private function matchServer( diff --git a/tests/Console/Command/CacheOpenAPIProcessorsTest.php b/tests/Console/Command/CacheOpenAPIProcessorsTest.php index 811ea07e..293acd3c 100644 --- a/tests/Console/Command/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Command/CacheOpenAPIProcessorsTest.php @@ -12,11 +12,11 @@ use Membrane\Filter\String\ToPascalCase; use Membrane\Filter\Type as TypeFilter; use Membrane\OpenAPI\Builder as Builder; -use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder; +use Membrane\OpenAPI\Builder\Internal\Request; use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor; use Membrane\OpenAPI\Filter\PathMatcher; -use Membrane\OpenAPI\Processor\Request; +use Membrane\OpenAPI; use Membrane\OpenAPI\Specification as Specification; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; @@ -42,14 +42,14 @@ #[UsesClass(Builder\Internal\Numeric::class)] #[UsesClass(Builder\Internal\Strings::class)] #[UsesClass(Builder\Internal\Objects::class)] -#[UsesClass(Builder\OpenAPIRequestBuilder::class)] +#[UsesClass(Builder\Internal\Request::class)] #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(PathMatcher::class)] #[UsesClass(PathParameterExtractor::class)] #[UsesClass(Processor\AllOf::class)] #[UsesClass(Membrane\OpenAPI\Filter\QueryStringToArray::class)] #[UsesClass(Membrane\OpenAPI\Filter\FormatStyle\Form::class)] -#[UsesClass(Request::class)] +#[UsesClass(OpenAPI\Processor\Request::class)] #[UsesClass(Specification\OpenAPIRequest::class)] #[UsesClass(Specification\OpenAPIResponse::class)] #[UsesClass(Membrane\Result\Result::class)] @@ -120,7 +120,7 @@ public function failsOnUnreadableOpenAPI(string $openAPI, string $destination, i public static function provideCasesToCache(): array { - $requestBuilder = new OpenAPIRequestBuilder(); + $requestBuilder = new Request(); $responseBuilder = new Builder\OpenAPIResponseBuilder(); $petstoreExpandedFilePath = __DIR__ . '/../../fixtures/OpenAPI/docs/petstore-expanded.json'; @@ -305,7 +305,7 @@ public function cachesProcessorsWithSuitableNamesToAvoidDuplicates(): void $hatstoreAPI = (new MembraneReader([OpenAPIVersion::Version_3_0])) ->readFromAbsoluteFilePath($hatstoreFilePath); - $requestBuilder = new Builder\OpenAPIRequestBuilder(); + $requestBuilder = new Builder\Internal\Request(); $expectedFindHats = $requestBuilder->build( new Specification\OpenAPIRequest( new PathParameterExtractor('/hats'), diff --git a/tests/Console/Service/CacheOpenAPIProcessorsTest.php b/tests/Console/Service/CacheOpenAPIProcessorsTest.php index 9f9ae9ed..8352feab 100644 --- a/tests/Console/Service/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Service/CacheOpenAPIProcessorsTest.php @@ -9,12 +9,11 @@ use Membrane\Console\Template; use Membrane\Filter\{String\AlphaNumeric, String\Explode, String\ToPascalCase, Type as TypeFilter}; use Membrane\OpenAPI\Builder as Builder; -use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder; +use Membrane\OpenAPI\Builder\Internal\Request; use Membrane\OpenAPI\ContentType; -use Membrane\OpenAPI\Exception\CannotReadOpenAPI; use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor; use Membrane\OpenAPI\Filter\PathMatcher; -use Membrane\OpenAPI\Processor\Request; +use Membrane\OpenAPI; use Membrane\OpenAPI\Specification; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; @@ -38,14 +37,14 @@ #[UsesClass(Builder\Internal\Numeric::class)] #[UsesClass(Builder\Internal\Strings::class)] #[UsesClass(Builder\Internal\Objects::class)] -#[UsesClass(Builder\OpenAPIRequestBuilder::class)] +#[UsesClass(Builder\Internal\Request::class)] #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(PathMatcher::class)] #[UsesClass(PathParameterExtractor::class)] #[UsesClass(Processor\AllOf::class)] -#[UsesClass(Membrane\OpenAPI\Filter\QueryStringToArray::class)] -#[UsesClass(Membrane\OpenAPI\Filter\FormatStyle\Form::class)] -#[UsesClass(Request::class)] +#[UsesClass(OpenAPI\Filter\QueryStringToArray::class)] +#[UsesClass(OpenAPI\Filter\FormatStyle\Form::class)] +#[UsesClass(OpenAPI\Processor\Request::class)] #[UsesClass(Specification\OpenAPIRequest::class)] #[UsesClass(Specification\OpenAPIResponse::class)] #[UsesClass(Membrane\Result\Result::class)] @@ -118,7 +117,7 @@ public function cachesProcessorsWithSuitableNamesToAvoidDuplicates(): void $hatstoreApi = (new MembraneReader([OpenAPIVersion::Version_3_0])) ->readFromAbsoluteFilePath($hatstoreFilePath); - $requestBuilder = new Builder\OpenAPIRequestBuilder(); + $requestBuilder = new Builder\Internal\Request(); $expectedFindHats = $requestBuilder->build( new Specification\OpenAPIRequest( new PathParameterExtractor('/hats'), @@ -209,7 +208,7 @@ public function cacheOnlyResponseProcessors(): void public static function provideCasesOfCachedRequestsFromPetstoreExpanded(): array { - $requestBuilder = new OpenAPIRequestBuilder(); + $requestBuilder = new Request(); $petstoreExpandedFilePath = __DIR__ . '/../../fixtures/OpenAPI/docs/petstore-expanded.json'; $petstoreExpandedOpenApi = (new MembraneReader([OpenAPIVersion::Version_3_0])) ->readFromAbsoluteFilePath($petstoreExpandedFilePath); diff --git a/tests/MembraneTest.php b/tests/MembraneTest.php index 7e1c4579..092674a5 100644 --- a/tests/MembraneTest.php +++ b/tests/MembraneTest.php @@ -37,7 +37,7 @@ #[UsesClass(Builder\Internal\Numeric::class)] #[UsesClass(Builder\Internal\Strings::class)] #[UsesClass(Builder\Internal\Objects::class)] -#[UsesClass(Builder\OpenAPIRequestBuilder::class)] +#[UsesClass(Builder\Internal\Request::class)] #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(Builder\RequestBuilder::class)] #[UsesClass(Builder\ResponseBuilder::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php b/tests/OpenAPI/Builder/Internal/RequestTest.php similarity index 97% rename from tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php rename to tests/OpenAPI/Builder/Internal/RequestTest.php index 72a6a4aa..403b1a7d 100644 --- a/tests/OpenAPI/Builder/OpenAPIRequestBuilderTest.php +++ b/tests/OpenAPI/Builder/Internal/RequestTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Membrane\Tests\OpenAPI\Builder; +namespace Membrane\Tests\OpenAPI\Builder\Internal; use Generator; use GuzzleHttp\Psr7\ServerRequest; @@ -15,12 +15,8 @@ use Membrane\Filter\Type\ToBool; use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; +use Membrane\OpenAPI\Builder\Internal; use Membrane\OpenAPI\Builder\Internal\Schema; -use Membrane\OpenAPI\Builder\Internal\Arrays; -use Membrane\OpenAPI\Builder\Internal\Numeric; -use Membrane\OpenAPI\Builder\Internal\Objects; -use Membrane\OpenAPI\Builder\Internal\Strings; -use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder; use Membrane\OpenAPI\Builder\RequestBuilder; use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; @@ -78,7 +74,7 @@ use PHPUnit\Framework\Attributes\UsesClass; use Psr\Http\Message\ServerRequestInterface; -#[CoversClass(OpenAPIRequestBuilder::class)] +#[CoversClass(Request::class)] #[CoversClass(Schema::class)] #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] @@ -86,11 +82,11 @@ #[UsesClass(RequestBuilder::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] #[UsesClass(OpenAPIRequest::class)] -#[UsesClass(Request::class)] -#[UsesClass(Arrays::class)] -#[UsesClass(Numeric::class)] -#[UsesClass(Objects::class)] -#[UsesClass(Strings::class)] +#[UsesClass(Internal\Request::class)] +#[UsesClass(Internal\Arrays::class)] +#[UsesClass(Internal\Numeric::class)] +#[UsesClass(Internal\Objects::class)] +#[UsesClass(Internal\Strings::class)] #[UsesClass(QueryStringToArray::class)] #[UsesClass(PathMatcher::class)] #[UsesClass(PathParameterExtractor::class)] @@ -133,9 +129,9 @@ #[UsesClass(ContentType::class)] #[UsesClass(LeftTrim::class)] #[UsesClass(KeyValueSplit::class)] -class OpenAPIRequestBuilderTest extends MembraneTestCase +class RequestTest extends MembraneTestCase { - public const string FIXTURES = __DIR__ . '/../../fixtures/OpenAPI/'; + public const string FIXTURES = __DIR__ . '/../../../fixtures/OpenAPI/'; #[Test, TestDox('Exceptions will be thrown for parameters with unsupported content types')] public function throwsExceptionForUnsupportedContentTypes(): void @@ -151,35 +147,17 @@ public function throwsExceptionForUnsupportedContentTypes(): void self::expectExceptionObject(CannotProcessOpenAPI::unsupportedMediaTypes($mediaType)); - new OpenAPIRequestBuilder()->build(new OpenAPIRequest( + new Internal\Request()->build(new OpenAPIRequest( new PathParameterExtractor($path), $pathItem, $method, )); } - #[Test, TestDox('It will support the OpenAPIRequest Specification')] - public function supportsRequestSpecification(): void - { - $specification = self::createStub(OpenAPIRequest::class); - $sut = new OpenAPIRequestBuilder(); - - self::assertTrue($sut->supports($specification)); - } - - #[Test, TestDox('It will not support any Specifications other than OpenAPIRequest')] - public function doesNotSupportSpecificationsOtherThanRequest(): void - { - $specification = self::createStub(\Membrane\Builder\Specification::class); - $sut = new OpenAPIRequestBuilder(); - - self::assertFalse($sut->supports($specification)); - } - #[Test, TestDox('It currently only supports application/json content')] public function throwsExceptionIfParameterHasContentThatIsNotJson(): void { - $openAPI = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $openAPI = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath((self::FIXTURES . 'noReferences.json')); $specification = new OpenAPIRequest( @@ -188,7 +166,7 @@ public function throwsExceptionIfParameterHasContentThatIsNotJson(): void Method::POST ); - $sut = new OpenAPIRequestBuilder(); + $sut = new Internal\Request(); $mediaTypes = array_keys($openAPI->paths['/requestpathexceptions']->post->parameters[0]->content); @@ -202,7 +180,7 @@ public function throwsExceptionIfParameterHasContentThatIsNotJson(): void #[DataProvider('dataSetsForBuild')] public function buildTest(Specification $spec, Processor $expected): void { - $sut = new OpenAPIRequestBuilder(); + $sut = new Internal\Request(); $actual = $sut->build($spec); @@ -219,7 +197,7 @@ public function itBuildsProcessorsThatValidateRequests( array | ServerRequestInterface $serverRequest, Result $expected ): void { - $sut = new OpenAPIRequestBuilder(); + $sut = new Internal\Request(); $processor = $sut->build($specification); @@ -233,7 +211,7 @@ public static function dataSetsForBuild(): Generator { $reader = new MembraneReader([OpenAPIVersion::Version_3_0]); $noRefAPI = $reader - ->readFromAbsoluteFilePath(__DIR__ . '/../../fixtures/OpenAPI/noReferences.json'); + ->readFromAbsoluteFilePath(__DIR__ . '/../../../fixtures/OpenAPI/noReferences.json'); yield 'Request: no path params, no operation params, no requestBody' => [ new OpenAPIRequest( diff --git a/tests/OpenAPI/Builder/RequestBuilderTest.php b/tests/OpenAPI/Builder/RequestBuilderTest.php index 7e654d3d..eb550efc 100644 --- a/tests/OpenAPI/Builder/RequestBuilderTest.php +++ b/tests/OpenAPI/Builder/RequestBuilderTest.php @@ -9,11 +9,7 @@ use Membrane\Filter\String\Explode; use Membrane\Filter\String\Implode; use Membrane\Filter\Type\ToInt; -use Membrane\OpenAPI\Builder\Internal\Schema; -use Membrane\OpenAPI\Builder\Internal\Arrays; -use Membrane\OpenAPI\Builder\Internal\Numeric; -use Membrane\OpenAPI\Builder\Internal\Strings; -use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder; +use Membrane\OpenAPI\Builder\Internal; use Membrane\OpenAPI\Builder\RequestBuilder; use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; @@ -58,13 +54,13 @@ #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] #[UsesClass(HumanReadable::class)] -#[UsesClass(Schema::class)] -#[UsesClass(OpenAPIRequestBuilder::class)] +#[UsesClass(Internal\Schema::class)] +#[UsesClass(Internal\Request::class)] +#[UsesClass(Internal\Arrays::class)] +#[UsesClass(Internal\Numeric::class)] +#[UsesClass(Internal\Strings::class)] #[UsesClass(OpenAPIRequest::class)] #[UsesClass(Request::class)] -#[UsesClass(Arrays::class)] -#[UsesClass(Numeric::class)] -#[UsesClass(Strings::class)] #[UsesClass(QueryStringToArray::class)] #[UsesClass(Form::class)] #[UsesClass(PathMatcher::class)] @@ -100,7 +96,7 @@ public function throwsExceptionIfParameterHasContentThatIsNotJson(): void $specification = new Request($openAPIFilePath, '/requestpathexceptions', Method::POST); $sut = new RequestBuilder(); - $openApi = (new Reader([OpenAPIVersion::Version_3_0])) + $openApi = new Reader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath($openAPIFilePath); $mediaTypes = array_keys($openApi->paths->getPath('/requestpathexceptions')->post->parameters[0]->content); From 87df7c386fb8173f4032a0caea4996a597e8ac82 Mon Sep 17 00:00:00 2001 From: John Charman Date: Wed, 23 Sep 2026 10:42:16 +0100 Subject: [PATCH 40/47] Replace get method with get property hook Since the method was a one-line lazy loader, it's simple enough to that a hook makes more sense. --- src/OpenAPI/Builder/Internal/Request.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/OpenAPI/Builder/Internal/Request.php b/src/OpenAPI/Builder/Internal/Request.php index 01d9d361..21006b1b 100644 --- a/src/OpenAPI/Builder/Internal/Request.php +++ b/src/OpenAPI/Builder/Internal/Request.php @@ -22,7 +22,9 @@ */ class Request { - private Schema $schemaBuilder; + private Schema $schemaBuilder { + get => $this->schemaBuilder ??= new Schema(); + } public function build(Specification $specification): Processor { @@ -45,7 +47,7 @@ private function fromRequestBody(OpenAPIRequest $specification): Processor return new Field('requestBody', new Passes()); } - return $this->getSchemaBuilder()->fromSchema( + return $this->schemaBuilder->fromSchema( $specification->requestBodySchema, 'requestBody', ); @@ -87,7 +89,7 @@ private function fromParameters(OpenAPIRequest $specification): array } $locations[$parameter->in->value]['fields'][] = $this - ->getSchemaBuilder() + ->schemaBuilder ->fromSchema( $parameter->getSchema(), $parameter->name, @@ -121,9 +123,4 @@ private function fromParameters(OpenAPIRequest $specification): array return $fieldSets; } - - private function getSchemaBuilder(): Schema - { - return $this->schemaBuilder ??= new Schema(); - } } From bb2b084dc891249837288189bcea1119c5b897d2 Mon Sep 17 00:00:00 2001 From: John Charman Date: Wed, 23 Sep 2026 12:14:44 +0100 Subject: [PATCH 41/47] Remove OpenAPIRequest Specification --- .../Service/YieldsClassDefinitions.php | 9 +- src/OpenAPI/Builder/Internal/Request.php | 73 ++-- src/OpenAPI/Builder/RequestBuilder.php | 8 +- src/OpenAPI/Specification/OpenAPIRequest.php | 65 ---- .../Command/CacheOpenAPIProcessorsTest.php | 49 +-- .../Service/CacheOpenAPIProcessorsTest.php | 57 ++-- tests/MembraneTest.php | 1 - .../OpenAPI/Builder/Internal/RequestTest.php | 311 ++++++++---------- tests/OpenAPI/Builder/RequestBuilderTest.php | 2 - .../Specification/OpenAPIRequestTest.php | 99 ------ 10 files changed, 235 insertions(+), 439 deletions(-) delete mode 100644 src/OpenAPI/Specification/OpenAPIRequest.php delete mode 100644 tests/OpenAPI/Specification/OpenAPIRequestTest.php diff --git a/src/Console/Service/YieldsClassDefinitions.php b/src/Console/Service/YieldsClassDefinitions.php index 6ec95386..bd6fd729 100644 --- a/src/Console/Service/YieldsClassDefinitions.php +++ b/src/Console/Service/YieldsClassDefinitions.php @@ -9,7 +9,6 @@ use Membrane\OpenAPI\Builder\Internal\Request; use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder; use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor; -use Membrane\OpenAPI\Specification\OpenAPIRequest; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; @@ -58,11 +57,9 @@ public function __invoke( namespace: "$cacheNamespace\\Request", name: $className, processor: $this->getRequestBuilder()->build( - new OpenAPIRequest( - new PathParameterExtractor($pathUrl), - $path, - $methodObject, - ) + new PathParameterExtractor($pathUrl), + $path, + $methodObject, ) ); } diff --git a/src/OpenAPI/Builder/Internal/Request.php b/src/OpenAPI/Builder/Internal/Request.php index 21006b1b..8dc66f58 100644 --- a/src/OpenAPI/Builder/Internal/Request.php +++ b/src/OpenAPI/Builder/Internal/Request.php @@ -4,11 +4,13 @@ namespace Membrane\OpenAPI\Builder\Internal; -use Membrane\Builder\Specification; +use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; +use Membrane\OpenAPI\Exception\CannotProcessSpecification; +use Membrane\OpenAPI\ExtractPathParameters\ExtractsPathParameters; use Membrane\OpenAPI\Filter; use Membrane\OpenAPI\Processor\Request as RequestProcessor; -use Membrane\OpenAPI\Specification\OpenAPIRequest; +use Membrane\OpenAPIReader\ValueObject\Valid\{Enum\Method, V30, V31}; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\In; use Membrane\Processor; use Membrane\Processor\BeforeSet; @@ -26,38 +28,63 @@ class Request get => $this->schemaBuilder ??= new Schema(); } - public function build(Specification $specification): Processor - { - assert($specification instanceof OpenAPIRequest); - - $processors = $this->fromParameters($specification); - $processors['body'] = $this->fromRequestBody($specification); + public function build( + ExtractsPathParameters $pathParameterExtractor, + V30\PathItem | V31\PathItem $pathItem, + Method $method, + ): Processor { + $operation = $pathItem->getOperations()[$method->value] + ?? throw CannotProcessSpecification::methodNotFound($method->value); return new RequestProcessor( '', - $specification->operationId, - $specification->method, - $processors + $operation->operationId, + $method, + [ + 'body' => $this->fromRequestBody($operation->requestBody), + ...$this->fromParameters( + $pathParameterExtractor, + $operation->parameters, + ), + ], ); } - private function fromRequestBody(OpenAPIRequest $specification): Processor - { - if ($specification->requestBodySchema === null) { + private function fromRequestBody( + null | V30\RequestBody | V31\RequestBody $requestBody, + ): Processor { + if ($requestBody === null || $requestBody->content === []) { return new Field('requestBody', new Passes()); } - return $this->schemaBuilder->fromSchema( - $specification->requestBodySchema, - 'requestBody', + foreach ($requestBody->content as $contentType => $mediaType) { + if ( + ContentType::fromContentTypeHeader($contentType) !== ContentType::Unmatched + && $mediaType->schema !== null + ) { + return $this->schemaBuilder->fromSchema( + $mediaType->schema, + 'requestBody', + ); + } + } + + throw CannotProcessOpenAPI::unsupportedMediaTypes( + ...array_keys($requestBody->content), ); } - /** @return Processor[] */ - private function fromParameters(OpenAPIRequest $specification): array - { + /** + * @param V30\Parameter[] | V31\Parameter[] $parameters + * + * @return Processor[] + */ + private function fromParameters( + ExtractsPathParameters $extractsPathParameters, + array $parameters, + ): array { $queryParameters = array_filter( - $specification->parameters, + $parameters, fn($p) => $p->in === In::Query, ); @@ -67,7 +94,7 @@ private function fromParameters(OpenAPIRequest $specification): array 'beforeSet' => $chain, ]; $locations = [ - 'path' => $location([new Filter\PathMatcher($specification->pathParameterExtractor)]), + 'path' => $location([new Filter\PathMatcher($extractsPathParameters)]), 'query' => $location([new Filter\QueryStringToArray(array_combine( array_map(fn($p) => $p->name, $queryParameters), array_map( @@ -82,7 +109,7 @@ private function fromParameters(OpenAPIRequest $specification): array 'cookie' => $location([]), ]; - foreach ($specification->parameters as $parameter) { + foreach ($parameters as $parameter) { if ($parameter->hasMediaType() && $parameter->getMediaType() !== 'application/json') { assert($parameter->getMediaType() !== null); throw CannotProcessOpenAPI::unsupportedMediaTypes($parameter->getMediaType()); diff --git a/src/OpenAPI/Builder/RequestBuilder.php b/src/OpenAPI/Builder/RequestBuilder.php index ba838037..b7662cb3 100644 --- a/src/OpenAPI/Builder/RequestBuilder.php +++ b/src/OpenAPI/Builder/RequestBuilder.php @@ -5,10 +5,8 @@ namespace Membrane\OpenAPI\Builder; use Membrane\Builder\{Builder, Specification}; -use Membrane\OpenAPI\Builder\Internal; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; -use Membrane\OpenAPI\Specification\OpenAPIRequest; use Membrane\OpenAPI\Specification\Request; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; @@ -40,13 +38,11 @@ public function build(Specification $specification): Processor continue; } - $newSpecification = new OpenAPIRequest( + return $this->getOpenAPIRequestBuilder()->build( $pathMatcher, $pathItem, - $specification->method + $specification->method, ); - - return $this->getOpenAPIRequestBuilder()->build($newSpecification); } throw CannotProcessSpecification::pathNotFound( diff --git a/src/OpenAPI/Specification/OpenAPIRequest.php b/src/OpenAPI/Specification/OpenAPIRequest.php deleted file mode 100644 index 2849657d..00000000 --- a/src/OpenAPI/Specification/OpenAPIRequest.php +++ /dev/null @@ -1,65 +0,0 @@ -getOperation($this->path, $this->method); - - $this->parameters = $operation->parameters; - - assert(isset($operation->operationId)); - $this->operationId = $operation->operationId; - - $this->requestBodySchema = isset($operation->requestBody) ? - $this->getRequestBodySchema($operation->requestBody->content) : - null; - } - - /** @param array $content */ - private function getRequestBodySchema(array $content): V30\Schema | V31\Schema | null - { - if ($content === []) { - return null; - } - - foreach ($content as $contentType => $mediaType) { - if ( - ContentType::fromContentTypeHeader($contentType) !== ContentType::Unmatched - && isset($mediaType->schema) - ) { - return $mediaType->schema; - } - } - - throw CannotProcessOpenAPI::unsupportedMediaTypes(...array_keys($content)); - } - - private function getOperation( - V30\PathItem | V31\PathItem $pathItem, - Method $method - ): V30\Operation | V31\Operation { - return $pathItem->getOperations()[$method->value] - ?? throw CannotProcessSpecification::methodNotFound($method->value); - } -} diff --git a/tests/Console/Command/CacheOpenAPIProcessorsTest.php b/tests/Console/Command/CacheOpenAPIProcessorsTest.php index 293acd3c..2258f380 100644 --- a/tests/Console/Command/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Command/CacheOpenAPIProcessorsTest.php @@ -11,12 +11,12 @@ use Membrane\Filter\String\Explode; use Membrane\Filter\String\ToPascalCase; use Membrane\Filter\Type as TypeFilter; +use Membrane\OpenAPI; use Membrane\OpenAPI\Builder as Builder; use Membrane\OpenAPI\Builder\Internal\Request; use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor; use Membrane\OpenAPI\Filter\PathMatcher; -use Membrane\OpenAPI; use Membrane\OpenAPI\Specification as Specification; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; @@ -50,7 +50,6 @@ #[UsesClass(Membrane\OpenAPI\Filter\QueryStringToArray::class)] #[UsesClass(Membrane\OpenAPI\Filter\FormatStyle\Form::class)] #[UsesClass(OpenAPI\Processor\Request::class)] -#[UsesClass(Specification\OpenAPIRequest::class)] #[UsesClass(Specification\OpenAPIResponse::class)] #[UsesClass(Membrane\Result\Result::class)] #[UsesClass(AlphaNumeric::class)] @@ -124,7 +123,7 @@ public static function provideCasesToCache(): array $responseBuilder = new Builder\OpenAPIResponseBuilder(); $petstoreExpandedFilePath = __DIR__ . '/../../fixtures/OpenAPI/docs/petstore-expanded.json'; - $petstoreExpandedOpenApi = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $petstoreExpandedOpenApi = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath($petstoreExpandedFilePath); return [ @@ -134,11 +133,9 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreA', 'Request\\FindPets', $requestBuilder->build( - new Specification\OpenAPIRequest( new PathParameterExtractor('/pets'), $petstoreExpandedOpenApi->paths['/pets'], Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method::GET - ) ), ], 'findPets : 200 Response' => [ @@ -173,11 +170,9 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreD', 'Request\\AddPet', $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/pets'), - $petstoreExpandedOpenApi->paths['/pets'], - Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method::POST - ) + new PathParameterExtractor('/pets'), + $petstoreExpandedOpenApi->paths['/pets'], + Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method::POST, ), ], 'addPet : 200 Response' => [ @@ -212,11 +207,9 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreG', 'Request\\FindPetById', $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/pets/{id}'), - $petstoreExpandedOpenApi->paths['/pets/{id}'], - Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method::GET - ) + new PathParameterExtractor('/pets/{id}'), + $petstoreExpandedOpenApi->paths['/pets/{id}'], + Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method::GET, ), ], 'find pet by id : 200 Response' => [ @@ -251,11 +244,9 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreJ', 'Request\\DeletePet', $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/pets/{id}'), - $petstoreExpandedOpenApi->paths['/pets/{id}'], - Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method::DELETE - ) + new PathParameterExtractor('/pets/{id}'), + $petstoreExpandedOpenApi->paths['/pets/{id}'], + Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method::DELETE ), ], 'deletePet : 204 Response' => [ @@ -302,23 +293,19 @@ public function cachesProcessorsWithSuitableNamesToAvoidDuplicates(): void { $hatstoreFilePath = __DIR__ . '/../../fixtures/OpenAPI/hatstore.json'; - $hatstoreAPI = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $hatstoreAPI = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath($hatstoreFilePath); $requestBuilder = new Builder\Internal\Request(); $expectedFindHats = $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/hats'), - $hatstoreAPI->paths['/hats'], - Method::GET - ) + new PathParameterExtractor('/hats'), + $hatstoreAPI->paths['/hats'], + Method::GET, ); $expectedFindHats1 = $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/hats/{id}'), - $hatstoreAPI->paths['/hats/{id}'], - Method::GET - ) + new PathParameterExtractor('/hats/{id}'), + $hatstoreAPI->paths['/hats/{id}'], + Method::GET, ); $this->sut->execute( diff --git a/tests/Console/Service/CacheOpenAPIProcessorsTest.php b/tests/Console/Service/CacheOpenAPIProcessorsTest.php index 8352feab..1374d870 100644 --- a/tests/Console/Service/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Service/CacheOpenAPIProcessorsTest.php @@ -8,12 +8,12 @@ use Membrane\Console\Service\CacheOpenAPIProcessors; use Membrane\Console\Template; use Membrane\Filter\{String\AlphaNumeric, String\Explode, String\ToPascalCase, Type as TypeFilter}; +use Membrane\OpenAPI; use Membrane\OpenAPI\Builder as Builder; use Membrane\OpenAPI\Builder\Internal\Request; use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor; use Membrane\OpenAPI\Filter\PathMatcher; -use Membrane\OpenAPI; use Membrane\OpenAPI\Specification; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; @@ -45,7 +45,6 @@ #[UsesClass(OpenAPI\Filter\QueryStringToArray::class)] #[UsesClass(OpenAPI\Filter\FormatStyle\Form::class)] #[UsesClass(OpenAPI\Processor\Request::class)] -#[UsesClass(Specification\OpenAPIRequest::class)] #[UsesClass(Specification\OpenAPIResponse::class)] #[UsesClass(Membrane\Result\Result::class)] #[UsesClass(IntString::class)] @@ -114,23 +113,19 @@ public function failsOnUnreadableOpenAPI(string $openAPI, string $destination): public function cachesProcessorsWithSuitableNamesToAvoidDuplicates(): void { $hatstoreFilePath = __DIR__ . '/../../fixtures/OpenAPI/hatstore.json'; - $hatstoreApi = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $hatstoreApi = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath($hatstoreFilePath); $requestBuilder = new Builder\Internal\Request(); $expectedFindHats = $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/hats'), - $hatstoreApi->paths['/hats'], - Method::GET - ) + new PathParameterExtractor('/hats'), + $hatstoreApi->paths['/hats'], + Method::GET, ); $expectedFindHats1 = $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/hats/{id}'), - $hatstoreApi->paths['/hats/{id}'], - Method::GET - ) + new PathParameterExtractor('/hats/{id}'), + $hatstoreApi->paths['/hats/{id}'], + Method::GET, ); $this->sut->cache($hatstoreFilePath, $this->root->url() . '/cache/', 'ServiceTest\\Hatstore'); @@ -210,7 +205,7 @@ public static function provideCasesOfCachedRequestsFromPetstoreExpanded(): array { $requestBuilder = new Request(); $petstoreExpandedFilePath = __DIR__ . '/../../fixtures/OpenAPI/docs/petstore-expanded.json'; - $petstoreExpandedOpenApi = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $petstoreExpandedOpenApi = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath($petstoreExpandedFilePath); return [ @@ -220,11 +215,9 @@ public static function provideCasesOfCachedRequestsFromPetstoreExpanded(): array 'ServiceTest\\PetstoreA', 'Request\\FindPets', $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/pets'), - $petstoreExpandedOpenApi->paths['/pets'], - Method::GET - ) + new PathParameterExtractor('/pets'), + $petstoreExpandedOpenApi->paths['/pets'], + Method::GET, ), ], 'addPet : Request' => [ @@ -233,11 +226,9 @@ public static function provideCasesOfCachedRequestsFromPetstoreExpanded(): array 'ServiceTest\\PetstoreD', 'Request\\AddPet', $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/pets'), - $petstoreExpandedOpenApi->paths['/pets'], - Method::POST - ) + new PathParameterExtractor('/pets'), + $petstoreExpandedOpenApi->paths['/pets'], + Method::POST, ), ], 'find pet by id : Request' => [ @@ -246,11 +237,9 @@ public static function provideCasesOfCachedRequestsFromPetstoreExpanded(): array 'ServiceTest\\PetstoreE', 'Request\\FindPetById', $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/pets/{id}'), - $petstoreExpandedOpenApi->paths['/pets/{id}'], - Method::GET - ) + new PathParameterExtractor('/pets/{id}'), + $petstoreExpandedOpenApi->paths['/pets/{id}'], + Method::GET, ), ], 'deletePet : Request' => [ @@ -259,11 +248,9 @@ public static function provideCasesOfCachedRequestsFromPetstoreExpanded(): array 'ServiceTest\\PetstoreH', 'Request\\DeletePet', $requestBuilder->build( - new Specification\OpenAPIRequest( - new PathParameterExtractor('/pets/{id}'), - $petstoreExpandedOpenApi->paths['/pets/{id}'], - Method::DELETE - ) + new PathParameterExtractor('/pets/{id}'), + $petstoreExpandedOpenApi->paths['/pets/{id}'], + Method::DELETE, ), ], ]; @@ -273,7 +260,7 @@ public static function provideCasesOfCachedResponsesFromPetstoreExpanded(): arra { $responseBuilder = new Builder\OpenAPIResponseBuilder(); $petstoreExpandedFilePath = __DIR__ . '/../../fixtures/OpenAPI/docs/petstore-expanded.json'; - $petstoreExpandedOpenApi = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $petstoreExpandedOpenApi = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath($petstoreExpandedFilePath); return [ diff --git a/tests/MembraneTest.php b/tests/MembraneTest.php index 092674a5..599f9f5a 100644 --- a/tests/MembraneTest.php +++ b/tests/MembraneTest.php @@ -41,7 +41,6 @@ #[UsesClass(Builder\OpenAPIResponseBuilder::class)] #[UsesClass(Builder\RequestBuilder::class)] #[UsesClass(Builder\ResponseBuilder::class)] -#[UsesClass(OpenAPISpecification\OpenAPIRequest::class)] #[UsesClass(OpenAPISpecification\OpenAPIResponse::class)] #[UsesClass(Processor\BeforeSet::class)] #[UsesClass(Processor\Collection::class)] diff --git a/tests/OpenAPI/Builder/Internal/RequestTest.php b/tests/OpenAPI/Builder/Internal/RequestTest.php index 403b1a7d..68d744d1 100644 --- a/tests/OpenAPI/Builder/Internal/RequestTest.php +++ b/tests/OpenAPI/Builder/Internal/RequestTest.php @@ -6,7 +6,6 @@ use Generator; use GuzzleHttp\Psr7\ServerRequest; -use Membrane\Builder\Specification; use Membrane\Filter\Shape\KeyValueSplit; use Membrane\Filter\String\Explode; use Membrane\Filter\String\Implode; @@ -21,6 +20,7 @@ use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Exception\CannotProcessSpecification; +use Membrane\OpenAPI\ExtractPathParameters\ExtractsPathParameters; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher as PathMatcherClass; use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor; use Membrane\OpenAPI\Filter\FormatStyle\DeepObject; @@ -31,15 +31,14 @@ use Membrane\OpenAPI\Filter\PathMatcher; use Membrane\OpenAPI\Filter\QueryStringToArray; use Membrane\OpenAPI\Processor\Request as RequestProcessor; -use Membrane\OpenAPI\Specification\OpenAPIRequest; use Membrane\OpenAPI\Specification\Request; use Membrane\OpenAPIReader\FileFormat; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; use Membrane\OpenAPIReader\ValueObject\Partial; +use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method; use Membrane\OpenAPIReader\ValueObject\Valid\Identifier; -use Membrane\OpenAPIReader\ValueObject\Valid\V30; use Membrane\Processor; use Membrane\Processor\AllOf; use Membrane\Processor\AnyOf; @@ -81,7 +80,6 @@ #[UsesClass(HumanReadable::class)] // to render test failure messages #[UsesClass(RequestBuilder::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] -#[UsesClass(OpenAPIRequest::class)] #[UsesClass(Internal\Request::class)] #[UsesClass(Internal\Arrays::class)] #[UsesClass(Internal\Numeric::class)] @@ -147,11 +145,11 @@ public function throwsExceptionForUnsupportedContentTypes(): void self::expectExceptionObject(CannotProcessOpenAPI::unsupportedMediaTypes($mediaType)); - new Internal\Request()->build(new OpenAPIRequest( + new Internal\Request()->build( new PathParameterExtractor($path), $pathItem, $method, - )); + ); } #[Test, TestDox('It currently only supports application/json content')] @@ -160,31 +158,33 @@ public function throwsExceptionIfParameterHasContentThatIsNotJson(): void $openAPI = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath((self::FIXTURES . 'noReferences.json')); - $specification = new OpenAPIRequest( - new PathParameterExtractor('/requestpathexceptions'), - $openAPI->paths['/requestpathexceptions'], - Method::POST - ); - $sut = new Internal\Request(); $mediaTypes = array_keys($openAPI->paths['/requestpathexceptions']->post->parameters[0]->content); self::expectExceptionObject(CannotProcessOpenAPI::unsupportedMediaTypes(...$mediaTypes)); - $sut->build($specification); + $sut->build( + new PathParameterExtractor('/requestpathexceptions'), + $openAPI->paths['/requestpathexceptions'], + Method::POST, + ); } #[Test] #[TestDox('Builds a Processor for the Operation Object (specified by the PathItem and method provided')] #[DataProvider('dataSetsForBuild')] - public function buildTest(Specification $spec, Processor $expected): void - { - $sut = new Internal\Request(); - - $actual = $sut->build($spec); - - self::assertProcessorEquals($expected, $actual); + public function buildTest( + Processor $expected, + ExtractsPathParameters $extractsPathParameters, + V30\PathItem | V31\PathItem $pathItem, + Method $method, + ): void { + self::assertProcessorEquals($expected, new Internal\Request()->build( + $extractsPathParameters, + $pathItem, + $method, + )); } #[Test] @@ -193,13 +193,19 @@ public function buildTest(Specification $spec, Processor $expected): void #[DataProvider('provideAPIWithQueryParameters')] #[DataProvider('provideAPIWithHeaderParameters')] public function itBuildsProcessorsThatValidateRequests( - OpenAPIRequest $specification, + Result $expected, + ExtractsPathParameters $extractsPathParameters, + V30\PathItem | V31\PathItem $pathItem, + Method $method, array | ServerRequestInterface $serverRequest, - Result $expected ): void { $sut = new Internal\Request(); - $processor = $sut->build($specification); + $processor = $sut->build( + $extractsPathParameters, + $pathItem, + $method, + ); $actual = $processor->process(new FieldName(''), $serverRequest); @@ -214,11 +220,6 @@ public static function dataSetsForBuild(): Generator ->readFromAbsoluteFilePath(__DIR__ . '/../../../fixtures/OpenAPI/noReferences.json'); yield 'Request: no path params, no operation params, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/path'), - $noRefAPI->paths['/path'], - Method::GET - ), new RequestProcessor( '', 'path-get', @@ -236,13 +237,11 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/path'), + $noRefAPI->paths['/path'], + Method::GET, ]; yield 'Patch Request: no path params, no operation params, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/path'), - $noRefAPI->paths['/path'], - Method::PATCH - ), new RequestProcessor( '', 'path-patch', @@ -260,13 +259,11 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/path'), + $noRefAPI->paths['/path'], + Method::PATCH, ]; yield 'Request: path param in path, no operation params, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestpathone/{id}'), - $noRefAPI->paths['/requestpathone/{id}'], - Method::GET - ), new RequestProcessor( '', 'requestpathone-get', @@ -286,13 +283,11 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/requestpathone/{id}'), + $noRefAPI->paths['/requestpathone/{id}'], + Method::GET, ]; yield 'Request: path param in path, operation param in query not required, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestpathone/{id}'), - $noRefAPI->paths['/requestpathone/{id}'], - Method::POST - ), new RequestProcessor( '', 'requestpathone-post', @@ -316,13 +311,11 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/requestpathone/{id}'), + $noRefAPI->paths['/requestpathone/{id}'], + Method::POST, ]; yield 'Request: path param in path, operation param in query required, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestpathone/{id}'), - $noRefAPI->paths['/requestpathone/{id}'], - Method::PUT, - ), new RequestProcessor( '', 'requestpathone-put', @@ -351,13 +344,11 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/requestpathone/{id}'), + $noRefAPI->paths['/requestpathone/{id}'], + Method::PUT, ]; yield 'Request: path param in path, operation param in query with json content, required, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestpathone/{id}'), - $noRefAPI->paths['/requestpathone/{id}'], - Method::DELETE - ), new RequestProcessor( '', 'requestpathone-delete', @@ -386,13 +377,11 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/requestpathone/{id}'), + $noRefAPI->paths['/requestpathone/{id}'], + Method::DELETE, ]; yield 'Request: path param in header, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestpathtwo'), - $noRefAPI->paths['/requestpathtwo'], - Method::GET - ), new RequestProcessor( '', 'requestpathtwo-get', @@ -410,13 +399,11 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/requestpathtwo'), + $noRefAPI->paths['/requestpathtwo'], + Method::GET, ]; yield 'Request: path param in header, operation param in cookie, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestpathtwo'), - $noRefAPI->paths['/requestpathtwo'], - Method::POST - ), new RequestProcessor( '', 'requestpathtwo-post', @@ -434,14 +421,12 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/requestpathtwo'), + $noRefAPI->paths['/requestpathtwo'], + Method::POST, ]; yield 'Request: identical param in header and query, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestpathtwo'), - $noRefAPI->paths['/requestpathtwo'], - Method::PUT - ), new RequestProcessor( '', 'requestpathtwo-put', @@ -463,14 +448,12 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/requestpathtwo'), + $noRefAPI->paths['/requestpathtwo'], + Method::PUT, ]; yield 'Request: same param in path and operation with different types, no requestBody' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestpathtwo'), - $noRefAPI->paths['/requestpathtwo'], - Method::DELETE - ), new RequestProcessor( '', 'requestpathtwo-delete', @@ -488,14 +471,12 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new Passes()), ] ), + new PathParameterExtractor('/requestpathtwo'), + $noRefAPI->paths['/requestpathtwo'], + Method::DELETE, ]; yield 'Request: requestBody param' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestbodypath'), - $noRefAPI->paths['/requestbodypath'], - Method::GET - ), new RequestProcessor( '', 'requestbodypath-get', @@ -513,14 +494,12 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new IsInt()), ] ), + new PathParameterExtractor('/requestbodypath'), + $noRefAPI->paths['/requestbodypath'], + Method::GET, ]; yield 'Request: operation param in query, requestBody param' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestbodypath'), - $noRefAPI->paths['/requestbodypath'], - Method::POST - ), new RequestProcessor( '', 'requestbodypath-post', @@ -542,14 +521,12 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new IsInt()), ] ), + new PathParameterExtractor('/requestbodypath'), + $noRefAPI->paths['/requestbodypath'], + Method::POST, ]; yield 'Request: path param in path, operation param in query, header, cookie, requestBody param' => [ - new OpenAPIRequest( - new PathParameterExtractor('/requestbodypath/{id}'), - $noRefAPI->paths['/requestbodypath/{id}'], - Method::GET - ), new RequestProcessor( '', 'requestbodypath-id-get', @@ -573,6 +550,9 @@ public static function dataSetsForBuild(): Generator 'body' => new Field('requestBody', new IsFloat()), ] ), + new PathParameterExtractor('/requestbodypath/{id}'), + $noRefAPI->paths['/requestbodypath/{id}'], + Method::GET, ]; $complexQueryAPI = fn(string $xOf) => $reader->readFromString( @@ -616,11 +596,6 @@ public static function dataSetsForBuild(): Generator ); yield 'query parameter with oneOf' => [ - new OpenAPIRequest( - new PathParameterExtractor('/path'), - $complexQueryAPI('oneOf')->paths['/path'], - Method::GET - ), $complexProcessor( new OneOf( 'complexity', @@ -629,14 +604,12 @@ public static function dataSetsForBuild(): Generator ), ['complexity' => ['style' => 'form', 'explode' => true]] ), + new PathParameterExtractor('/path'), + $complexQueryAPI('oneOf')->paths['/path'], + Method::GET, ]; yield 'query parameter with anyOf' => [ - new OpenAPIRequest( - new PathParameterExtractor('/path'), - $complexQueryAPI('anyOf')->paths['/path'], - Method::GET - ), $complexProcessor( new AnyOf( 'complexity', @@ -645,14 +618,12 @@ public static function dataSetsForBuild(): Generator ), ['complexity' => ['style' => 'form', 'explode' => true]] ), + new PathParameterExtractor('/path'), + $complexQueryAPI('anyOf')->paths['/path'], + Method::GET ]; yield 'query parameter with allOf' => [ - new OpenAPIRequest( - new PathParameterExtractor('/path'), - $complexQueryAPI('allOf')->paths['/path'], - Method::GET - ), $complexProcessor( new AllOf( 'complexity', @@ -661,25 +632,22 @@ public static function dataSetsForBuild(): Generator ), ['complexity' => ['style' => 'form', 'explode' => true]] ), + new PathParameterExtractor('/path'), + $complexQueryAPI('allOf')->paths['/path'], + Method::GET, ]; } public static function dataSetsForDocExamples(): array { - $petstoreApi = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $petstoreApi = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath(self::FIXTURES . '/docs/petstore.yaml'); - $petstoreExpandedApi = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $petstoreExpandedApi = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath(self::FIXTURES . '/docs/petstore-expanded.json'); return [ 'petstore /pets get, minimal (valid)' => [ - new OpenAPIRequest( - new PathParameterExtractor('/pets'), - $petstoreApi->paths['/pets'], - Method::GET - ), - new ServerRequest('get', 'http://petstore.swagger.io/v1/pets'), Result::valid( [ 'request' => ['method' => 'get', 'operationId' => 'listPets'], @@ -690,14 +658,12 @@ public static function dataSetsForDocExamples(): array 'body' => '', ], ), + new PathParameterExtractor('/pets'), + $petstoreApi->paths['/pets'], + Method::GET, + new ServerRequest('get', 'http://petstore.swagger.io/v1/pets'), ], 'petstore /pets/{petid} get, minimal (valid)' => [ - new OpenAPIRequest( - new PathParameterExtractor('/pets/{petId}'), - $petstoreApi->paths['/pets/{petId}'], - Method::GET - ), - new ServerRequest('get', 'http://petstore.swagger.io/v1/pets/Harley'), Result::valid( [ 'request' => ['method' => 'get', 'operationId' => 'showPetById'], @@ -708,14 +674,12 @@ public static function dataSetsForDocExamples(): array 'body' => '', ], ), + new PathParameterExtractor('/pets/{petId}'), + $petstoreApi->paths['/pets/{petId}'], + Method::GET, + new ServerRequest('get', 'http://petstore.swagger.io/v1/pets/Harley'), ], 'petstore expanded /pets get (invalid)' => [ - new OpenAPIRequest( - new PathParameterExtractor('/pets'), - $petstoreExpandedApi->paths['/pets'], - Method::GET - ), - new ServerRequest('get', 'http://petstore.swagger.io/api/pets?limit=five'), Result::invalid( [ 'request' => ['method' => 'get', 'operationId' => 'findPets'], @@ -730,14 +694,12 @@ public static function dataSetsForDocExamples(): array new Message('String value must be an integer.', []) ) ), + new PathParameterExtractor('/pets'), + $petstoreExpandedApi->paths['/pets'], + Method::GET, + new ServerRequest('get', 'http://petstore.swagger.io/api/pets?limit=five'), ], 'petstore expanded /pets get, minimal (valid)' => [ - new OpenAPIRequest( - new PathParameterExtractor('/pets'), - $petstoreExpandedApi->paths['/pets'], - Method::GET - ), - new ServerRequest('get', 'http://petstore.swagger.io/api/pets?limit=5&tags=cat,tabby'), Result::valid( [ 'request' => ['method' => 'get', 'operationId' => 'findPets'], @@ -748,14 +710,21 @@ public static function dataSetsForDocExamples(): array 'body' => '', ] ), + new PathParameterExtractor('/pets'), + $petstoreExpandedApi->paths['/pets'], + Method::GET, + new ServerRequest('get', 'http://petstore.swagger.io/api/pets?limit=5&tags=cat,tabby'), ], ]; } - /** @return Generator */ public static function provideAPIWithPathParameters(): Generator @@ -766,16 +735,6 @@ public static function provideAPIWithPathParameters(): Generator string $input, array $output, ) => [ - new OpenAPIRequest( - new PathParameterExtractor($path), - V30\PathItem::fromPartial( - new Identifier('test-path'), - [], - new Partial\PathItem(path: '/path', get: $operation), - ), - Method::GET - ), - new ServerRequest('get', $input), Result::valid([ 'request' => ['method' => 'get', 'operationId' => 'test-op'], 'path' => $output, @@ -784,6 +743,14 @@ public static function provideAPIWithPathParameters(): Generator 'cookie' => [], 'body' => '', ]), + new PathParameterExtractor($path), + V30\PathItem::fromPartial( + new Identifier('test-path'), + [], + new Partial\PathItem(path: '/path', get: $operation), + ), + Method::GET, + new ServerRequest('get', $input), ]; yield 'bool path parameter (style:simple, explode:false)' => $dataSet( @@ -1467,10 +1434,13 @@ public static function provideAPIWithPathParameters(): Generator ); } - /** @return Generator */ public static function provideAPIWithQueryParameters(): Generator @@ -1480,16 +1450,6 @@ public static function provideAPIWithQueryParameters(): Generator string $input, array $output, ) => [ - new OpenAPIRequest( - new PathParameterExtractor('/pets'), - V30\PathItem::fromPartial( - new Identifier('test-path'), - [], - new Partial\PathItem(path: '/path', get: $operation), - ), - Method::GET - ), - new ServerRequest('get', '/pets?' . $input), Result::valid([ 'request' => ['method' => 'get', 'operationId' => 'test-op'], 'path' => [], @@ -1498,6 +1458,14 @@ public static function provideAPIWithQueryParameters(): Generator 'cookie' => [], 'body' => '', ]), + new PathParameterExtractor('/pets'), + V30\PathItem::fromPartial( + new Identifier('test-path'), + [], + new Partial\PathItem(path: '/path', get: $operation), + ), + Method::GET, + new ServerRequest('get', '/pets?' . $input), ]; yield 'type:string, style:form, explode:false' => $dataSet( @@ -1744,10 +1712,13 @@ public static function provideAPIWithQueryParameters(): Generator ); } - /** @return Generator */ public static function provideAPIWithHeaderParameters(): Generator @@ -1757,16 +1728,6 @@ public static function provideAPIWithHeaderParameters(): Generator array $requestHeaders, array $resultHeaders ) => [ - new OpenAPIRequest( - new PathParameterExtractor('/path'), - V30\PathItem::fromPartial( - new Identifier('test-path'), - [], - new Partial\PathItem(path: '/path', get: $operation) - ), - Method::GET - ), - new ServerRequest('get', '/path', $requestHeaders), Result::valid([ 'request' => ['method' => 'get', 'operationId' => 'test-op'], 'path' => [], @@ -1775,6 +1736,14 @@ public static function provideAPIWithHeaderParameters(): Generator 'cookie' => [], 'body' => '', ]), + new PathParameterExtractor('/path'), + V30\PathItem::fromPartial( + new Identifier('test-path'), + [], + new Partial\PathItem(path: '/path', get: $operation) + ), + Method::GET, + new ServerRequest('get', '/path', $requestHeaders), ]; yield 'string header (explode:false)' => $dataSet( diff --git a/tests/OpenAPI/Builder/RequestBuilderTest.php b/tests/OpenAPI/Builder/RequestBuilderTest.php index eb550efc..1dd9369b 100644 --- a/tests/OpenAPI/Builder/RequestBuilderTest.php +++ b/tests/OpenAPI/Builder/RequestBuilderTest.php @@ -19,7 +19,6 @@ use Membrane\OpenAPI\Filter\PathMatcher; use Membrane\OpenAPI\Filter\QueryStringToArray; use Membrane\OpenAPI\Processor\Request as RequestProcessor; -use Membrane\OpenAPI\Specification\OpenAPIRequest; use Membrane\OpenAPI\Specification\Request; use Membrane\OpenAPIReader\OpenAPIVersion; use Membrane\OpenAPIReader\Reader; @@ -59,7 +58,6 @@ #[UsesClass(Internal\Arrays::class)] #[UsesClass(Internal\Numeric::class)] #[UsesClass(Internal\Strings::class)] -#[UsesClass(OpenAPIRequest::class)] #[UsesClass(Request::class)] #[UsesClass(QueryStringToArray::class)] #[UsesClass(Form::class)] diff --git a/tests/OpenAPI/Specification/OpenAPIRequestTest.php b/tests/OpenAPI/Specification/OpenAPIRequestTest.php deleted file mode 100644 index c32973ba..00000000 --- a/tests/OpenAPI/Specification/OpenAPIRequestTest.php +++ /dev/null @@ -1,99 +0,0 @@ -openApi = new MembraneReader([OpenAPIVersion::Version_3_0]) - ->readFromAbsoluteFilePath(__DIR__ . '/../../fixtures/OpenAPI/docs/petstore-expanded.json'); - $this->pathParameterExtractor = new PathParameterExtractor('/pets'); - } - - #[Test, TestDox('If the method given is not specified on the PathItem then an exception will be thrown')] - public function throwsExceptionIfMethodNotFound(): void - { - self::expectExceptionObject(CannotProcessSpecification::methodNotFound(Method::DELETE->value)); - - new OpenAPIRequest($this->pathParameterExtractor, $this->openApi->paths['/pets'], Method::DELETE); - } - - #[Test, TestDox('Throws an exception if the request body contains content that is not supported')] - public function throwsExceptionIfRequestBodyContentContainsUnsupportedMediaTypes(): void - { - $pathItem = new MembraneReader([OpenAPIVersion::Version_3_0]) - ->readFromAbsoluteFilePath(__DIR__ . '/../../fixtures/OpenAPI/noReferences.json') - ->paths['/path']; - - $pathParameterExtractor = new PathParameterExtractor('/path'); - - self::expectExceptionObject( - CannotProcessOpenAPI::unsupportedMediaTypes(...array_keys($pathItem->put->requestBody->content)) - ); - - new OpenAPIRequest($pathParameterExtractor, $pathItem, Method::PUT); - } - - #[Test, TestDox('$parameters will contain an array of parameters with their names as keys')] - public function parameterswillContainRelevantParameters(): void - { - $expected = $this->openApi->paths['/pets']->get->parameters; - - $sut = new OpenAPIRequest($this->pathParameterExtractor, $this->openApi->paths['/pets'], Method::GET); - self::assertEquals($expected, $sut->parameters); - } - - #[Test, TestDox('$requestBodySchema will be null if request body has no content')] - public function requestBodySchemaIsNullIfRequestBodyHasNoContent(): void - { - $sut = new OpenAPIRequest($this->pathParameterExtractor, $this->openApi->paths['/pets'], Method::GET); - - self::assertNull($sut->requestBodySchema); - } - - #[Test, TestDox('$requestBodySchema will contain the request body schema if it exists')] - public function requestBodySchemaWillContainRelevantRequestBodyContent(): void - { - $expected = $this->openApi->paths['/pets']->post->requestBody->content['application/json']->schema; - $sut = new OpenAPIRequest($this->pathParameterExtractor, $this->openApi->paths['/pets'], Method::POST); - - self::assertEquals($expected, $sut->requestBodySchema); - } - - #[Test, TestDox('$operationId contains operationId for matching Operation Object')] - public function operationIdContainsRelevantOperationId(): void - { - $sut = new OpenAPIRequest($this->pathParameterExtractor, $this->openApi->paths['/pets'], Method::GET); - - self::assertEquals('findPets', $sut->operationId); - } -} From 879665c272c0b8f9db7bd6a8a33808eb4fc5e776 Mon Sep 17 00:00:00 2001 From: John Charman Date: Wed, 23 Sep 2026 12:27:07 +0100 Subject: [PATCH 42/47] Rename OpenAPIResponseBuilder to Internal\Response --- .../Service/YieldsClassDefinitions.php | 8 +++---- .../Response.php} | 5 ++--- src/OpenAPI/Builder/ResponseBuilder.php | 20 +++++------------ .../Command/CacheOpenAPIProcessorsTest.php | 4 ++-- .../Service/CacheOpenAPIProcessorsTest.php | 4 ++-- tests/MembraneTest.php | 2 +- .../Builder/OpenAPIResponseBuilderTest.php | 22 +++++++++---------- tests/OpenAPI/Builder/ResponseBuilderTest.php | 15 ++++++++----- 8 files changed, 38 insertions(+), 42 deletions(-) rename src/OpenAPI/Builder/{OpenAPIResponseBuilder.php => Internal/Response.php} (86%) diff --git a/src/Console/Service/YieldsClassDefinitions.php b/src/Console/Service/YieldsClassDefinitions.php index bd6fd729..b8540a00 100644 --- a/src/Console/Service/YieldsClassDefinitions.php +++ b/src/Console/Service/YieldsClassDefinitions.php @@ -7,7 +7,7 @@ use Membrane\Console\Template; use Membrane\Filter; use Membrane\OpenAPI\Builder\Internal\Request; -use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder; +use Membrane\OpenAPI\Builder\Internal\Response; use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPIReader\MembraneReader; @@ -17,7 +17,7 @@ final class YieldsClassDefinitions { private Request $requestBuilder; - private OpenAPIResponseBuilder $responseBuilder; + private Response $responseBuilder; public function __construct( private readonly \Psr\Log\LoggerInterface $logger, @@ -147,10 +147,10 @@ private function getRequestBuilder(): Request return $this->requestBuilder; } - private function getResponseBuilder(): OpenAPIResponseBuilder + private function getResponseBuilder(): Response { if (!isset($this->responseBuilder)) { - $this->responseBuilder = new OpenAPIResponseBuilder(); + $this->responseBuilder = new Response(); return $this->responseBuilder; } return $this->responseBuilder; diff --git a/src/OpenAPI/Builder/OpenAPIResponseBuilder.php b/src/OpenAPI/Builder/Internal/Response.php similarity index 86% rename from src/OpenAPI/Builder/OpenAPIResponseBuilder.php rename to src/OpenAPI/Builder/Internal/Response.php index d17a9426..7d53b2a2 100644 --- a/src/OpenAPI/Builder/OpenAPIResponseBuilder.php +++ b/src/OpenAPI/Builder/Internal/Response.php @@ -2,16 +2,15 @@ declare(strict_types=1); -namespace Membrane\OpenAPI\Builder; +namespace Membrane\OpenAPI\Builder\Internal; use Membrane\Builder\Specification; -use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\Processor; use Membrane\Processor\Field; use Membrane\Validator\Utility\Passes; -class OpenAPIResponseBuilder extends Schema +class Response extends Schema { public function supports(Specification $specification): bool { diff --git a/src/OpenAPI/Builder/ResponseBuilder.php b/src/OpenAPI/Builder/ResponseBuilder.php index 6f00b907..3a8c552d 100644 --- a/src/OpenAPI/Builder/ResponseBuilder.php +++ b/src/OpenAPI/Builder/ResponseBuilder.php @@ -19,7 +19,9 @@ class ResponseBuilder implements Builder { - private OpenAPIResponseBuilder $responseBuilder; + private Internal\Response $responseBuilder { + get => $this->responseBuilder ??= new Internal\Response(); + } public function supports(Specification $specification): bool { @@ -30,10 +32,10 @@ public function build(Specification $specification): Processor { assert($specification instanceof Response); - $openAPI = (new MembraneReader([ + $openAPI = new MembraneReader([ OpenAPIVersion::Version_3_0, OpenAPIVersion::Version_3_1 - ]))->readFromAbsoluteFilePath($specification->absoluteFilePath); + ])->readFromAbsoluteFilePath($specification->absoluteFilePath); $serverUrl = $this->matchServer($openAPI, $specification->url); foreach ($openAPI->paths as $path => $pathItem) { @@ -52,7 +54,7 @@ public function build(Specification $specification): Processor $response ); - return $this->getOpenAPIResponseBuilder()->build($newSpecification); + return $this->responseBuilder->build($newSpecification); } throw CannotProcessSpecification::pathNotFound( @@ -61,16 +63,6 @@ public function build(Specification $specification): Processor ); } - private function getOpenAPIResponseBuilder(): OpenAPIResponseBuilder - { - if (!isset($this->responseBuilder)) { - $this->responseBuilder = new OpenAPIResponseBuilder(); - } - - return $this->responseBuilder; - } - - private function getOperation( V30\PathItem | V31\PathItem $pathItem, Method $method diff --git a/tests/Console/Command/CacheOpenAPIProcessorsTest.php b/tests/Console/Command/CacheOpenAPIProcessorsTest.php index 2258f380..4d1162b4 100644 --- a/tests/Console/Command/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Command/CacheOpenAPIProcessorsTest.php @@ -43,7 +43,7 @@ #[UsesClass(Builder\Internal\Strings::class)] #[UsesClass(Builder\Internal\Objects::class)] #[UsesClass(Builder\Internal\Request::class)] -#[UsesClass(Builder\OpenAPIResponseBuilder::class)] +#[UsesClass(Builder\Internal\Response::class)] #[UsesClass(PathMatcher::class)] #[UsesClass(PathParameterExtractor::class)] #[UsesClass(Processor\AllOf::class)] @@ -120,7 +120,7 @@ public function failsOnUnreadableOpenAPI(string $openAPI, string $destination, i public static function provideCasesToCache(): array { $requestBuilder = new Request(); - $responseBuilder = new Builder\OpenAPIResponseBuilder(); + $responseBuilder = new Builder\Internal\Response(); $petstoreExpandedFilePath = __DIR__ . '/../../fixtures/OpenAPI/docs/petstore-expanded.json'; $petstoreExpandedOpenApi = new MembraneReader([OpenAPIVersion::Version_3_0]) diff --git a/tests/Console/Service/CacheOpenAPIProcessorsTest.php b/tests/Console/Service/CacheOpenAPIProcessorsTest.php index 1374d870..629e1b82 100644 --- a/tests/Console/Service/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Service/CacheOpenAPIProcessorsTest.php @@ -38,7 +38,7 @@ #[UsesClass(Builder\Internal\Strings::class)] #[UsesClass(Builder\Internal\Objects::class)] #[UsesClass(Builder\Internal\Request::class)] -#[UsesClass(Builder\OpenAPIResponseBuilder::class)] +#[UsesClass(Builder\Internal\Response::class)] #[UsesClass(PathMatcher::class)] #[UsesClass(PathParameterExtractor::class)] #[UsesClass(Processor\AllOf::class)] @@ -258,7 +258,7 @@ public static function provideCasesOfCachedRequestsFromPetstoreExpanded(): array public static function provideCasesOfCachedResponsesFromPetstoreExpanded(): array { - $responseBuilder = new Builder\OpenAPIResponseBuilder(); + $responseBuilder = new Builder\Internal\Response(); $petstoreExpandedFilePath = __DIR__ . '/../../fixtures/OpenAPI/docs/petstore-expanded.json'; $petstoreExpandedOpenApi = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath($petstoreExpandedFilePath); diff --git a/tests/MembraneTest.php b/tests/MembraneTest.php index 599f9f5a..11b0f350 100644 --- a/tests/MembraneTest.php +++ b/tests/MembraneTest.php @@ -38,7 +38,7 @@ #[UsesClass(Builder\Internal\Strings::class)] #[UsesClass(Builder\Internal\Objects::class)] #[UsesClass(Builder\Internal\Request::class)] -#[UsesClass(Builder\OpenAPIResponseBuilder::class)] +#[UsesClass(Builder\Internal\Response::class)] #[UsesClass(Builder\RequestBuilder::class)] #[UsesClass(Builder\ResponseBuilder::class)] #[UsesClass(OpenAPISpecification\OpenAPIResponse::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php index 7559a59d..929fa0d8 100644 --- a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php @@ -7,8 +7,8 @@ use Generator; use Membrane\Builder\Specification; use Membrane\Filter\String\ToUpperCase; +use Membrane\OpenAPI\Builder\Internal; use Membrane\OpenAPI\Builder\Internal\Schema; -use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; @@ -54,7 +54,7 @@ use PHPUnit\Framework\Attributes\UsesClass; use PHPUnit\Framework\TestCase; -#[CoversClass(OpenAPIResponseBuilder::class)] +#[CoversClass(Internal\Response::class)] #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] #[CoversClass(Schema::class)] @@ -106,7 +106,7 @@ public function throwsExceptionIfNotIsFound(): void $operation = $openApi->paths['/responsepath']->get; - $sut = new OpenAPIResponseBuilder(); + $sut = new Internal\Response(); $response = new OpenAPIResponse( $operation->operationId, '360', @@ -122,7 +122,7 @@ public function throwsExceptionIfNotIsFound(): void public function supportsResponseSpecification(): void { $specification = self::createStub(OpenAPIResponse::class); - $sut = new OpenAPIResponseBuilder(); + $sut = new Internal\Response(); self::assertTrue($sut->supports($specification)); } @@ -131,7 +131,7 @@ public function supportsResponseSpecification(): void public function doesNotSupportSpecificationsThatAreNotResponse(): void { $specification = self::createStub(\Membrane\Builder\Specification::class); - $sut = new OpenAPIResponseBuilder(); + $sut = new Internal\Response(); self::assertFalse($sut->supports($specification)); } @@ -943,7 +943,7 @@ public static function dataSetsforBuilds(): array #[DataProvider('dataSetsforBuilds')] public function buildsTest(Specification $spec, Processor $expected): void { - $sut = new OpenAPIResponseBuilder(); + $sut = new Internal\Response(); $processor = $sut->build($spec); @@ -952,7 +952,7 @@ public function buildsTest(Specification $spec, Processor $expected): void public static function dataSetsForDocExamples(): array { - $petstore = (new MembraneReader([OpenAPIVersion::Version_3_0])) + $petstore = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath(self::DIR . 'docs/petstore.yaml'); $petsGet200Response = new OpenAPIResponse( @@ -1018,7 +1018,7 @@ public static function dataSetsForDocExamples(): array #[Test] public function docsTest(Specification $spec, array $data, Result $expected): void { - $sut = new OpenAPIResponseBuilder(); + $sut = new Internal\Response(); $processor = $sut->build($spec); @@ -1029,9 +1029,9 @@ public function docsTest(Specification $spec, array $data, Result $expected): vo #[DataProvider('provideDateStrings')] public function itValidatesDateTime(Result $expected, string $dateTime): void { - $noReferences = (new MembraneReader([ + $noReferences = new MembraneReader([ OpenAPIVersion::Version_3_0, - ]))->readFromAbsoluteFilePath(self::DIR . 'noReferences.json'); + ])->readFromAbsoluteFilePath(self::DIR . 'noReferences.json'); $specification = new OpenAPIResponse( $noReferences->paths['/responsepath']->get->operationId, @@ -1039,7 +1039,7 @@ public function itValidatesDateTime(Result $expected, string $dateTime): void $noReferences->paths['/responsepath']->get->responses['224'] ); - $sut = new OpenAPIResponseBuilder(); + $sut = new Internal\Response(); $processor = $sut->build($specification); diff --git a/tests/OpenAPI/Builder/ResponseBuilderTest.php b/tests/OpenAPI/Builder/ResponseBuilderTest.php index f6cb9ee7..d9e78893 100644 --- a/tests/OpenAPI/Builder/ResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/ResponseBuilderTest.php @@ -6,8 +6,8 @@ use Membrane\Builder\Specification; use Membrane\Filter\String\ToUpperCase; +use Membrane\OpenAPI\Builder\Internal; use Membrane\OpenAPI\Builder\Internal\Schema; -use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder; use Membrane\OpenAPI\Builder\ResponseBuilder; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Exception\CannotProcessResponse; @@ -58,8 +58,8 @@ #[CoversClass(CannotProcessResponse::class)] #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] -#[UsesClass(Schema::class)] -#[UsesClass(OpenAPIResponseBuilder::class)] +#[UsesClass(Internal\Schema::class)] +#[UsesClass(Internal\Response::class)] #[UsesClass(OpenAPIResponse::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\Arrays::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] @@ -107,7 +107,12 @@ public function setUp(): void #[Test, TestDox('It throws an exception if you try to use the keyword "not"')] public function throwsExceptionIfNotIsFound(): void { - $response = new Response(self::DIR . 'noReferences.json', '/responsepath', Method::GET, '360'); + $response = new Response( + self::DIR . 'noReferences.json', + '/responsepath', + Method::GET, + '360', + ); self::expectExceptionObject(CannotProcessOpenAPI::unsupportedKeyword('not')); @@ -169,7 +174,7 @@ public function throwsExceptionIfPathCannotBeFound(): void $specification = new Response(self::DIR . 'noReferences.json', '/nonexistentpath', Method::GET, '200'); - (new ResponseBuilder())->build($specification); + new ResponseBuilder()->build($specification); } public static function dataSetsforBuilds(): array From 8c28cd78d5371de3201b8a341ee361becf4e6acb Mon Sep 17 00:00:00 2001 From: John Charman Date: Wed, 23 Sep 2026 13:04:53 +0100 Subject: [PATCH 43/47] Remove OpenAPIResponse Specification --- .../Service/YieldsClassDefinitions.php | 13 +- src/OpenAPI/Builder/Internal/Response.php | 30 +- src/OpenAPI/Builder/ResponseBuilder.php | 11 +- src/OpenAPI/Specification/OpenAPIResponse.php | 35 -- .../Command/CacheOpenAPIProcessorsTest.php | 41 +- .../Service/CacheOpenAPIProcessorsTest.php | 43 +- tests/MembraneTest.php | 2 - .../Builder/OpenAPIResponseBuilderTest.php | 486 ++++-------------- tests/OpenAPI/Builder/ResponseBuilderTest.php | 2 - .../Specification/OpenAPIResponseTest.php | 74 --- 10 files changed, 125 insertions(+), 612 deletions(-) delete mode 100644 src/OpenAPI/Specification/OpenAPIResponse.php delete mode 100644 tests/OpenAPI/Specification/OpenAPIResponseTest.php diff --git a/src/Console/Service/YieldsClassDefinitions.php b/src/Console/Service/YieldsClassDefinitions.php index b8540a00..11d626f7 100644 --- a/src/Console/Service/YieldsClassDefinitions.php +++ b/src/Console/Service/YieldsClassDefinitions.php @@ -9,7 +9,6 @@ use Membrane\OpenAPI\Builder\Internal\Request; use Membrane\OpenAPI\Builder\Internal\Response; use Membrane\OpenAPI\ExtractPathParameters\PathParameterExtractor; -use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; use Membrane\OpenAPIReader\ValueObject\Valid\{Enum\Method, V30, V31}; @@ -74,13 +73,7 @@ public function __invoke( yield new Template\Processor( namespace: "$cacheNamespace\\Response\\$prefixedCode", name: $className, - processor: $this->getResponseBuilder()->build( - new OpenAPIResponse( - $operation->operationId, - (string)$code, - $response, - ) - ) + processor: $this->getResponseBuilder()->build($response), ); } } @@ -107,10 +100,10 @@ public function __invoke( private function readOpenAPIFile(string $filepath): V30\OpenAPI | V31\OpenAPI { $this->logger->info("Reading OpenAPI from $filepath"); - return (new MembraneReader([ + return new MembraneReader([ OpenAPIVersion::Version_3_0, OpenAPIVersion::Version_3_1 - ]))->readFromAbsoluteFilePath($filepath); + ])->readFromAbsoluteFilePath($filepath); } /** @param array $existingClassNames */ diff --git a/src/OpenAPI/Builder/Internal/Response.php b/src/OpenAPI/Builder/Internal/Response.php index 7d53b2a2..90371381 100644 --- a/src/OpenAPI/Builder/Internal/Response.php +++ b/src/OpenAPI/Builder/Internal/Response.php @@ -4,32 +4,36 @@ namespace Membrane\OpenAPI\Builder\Internal; -use Membrane\Builder\Specification; -use Membrane\OpenAPI\Specification\OpenAPIResponse; +use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; +use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; use Membrane\Processor; use Membrane\Processor\Field; use Membrane\Validator\Utility\Passes; class Response extends Schema { - public function supports(Specification $specification): bool - { - return ($specification instanceof OpenAPIResponse); + public function build( + V30\Response | V31\Response $response, + ): Processor { + return $this->fromContent($response); } - public function build(Specification $specification): Processor + private function fromContent(V30\Response | V31\Response $response): Processor { - assert($specification instanceof OpenAPIResponse); + if ($response->content === []) { + return new Field('', new Passes()); + } - return $this->fromContent($specification); - } + if (! isset($response->content['application/json'])) { + throw CannotProcessOpenAPI::unsupportedMediaTypes( + ...array_keys($response->content), + ); + } - private function fromContent(OpenAPIResponse $response): Processor - { - if ($response->schema === null) { + if ($response->content['application/json']->schema === null) { return new Field('', new Passes()); } - return $this->fromSchema($response->schema); + return $this->fromSchema($response->content['application/json']->schema); } } diff --git a/src/OpenAPI/Builder/ResponseBuilder.php b/src/OpenAPI/Builder/ResponseBuilder.php index 3a8c552d..31c6ba82 100644 --- a/src/OpenAPI/Builder/ResponseBuilder.php +++ b/src/OpenAPI/Builder/ResponseBuilder.php @@ -9,7 +9,6 @@ use Membrane\OpenAPI\Exception\CannotProcessResponse; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; -use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPI\Specification\Response; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; @@ -34,7 +33,7 @@ public function build(Specification $specification): Processor $openAPI = new MembraneReader([ OpenAPIVersion::Version_3_0, - OpenAPIVersion::Version_3_1 + OpenAPIVersion::Version_3_1, ])->readFromAbsoluteFilePath($specification->absoluteFilePath); $serverUrl = $this->matchServer($openAPI, $specification->url); @@ -48,13 +47,7 @@ public function build(Specification $specification): Processor $response = $this->getResponse($operation, $specification->statusCode); - $newSpecification = new OpenAPIResponse( - $operation->operationId, - $specification->statusCode, - $response - ); - - return $this->responseBuilder->build($newSpecification); + return $this->responseBuilder->build($response); } throw CannotProcessSpecification::pathNotFound( diff --git a/src/OpenAPI/Specification/OpenAPIResponse.php b/src/OpenAPI/Specification/OpenAPIResponse.php deleted file mode 100644 index 7a8c8ef6..00000000 --- a/src/OpenAPI/Specification/OpenAPIResponse.php +++ /dev/null @@ -1,35 +0,0 @@ -schema = $this->getSchema($response->content); - } - - /** @param array $content */ - private function getSchema(array $content): V30\Schema | V31\Schema | null - { - if ($content === []) { - return null; - } - - $schema = $content['application/json']->schema - ?? throw CannotProcessOpenAPI::unsupportedMediaTypes(...array_keys($content)); - - return $schema; - } -} diff --git a/tests/Console/Command/CacheOpenAPIProcessorsTest.php b/tests/Console/Command/CacheOpenAPIProcessorsTest.php index 4d1162b4..107a3512 100644 --- a/tests/Console/Command/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Command/CacheOpenAPIProcessorsTest.php @@ -50,7 +50,6 @@ #[UsesClass(Membrane\OpenAPI\Filter\QueryStringToArray::class)] #[UsesClass(Membrane\OpenAPI\Filter\FormatStyle\Form::class)] #[UsesClass(OpenAPI\Processor\Request::class)] -#[UsesClass(Specification\OpenAPIResponse::class)] #[UsesClass(Membrane\Result\Result::class)] #[UsesClass(AlphaNumeric::class)] #[UsesClass(ToPascalCase::class)] @@ -144,11 +143,7 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreB', 'Response\\Code200\\FindPets', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'findPets', - '200', - $petstoreExpandedOpenApi->paths['/pets']->get->responses['200'] - ) + $petstoreExpandedOpenApi->paths['/pets']->get->responses['200'], ), ], 'findPets : default Response' => [ @@ -157,11 +152,7 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreC', 'Response\\CodeDefault\\FindPets', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'findPets', - 'default', $petstoreExpandedOpenApi->paths['/pets']->get->responses['default'] - ) ), ], 'addPet : Request' => [ @@ -181,11 +172,7 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreE', 'Response\\Code200\\AddPet', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'addPet', - '200', - $petstoreExpandedOpenApi->paths['/pets']->post->responses['200'] - ) + $petstoreExpandedOpenApi->paths['/pets']->post->responses['200'], ), ], 'addPet : default Response' => [ @@ -194,11 +181,7 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreF', 'Response\\CodeDefault\\AddPet', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'addPet', - 'default', - $petstoreExpandedOpenApi->paths['/pets']->post->responses['default'] - ) + $petstoreExpandedOpenApi->paths['/pets']->post->responses['default'], ), ], 'find pet by id : Request' => [ @@ -218,11 +201,7 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreH', 'Response\\Code200\\FindPetById', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'find pet by id', - '200', - $petstoreExpandedOpenApi->paths['/pets/{id}']->get->responses['200'] - ) + $petstoreExpandedOpenApi->paths['/pets/{id}']->get->responses['200'] ), ], 'find pet by id : default Response' => [ @@ -231,11 +210,7 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreI', 'Response\\CodeDefault\\FindPetById', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'find pet by id', - 'default', - $petstoreExpandedOpenApi->paths['/pets/{id}']->get->responses['default'] - ) + $petstoreExpandedOpenApi->paths['/pets/{id}']->get->responses['default'] ), ], 'deletePet : Request' => [ @@ -255,11 +230,7 @@ public static function provideCasesToCache(): array 'CommandTest\\PetstoreK', 'Response\\Code204\\DeletePet', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'deletePet', - '204', - $petstoreExpandedOpenApi->paths['/pets/{id}']->delete->responses['204'] - ) + $petstoreExpandedOpenApi->paths['/pets/{id}']->delete->responses['204'] ), ], ]; diff --git a/tests/Console/Service/CacheOpenAPIProcessorsTest.php b/tests/Console/Service/CacheOpenAPIProcessorsTest.php index 629e1b82..9c5b46ec 100644 --- a/tests/Console/Service/CacheOpenAPIProcessorsTest.php +++ b/tests/Console/Service/CacheOpenAPIProcessorsTest.php @@ -45,7 +45,6 @@ #[UsesClass(OpenAPI\Filter\QueryStringToArray::class)] #[UsesClass(OpenAPI\Filter\FormatStyle\Form::class)] #[UsesClass(OpenAPI\Processor\Request::class)] -#[UsesClass(Specification\OpenAPIResponse::class)] #[UsesClass(Membrane\Result\Result::class)] #[UsesClass(IntString::class)] #[UsesClass(AlphaNumeric::class)] @@ -270,11 +269,7 @@ public static function provideCasesOfCachedResponsesFromPetstoreExpanded(): arra 'ServiceTest\\PetstoreB', 'Response\\Code200\\FindPets', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'findPets', - '200', - $petstoreExpandedOpenApi->paths['/pets']->get->responses['200'] - ) + $petstoreExpandedOpenApi->paths['/pets']->get->responses['200'] ), ], 'findPets : default Response' => [ @@ -283,11 +278,7 @@ public static function provideCasesOfCachedResponsesFromPetstoreExpanded(): arra 'ServiceTest\\PetstoreC', 'Response\\CodeDefault\\FindPets', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'findPets', - 'default', - $petstoreExpandedOpenApi->paths['/pets']->get->responses['default'] - ) + $petstoreExpandedOpenApi->paths['/pets']->get->responses['default'] ), ], 'addPet : 200 Response' => [ @@ -296,11 +287,7 @@ public static function provideCasesOfCachedResponsesFromPetstoreExpanded(): arra 'ServiceTest\\PetstoreE', 'Response\\Code200\\AddPet', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'addPet', - '200', - $petstoreExpandedOpenApi->paths['/pets']->post->responses['200'] - ) + $petstoreExpandedOpenApi->paths['/pets']->post->responses['200'] ), ], 'addPet : default Response' => [ @@ -309,11 +296,7 @@ public static function provideCasesOfCachedResponsesFromPetstoreExpanded(): arra 'ServiceTest\\PetstoreF', 'Response\\CodeDefault\\AddPet', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'addPet', - 'default', - $petstoreExpandedOpenApi->paths['/pets']->post->responses['default'] - ) + $petstoreExpandedOpenApi->paths['/pets']->post->responses['default'] ), ], 'find pet by id : 200 Response' => [ @@ -322,11 +305,7 @@ public static function provideCasesOfCachedResponsesFromPetstoreExpanded(): arra 'ServiceTest\\PetstoreF', 'Response\\Code200\\FindPetById', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'find pet by id', - '200', - $petstoreExpandedOpenApi->paths['/pets/{id}']->get->responses['200'] - ) + $petstoreExpandedOpenApi->paths['/pets/{id}']->get->responses['200'] ), ], 'find pet by id : default Response' => [ @@ -335,11 +314,7 @@ public static function provideCasesOfCachedResponsesFromPetstoreExpanded(): arra 'ServiceTest\\PetstoreG', 'Response\\CodeDefault\\FindPetById', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'find pet by id', - 'default', - $petstoreExpandedOpenApi->paths['/pets/{id}']->get->responses['default'] - ) + $petstoreExpandedOpenApi->paths['/pets/{id}']->get->responses['default'] ), ], 'deletePet : 204 Response' => [ @@ -348,11 +323,7 @@ public static function provideCasesOfCachedResponsesFromPetstoreExpanded(): arra 'ServiceTest\\PetstoreI', 'Response\\Code204\\DeletePet', $responseBuilder->build( - new Specification\OpenAPIResponse( - 'deletePet', - '204', - $petstoreExpandedOpenApi->paths['/pets/{id}']->delete->responses['204'] - ) + $petstoreExpandedOpenApi->paths['/pets/{id}']->delete->responses['204'] ), ], ]; diff --git a/tests/MembraneTest.php b/tests/MembraneTest.php index 11b0f350..387ac66c 100644 --- a/tests/MembraneTest.php +++ b/tests/MembraneTest.php @@ -12,7 +12,6 @@ use Membrane\Membrane; use Membrane\OpenAPI\Builder as Builder; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; -use Membrane\OpenAPI\Specification as OpenAPISpecification; use Membrane\OpenAPI\Specification\Request; use Membrane\OpenAPI\Specification\Response; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method; @@ -41,7 +40,6 @@ #[UsesClass(Builder\Internal\Response::class)] #[UsesClass(Builder\RequestBuilder::class)] #[UsesClass(Builder\ResponseBuilder::class)] -#[UsesClass(OpenAPISpecification\OpenAPIResponse::class)] #[UsesClass(Processor\BeforeSet::class)] #[UsesClass(Processor\Collection::class)] #[UsesClass(Processor\FieldSet::class)] diff --git a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php index 929fa0d8..26280abc 100644 --- a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php @@ -5,6 +5,7 @@ namespace Membrane\Tests\OpenAPI\Builder; use Generator; +use \Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; use Membrane\Builder\Specification; use Membrane\Filter\String\ToUpperCase; use Membrane\OpenAPI\Builder\Internal; @@ -12,7 +13,6 @@ use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; -use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPI\Specification\Response; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; @@ -58,7 +58,6 @@ #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] #[CoversClass(Schema::class)] -#[UsesClass(OpenAPIResponse::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\Arrays::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\Numeric::class)] @@ -100,40 +99,16 @@ class OpenAPIResponseBuilderTest extends TestCase #[Test, TestDox('It throws an exception if you try to use the keyword "not"')] public function throwsExceptionIfNotIsFound(): void { - $openApi = (new MembraneReader([ + $openApi = new MembraneReader([ OpenAPIVersion::Version_3_0, - ]))->readFromAbsoluteFilePath(self::DIR . 'noReferences.json'); + ])->readFromAbsoluteFilePath(self::DIR . 'noReferences.json'); $operation = $openApi->paths['/responsepath']->get; $sut = new Internal\Response(); - $response = new OpenAPIResponse( - $operation->operationId, - '360', - $operation->responses['360'], - ); - self::expectExceptionObject(CannotProcessOpenAPI::unsupportedKeyword('not')); - $sut->build($response); - } - - #[Test, TestDox('It supports the Response Specification')] - public function supportsResponseSpecification(): void - { - $specification = self::createStub(OpenAPIResponse::class); - $sut = new Internal\Response(); - - self::assertTrue($sut->supports($specification)); - } - - #[Test, TestDox('It does not support any Specifications that are not Response')] - public function doesNotSupportSpecificationsThatAreNotResponse(): void - { - $specification = self::createStub(\Membrane\Builder\Specification::class); - $sut = new Internal\Response(); - - self::assertFalse($sut->supports($specification)); + $sut->build($operation->responses['360']); } public static function dataSetsforBuilds(): array @@ -147,83 +122,43 @@ public static function dataSetsforBuilds(): array return [ 'no properties' => [ - new OpenAPIResponse( - $noReferences->paths['/path']->get->operationId, - '200', - $noReferences->paths['/path']->get->responses['200'] - ), + $noReferences->paths['/path']->get->responses['200'], new Field('', new Passes()), ], 'int' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '200', - $noReferences->paths['/responsepath']->get->responses['200'] - ), + $noReferences->paths['/responsepath']->get->responses['200'], new Field('', new IsInt()), ], 'nullable int' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '201', - $noReferences->paths['/responsepath']->get->responses['201'] - ), + $noReferences->paths['/responsepath']->get->responses['201'], new AnyOf('', new Field('', new IsInt()), new Field('', new IsNull())), ], 'int, inclusive min' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '202', - $noReferences->paths['/responsepath']->get->responses['202'] - ), + $noReferences->paths['/responsepath']->get->responses['202'], new Field('', new IsInt(), new Minimum(0)), ], 'int, exclusive min' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '203', - $noReferences->paths['/responsepath']->get->responses['203'] - ), + $noReferences->paths['/responsepath']->get->responses['203'], new Field('', new IsInt(), new Minimum(0, true)), ], 'int, inclusive max' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '204', - $noReferences->paths['/responsepath']->get->responses['204'] - ), + $noReferences->paths['/responsepath']->get->responses['204'], new Field('', new IsInt(), new Maximum(100)), ], 'int, exclusive max' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '205', - $noReferences->paths['/responsepath']->get->responses['205'] - ), + $noReferences->paths['/responsepath']->get->responses['205'], new Field('', new IsInt(), new Maximum(100, true)), ], 'int, multipleOf' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '206', - $noReferences->paths['/responsepath']->get->responses['206'] - ), + $noReferences->paths['/responsepath']->get->responses['206'], new Field('', new IsInt(), new MultipleOf(3)), ], 'int, enum' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '207', - $noReferences->paths['/responsepath']->get->responses['207'] - ), + $noReferences->paths['/responsepath']->get->responses['207'], new Field('', new IsInt(), new Contained([1, 2, 3])), ], 'nullable int, enum, exclusive min, inclusive max, multipleOf' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '209', - $noReferences->paths['/responsepath']->get->responses['209'] - ), + $noReferences->paths['/responsepath']->get->responses['209'], new AnyOf( '', new Field( @@ -238,59 +173,31 @@ public static function dataSetsforBuilds(): array ), ], 'number' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '210', - $noReferences->paths['/responsepath']->get->responses['210'] - ), + $noReferences->paths['/responsepath']->get->responses['210'], new Field('', new IsNumber()), ], 'nullable number' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '211', - $noReferences->paths['/responsepath']->get->responses['211'] - ), + $noReferences->paths['/responsepath']->get->responses['211'], new AnyOf('', new Field('', new IsNumber()), new Field('', new IsNull())), ], 'number, enum' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '212', - $noReferences->paths['/responsepath']->get->responses['212'] - ), + $noReferences->paths['/responsepath']->get->responses['212'], new Field('', new IsNumber(), new Contained([1, 2.3, 4])), ], 'number, float format' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '213', - $noReferences->paths['/responsepath']->get->responses['213'] - ), + $noReferences->paths['/responsepath']->get->responses['213'], new Field('', new IsFloat()), ], 'nullable number, float format' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '214', - $noReferences->paths['/responsepath']->get->responses['214'] - ), + $noReferences->paths['/responsepath']->get->responses['214'], new AnyOf('', new Field('', new IsFloat()), new Field('', new IsNull())), ], 'number, double format' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '215', - $noReferences->paths['/responsepath']->get->responses['215'] - ), + $noReferences->paths['/responsepath']->get->responses['215'], new Field('', new IsFloat()), ], 'nullable number, enum, inclusive min, exclusive max, multipleOf' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '219', - $noReferences->paths['/responsepath']->get->responses['219'] - ), + $noReferences->paths['/responsepath']->get->responses['219'], new AnyOf( '', new Field( @@ -305,43 +212,23 @@ public static function dataSetsforBuilds(): array ), ], 'string' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '220', - $noReferences->paths['/responsepath']->get->responses['220'] - ), + $noReferences->paths['/responsepath']->get->responses['220'], new Field('', new IsString()), ], 'nullable string' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '221', - $noReferences->paths['/responsepath']->get->responses['221'] - ), + $noReferences->paths['/responsepath']->get->responses['221'], new AnyOf('', new Field('', new IsString()), new Field('', new IsNull())), ], 'string, enum' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '222', - $noReferences->paths['/responsepath']->get->responses['222'] - ), + $noReferences->paths['/responsepath']->get->responses['222'], new Field('', new IsString(), new Contained(['a', 'b', 'c'])), ], 'string, date format' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '223', - $noReferences->paths['/responsepath']->get->responses['223'] - ), + $noReferences->paths['/responsepath']->get->responses['223'], new Field('', new IsString(), new DateString('Y-m-d', true)), ], 'string, date-time format' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '224', - $noReferences->paths['/responsepath']->get->responses['224'] - ), + $noReferences->paths['/responsepath']->get->responses['224'], new Field( '', new IsString(), @@ -353,35 +240,19 @@ public static function dataSetsforBuilds(): array ), ], 'string, minLength' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '225', - $noReferences->paths['/responsepath']->get->responses['225'] - ), + $noReferences->paths['/responsepath']->get->responses['225'], new Field('', new IsString(), new Length(5)), ], 'string, maxLength' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '226', - $noReferences->paths['/responsepath']->get->responses['226'] - ), + $noReferences->paths['/responsepath']->get->responses['226'], new Field('', new IsString(), new Length(0, 10)), ], 'string, pattern' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '227', - $noReferences->paths['/responsepath']->get->responses['227'] - ), + $noReferences->paths['/responsepath']->get->responses['227'], new Field('', new IsString(), new Regex('#[A-Za-z]+#u')), ], 'nullable string, enum, minLength, maxLength, pattern' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '229', - $noReferences->paths['/responsepath']->get->responses['229'] - ), + $noReferences->paths['/responsepath']->get->responses['229'], new AnyOf( '', new Field( @@ -395,35 +266,19 @@ public static function dataSetsforBuilds(): array ), ], 'bool' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '230', - $noReferences->paths['/responsepath']->get->responses['230'] - ), + $noReferences->paths['/responsepath']->get->responses['230'], new Field('', new IsBool()), ], 'nullable bool' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '231', - $noReferences->paths['/responsepath']->get->responses['231'] - ), + $noReferences->paths['/responsepath']->get->responses['231'], new AnyOf('', new Field('', new IsBool()), new Field('', new IsNull())), ], 'bool, enum' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '232', - $noReferences->paths['/responsepath']->get->responses['232'] - ), + $noReferences->paths['/responsepath']->get->responses['232'], new Field('', new IsBool(), new Contained([true])), ], 'nullable bool, enum' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '239', - $noReferences->paths['/responsepath']->get->responses['239'] - ), + $noReferences->paths['/responsepath']->get->responses['239'], new AnyOf( '', new Field('', new IsBool(), new Contained([true, null])), @@ -431,19 +286,11 @@ public static function dataSetsforBuilds(): array ), ], 'array of ints' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '240', - $noReferences->paths['/responsepath']->get->responses['240'] - ), + $noReferences->paths['/responsepath']->get->responses['240'], new Collection('', new BeforeSet(new IsList()), new Field('', new IsInt())), ], 'array of strings, enum' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '241', - $noReferences->paths['/responsepath']->get->responses['241'] - ), + $noReferences->paths['/responsepath']->get->responses['241'], new Collection( '', new BeforeSet(new IsList(), new Contained([['a', 'b', 'c'], ['d', 'e', 'f']])), @@ -451,11 +298,7 @@ public static function dataSetsforBuilds(): array ), ], 'nullable array of strings' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '242', - $noReferences->paths['/responsepath']->get->responses['242'] - ), + $noReferences->paths['/responsepath']->get->responses['242'], new AnyOf( '', new Collection('', new BeforeSet(new IsList()), new Field('', new IsString())), @@ -463,35 +306,19 @@ public static function dataSetsforBuilds(): array ), ], 'array of booleans, minItems' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '243', - $noReferences->paths['/responsepath']->get->responses['243'] - ), + $noReferences->paths['/responsepath']->get->responses['243'], new Collection('', new BeforeSet(new IsList(), new Count(5)), new Field('', new IsBool())), ], 'array of floats, maxItems' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '244', - $noReferences->paths['/responsepath']->get->responses['244'] - ), + $noReferences->paths['/responsepath']->get->responses['244'], new Collection('', new BeforeSet(new IsList(), new Count(0, 5)), new Field('', new IsFloat())), ], 'array of numbers, uniqueItems' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '245', - $noReferences->paths['/responsepath']->get->responses['245'] - ), + $noReferences->paths['/responsepath']->get->responses['245'], new Collection('', new BeforeSet(new IsList(), new Unique()), new Field('', new IsNumber())), ], 'nullable array of nullable numbers, enum, minItems, maxItems, uniqueItems' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '269', - $noReferences->paths['/responsepath']->get->responses['269'] - ), + $noReferences->paths['/responsepath']->get->responses['269'], new AnyOf( '', new Collection( @@ -508,11 +335,7 @@ public static function dataSetsforBuilds(): array ), ], 'object with (string) name' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '270', - $noReferences->paths['/responsepath']->get->responses['270'] - ), + $noReferences->paths['/responsepath']->get->responses['270'], new FieldSet( '', new BeforeSet(new IsArray()), @@ -520,11 +343,7 @@ public static function dataSetsforBuilds(): array ), ], 'object with (int) id, enum' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '271', - $noReferences->paths['/responsepath']->get->responses['271'] - ), + $noReferences->paths['/responsepath']->get->responses['271'], new FieldSet( '', new BeforeSet(new IsArray(), new Contained([['id' => 5], ['id' => 10]])), @@ -532,11 +351,7 @@ public static function dataSetsforBuilds(): array ), ], 'nullable object with (float) price' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '272', - $noReferences->paths['/responsepath']->get->responses['272'] - ), + $noReferences->paths['/responsepath']->get->responses['272'], new AnyOf( '', new FieldSet('', new BeforeSet(new IsArray()), new Field('price', new IsFloat())), @@ -544,11 +359,7 @@ public static function dataSetsforBuilds(): array ), ], 'object with (string) name, (int) id, (bool) status' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '273', - $noReferences->paths['/responsepath']->get->responses['273'] - ), + $noReferences->paths['/responsepath']->get->responses['273'], new FieldSet( '', new BeforeSet(new IsArray()), @@ -558,11 +369,7 @@ public static function dataSetsforBuilds(): array ), ], 'object with (string) name, (int) id, (bool) status, required' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '274', - $noReferences->paths['/responsepath']->get->responses['274'] - ), + $noReferences->paths['/responsepath']->get->responses['274'], new FieldSet( '', new BeforeSet(new IsArray(), new RequiredFields('name', 'id')), @@ -572,11 +379,7 @@ public static function dataSetsforBuilds(): array ), ], 'nullable object with (string) name, (int) id, (bool) status, enum, required' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '299', - $noReferences->paths['/responsepath']->get->responses['299'] - ), + $noReferences->paths['/responsepath']->get->responses['299'], new FieldSet( '', new BeforeSet( @@ -595,19 +398,11 @@ public static function dataSetsforBuilds(): array ), ], 'allOf, one object (should act like normal object)' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '300', - $noReferences->paths['/responsepath']->get->responses['300'] - ), + $noReferences->paths['/responsepath']->get->responses['300'], new FieldSet('', new Field('name', new IsString()), new BeforeSet(new IsArray())), ], 'allOf, two objects, one identical parameter' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '301', - $noReferences->paths['/responsepath']->get->responses['301'] - ), + $noReferences->paths['/responsepath']->get->responses['301'], new AllOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -615,11 +410,7 @@ public static function dataSetsforBuilds(): array ), ], 'allOf, two objects, one unique parameters' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '302', - $noReferences->paths['/responsepath']->get->responses['302'] - ), + $noReferences->paths['/responsepath']->get->responses['302'], new AllOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -627,11 +418,7 @@ public static function dataSetsforBuilds(): array ), ], 'allOf, two objects, conflicting parameter' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '303', - $noReferences->paths['/responsepath']->get->responses['303'] - ), + $noReferences->paths['/responsepath']->get->responses['303'], new AllOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -639,11 +426,7 @@ public static function dataSetsforBuilds(): array ), ], 'allOf, two objects, unique parameters, one requiredField' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '304', - $noReferences->paths['/responsepath']->get->responses['304'] - ), + $noReferences->paths['/responsepath']->get->responses['304'], new AllOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -655,11 +438,7 @@ public static function dataSetsforBuilds(): array ), ], 'allOf, two objects, unique parameters, two requiredField' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '305', - $noReferences->paths['/responsepath']->get->responses['305'] - ), + $noReferences->paths['/responsepath']->get->responses['305'], new AllOf( '', new FieldSet( @@ -675,11 +454,7 @@ public static function dataSetsforBuilds(): array ), ], 'allOf, two objects, unique parameters, two requiredFields requiring the other schemas property' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '306', - $noReferences->paths['/responsepath']->get->responses['306'] - ), + $noReferences->paths['/responsepath']->get->responses['306'], new AllOf( '', new FieldSet( @@ -695,19 +470,11 @@ public static function dataSetsforBuilds(): array ), ], 'anyOf, one object (should act like normal object)' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '320', - $noReferences->paths['/responsepath']->get->responses['320'] - ), + $noReferences->paths['/responsepath']->get->responses['320'], new FieldSet('', new Field('name', new IsString()), new BeforeSet(new IsArray())), ], 'anyOf, two objects, one identical parameter' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '321', - $noReferences->paths['/responsepath']->get->responses['321'] - ), + $noReferences->paths['/responsepath']->get->responses['321'], new AnyOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -715,11 +482,7 @@ public static function dataSetsforBuilds(): array ), ], 'anyOf, two objects, one unique parameters' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '322', - $noReferences->paths['/responsepath']->get->responses['322'] - ), + $noReferences->paths['/responsepath']->get->responses['322'], new AnyOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -727,11 +490,7 @@ public static function dataSetsforBuilds(): array ), ], 'anyOf, two objects, conflicting parameter' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '323', - $noReferences->paths['/responsepath']->get->responses['323'] - ), + $noReferences->paths['/responsepath']->get->responses['323'], new AnyOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -739,11 +498,7 @@ public static function dataSetsforBuilds(): array ), ], 'anyOf, two objects, unique parameters, one requiredField' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '324', - $noReferences->paths['/responsepath']->get->responses['324'] - ), + $noReferences->paths['/responsepath']->get->responses['324'], new AnyOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -755,11 +510,7 @@ public static function dataSetsforBuilds(): array ), ], 'anyOf, two objects, unique parameters, two requiredField' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '325', - $noReferences->paths['/responsepath']->get->responses['325'] - ), + $noReferences->paths['/responsepath']->get->responses['325'], new AnyOf( '', new FieldSet( @@ -775,11 +526,7 @@ public static function dataSetsforBuilds(): array ), ], 'anyOf, two objects, unique parameters, two requiredFields requiring the other schemas property' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '326', - $noReferences->paths['/responsepath']->get->responses['326'] - ), + $noReferences->paths['/responsepath']->get->responses['326'], new AnyOf( '', new FieldSet( @@ -795,19 +542,11 @@ public static function dataSetsforBuilds(): array ), ], 'oneOf, one object (should act like normal object)' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '340', - $noReferences->paths['/responsepath']->get->responses['340'] - ), + $noReferences->paths['/responsepath']->get->responses['340'], new FieldSet('', new Field('name', new IsString()), new BeforeSet(new IsArray())), ], 'oneOf, two objects, one identical parameter' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '341', - $noReferences->paths['/responsepath']->get->responses['341'] - ), + $noReferences->paths['/responsepath']->get->responses['341'], new OneOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -815,11 +554,7 @@ public static function dataSetsforBuilds(): array ), ], 'oneOf, two objects, one unique parameters' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '342', - $noReferences->paths['/responsepath']->get->responses['342'] - ), + $noReferences->paths['/responsepath']->get->responses['342'], new OneOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -827,11 +562,7 @@ public static function dataSetsforBuilds(): array ), ], 'oneOf, two objects, conflicting parameter' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '343', - $noReferences->paths['/responsepath']->get->responses['343'] - ), + $noReferences->paths['/responsepath']->get->responses['343'], new OneOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -839,11 +570,7 @@ public static function dataSetsforBuilds(): array ), ], 'oneOf, two objects, unique parameters, one requiredField' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '344', - $noReferences->paths['/responsepath']->get->responses['344'] - ), + $noReferences->paths['/responsepath']->get->responses['344'], new OneOf( '', new FieldSet('Branch-1', new Field('id', new IsInt()), new BeforeSet(new IsArray())), @@ -855,11 +582,7 @@ public static function dataSetsforBuilds(): array ), ], 'oneOf, two objects, unique parameters, two requiredField' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '345', - $noReferences->paths['/responsepath']->get->responses['345'] - ), + $noReferences->paths['/responsepath']->get->responses['345'], new OneOf( '', new FieldSet( @@ -875,11 +598,7 @@ public static function dataSetsforBuilds(): array ), ], 'oneOf, two objects, unique parameters, two requiredFields requiring the other schemas property' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '346', - $noReferences->paths['/responsepath']->get->responses['346'] - ), + $noReferences->paths['/responsepath']->get->responses['346'], new OneOf( '', new FieldSet( @@ -895,35 +614,19 @@ public static function dataSetsforBuilds(): array ), ], 'schema with no specified type' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '404', - $noReferences->paths['/responsepath']->get->responses['404'] - ), + $noReferences->paths['/responsepath']->get->responses['404'], new Field('', new Passes()), ], 'schema with empty content' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '405', - $noReferences->paths['/responsepath']->get->responses['405'] - ), + $noReferences->paths['/responsepath']->get->responses['405'], new Field('', new Passes()), ], 'schema with no content' => [ - new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '406', - $noReferences->paths['/responsepath']->get->responses['406'] - ), + $noReferences->paths['/responsepath']->get->responses['406'], new Field('', new Passes()), ], 'petstore.yaml: /pets path -> get operation -> 200 response' => [ - new OpenAPIResponse( - $petstore->paths['/pets']->get->operationId, - '200', - $petstore->paths['/pets']->get->responses['200'] - ), + $petstore->paths['/pets']->get->responses['200'], new Collection( '', new BeforeSet(new IsList(), new Count(0, 100)), @@ -941,13 +644,11 @@ public static function dataSetsforBuilds(): array #[Test, TestDox('It builds processors that can validate data matches response content')] #[DataProvider('dataSetsforBuilds')] - public function buildsTest(Specification $spec, Processor $expected): void - { - $sut = new Internal\Response(); - - $processor = $sut->build($spec); - - self::assertEquals($expected, $processor); + public function buildsTest( + V30\Response | V31\Response $response, + Processor $expected, + ): void { + self::assertEquals($expected, new Internal\Response()->build($response)); } public static function dataSetsForDocExamples(): array @@ -955,11 +656,7 @@ public static function dataSetsForDocExamples(): array $petstore = new MembraneReader([OpenAPIVersion::Version_3_0]) ->readFromAbsoluteFilePath(self::DIR . 'docs/petstore.yaml'); - $petsGet200Response = new OpenAPIResponse( - $petstore->paths['/pets']->get->operationId, - '200', - $petstore->paths['/pets']->get->responses['200'] - ); + $petsGet200Response = $petstore->paths['/pets']->get->responses['200']; return [ 'dataSet A' => [ @@ -1016,32 +713,29 @@ public static function dataSetsForDocExamples(): array #[DataProvider('dataSetsForDocExamples')] #[Test] - public function docsTest(Specification $spec, array $data, Result $expected): void - { - $sut = new Internal\Response(); - - $processor = $sut->build($spec); + public function docsTest( + V30\Response | V31\Response $response, + array $data, + Result $expected, + ): void { + $processor = new Internal\Response()->build($response); self::assertEquals($expected, $processor->process(new FieldName(''), $data)); } #[Test] #[DataProvider('provideDateStrings')] - public function itValidatesDateTime(Result $expected, string $dateTime): void + public function itValidatesDateTime( + Result $expected, + string $dateTime, + ): void { $noReferences = new MembraneReader([ OpenAPIVersion::Version_3_0, ])->readFromAbsoluteFilePath(self::DIR . 'noReferences.json'); - $specification = new OpenAPIResponse( - $noReferences->paths['/responsepath']->get->operationId, - '224', - $noReferences->paths['/responsepath']->get->responses['224'] - ); - - $sut = new Internal\Response(); - - $processor = $sut->build($specification); + $processor = new Internal\Response() + ->build($noReferences->paths['/responsepath']->get->responses['224']); self::assertEquals($expected, $processor->process(new FieldName(''), $dateTime)); } diff --git a/tests/OpenAPI/Builder/ResponseBuilderTest.php b/tests/OpenAPI/Builder/ResponseBuilderTest.php index d9e78893..50e74db2 100644 --- a/tests/OpenAPI/Builder/ResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/ResponseBuilderTest.php @@ -13,7 +13,6 @@ use Membrane\OpenAPI\Exception\CannotProcessResponse; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; -use Membrane\OpenAPI\Specification\OpenAPIResponse; use Membrane\OpenAPI\Specification\Response; use Membrane\OpenAPIReader\ValueObject\Valid\Enum\Method; use Membrane\Processor; @@ -60,7 +59,6 @@ #[CoversClass(CannotProcessOpenAPI::class)] #[UsesClass(Internal\Schema::class)] #[UsesClass(Internal\Response::class)] -#[UsesClass(OpenAPIResponse::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\Arrays::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] #[UsesClass(\Membrane\OpenAPI\Builder\Internal\Numeric::class)] diff --git a/tests/OpenAPI/Specification/OpenAPIResponseTest.php b/tests/OpenAPI/Specification/OpenAPIResponseTest.php deleted file mode 100644 index e4bce68f..00000000 --- a/tests/OpenAPI/Specification/OpenAPIResponseTest.php +++ /dev/null @@ -1,74 +0,0 @@ - new Partial\MediaType( - contentType: 'application/pdf', - schema: new Partial\Schema(type: 'string'), - )] - )); - - self::expectExceptionObject(CannotProcessOpenAPI::unsupportedMediaTypes(...array_keys($response->content))); - - new OpenAPIResponse('testOperation', '200', $response); - } - - #[Test, TestDox('$schema contains a Schema Object if the response has empty content')] - public function schemaIsNullIfContentIsEmpty(): void - { - $response = new V30\Response(new Identifier('test'), new Partial\Response( - description: 'Success', - headers: [], - content: [] - )); - - $sut = new OpenAPIResponse('testOperation', '200', $response); - - self::assertNull($sut->schema); - } - - #[Test, TestDox('$schema contains a Schema Object if the response has content')] - public function schemaContainsResponseSchemaIfContentExists(): void - { - $expected = new V30\Schema( - new Identifier('test', 'application/json', 'schema'), - new Partial\Schema(type: 'integer') - ); - - $response = new V30\Response(new Identifier('test'), new Partial\Response( - description: 'Success', - headers: [], - content: ['application/pdf' => new Partial\MediaType( - contentType: 'application/json', - schema: new Partial\Schema(type: 'integer'), - )] - )); - - $sut = new OpenAPIResponse('testOperation', '200', $response); - - self::assertEquals($expected, $sut->schema); - } -} From 1e999bad3c93aaeabe68ebef1764e5f516e95d3f Mon Sep 17 00:00:00 2001 From: John Charman Date: Wed, 23 Sep 2026 13:09:38 +0100 Subject: [PATCH 44/47] Add internal doc block to Internal\Response --- src/OpenAPI/Builder/Internal/Response.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/OpenAPI/Builder/Internal/Response.php b/src/OpenAPI/Builder/Internal/Response.php index 90371381..d537e540 100644 --- a/src/OpenAPI/Builder/Internal/Response.php +++ b/src/OpenAPI/Builder/Internal/Response.php @@ -10,6 +10,9 @@ use Membrane\Processor\Field; use Membrane\Validator\Utility\Passes; +/** + * @internal see README.md + */ class Response extends Schema { public function build( From 1f8bc6b141874f40ff7a3dcd45a7d1bccd72fd87 Mon Sep 17 00:00:00 2001 From: John Charman Date: Wed, 23 Sep 2026 13:27:42 +0100 Subject: [PATCH 45/47] Replace property hooks with getters This is done to appease php code sniffer for now, it seems not to understand them --- src/OpenAPI/Builder/Internal/Request.php | 13 ++++++++----- src/OpenAPI/Builder/ResponseBuilder.php | 12 ++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/OpenAPI/Builder/Internal/Request.php b/src/OpenAPI/Builder/Internal/Request.php index 8dc66f58..77d91e2b 100644 --- a/src/OpenAPI/Builder/Internal/Request.php +++ b/src/OpenAPI/Builder/Internal/Request.php @@ -24,9 +24,7 @@ */ class Request { - private Schema $schemaBuilder { - get => $this->schemaBuilder ??= new Schema(); - } + private Schema $schemaBuilder; public function build( ExtractsPathParameters $pathParameterExtractor, @@ -62,7 +60,7 @@ private function fromRequestBody( ContentType::fromContentTypeHeader($contentType) !== ContentType::Unmatched && $mediaType->schema !== null ) { - return $this->schemaBuilder->fromSchema( + return $this->getSchemaBuilder()->fromSchema( $mediaType->schema, 'requestBody', ); @@ -116,7 +114,7 @@ private function fromParameters( } $locations[$parameter->in->value]['fields'][] = $this - ->schemaBuilder + ->getSchemaBuilder() ->fromSchema( $parameter->getSchema(), $parameter->name, @@ -150,4 +148,9 @@ private function fromParameters( return $fieldSets; } + + private function getSchemaBuilder(): Schema + { + return $this->schemaBuilder ??= new Schema(); + } } diff --git a/src/OpenAPI/Builder/ResponseBuilder.php b/src/OpenAPI/Builder/ResponseBuilder.php index 31c6ba82..89aace67 100644 --- a/src/OpenAPI/Builder/ResponseBuilder.php +++ b/src/OpenAPI/Builder/ResponseBuilder.php @@ -6,6 +6,7 @@ use Membrane\Builder\Builder; use Membrane\Builder\Specification; +use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Exception\CannotProcessResponse; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; @@ -18,9 +19,7 @@ class ResponseBuilder implements Builder { - private Internal\Response $responseBuilder { - get => $this->responseBuilder ??= new Internal\Response(); - } + private Internal\Response $responseBuilder; public function supports(Specification $specification): bool { @@ -47,7 +46,7 @@ public function build(Specification $specification): Processor $response = $this->getResponse($operation, $specification->statusCode); - return $this->responseBuilder->build($response); + return $this->getResponseBuilder()->build($response); } throw CannotProcessSpecification::pathNotFound( @@ -89,4 +88,9 @@ private function matchServer( return ''; } + + private function getResponseBuilder(): Internal\Response + { + return $this->responseBuilder ??= new Internal\Response(); + } } From 228a11d2d81a4294c8c31b2d6446ffbb01ef2a6d Mon Sep 17 00:00:00 2001 From: John Charman Date: Wed, 23 Sep 2026 13:30:45 +0100 Subject: [PATCH 46/47] Move internal test for Internal\Response to correct namespace --- .../ResponseTest.php} | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) rename tests/OpenAPI/Builder/{OpenAPIResponseBuilderTest.php => Internal/ResponseTest.php} (99%) diff --git a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php b/tests/OpenAPI/Builder/Internal/ResponseTest.php similarity index 99% rename from tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php rename to tests/OpenAPI/Builder/Internal/ResponseTest.php index 26280abc..6ada7517 100644 --- a/tests/OpenAPI/Builder/OpenAPIResponseBuilderTest.php +++ b/tests/OpenAPI/Builder/Internal/ResponseTest.php @@ -2,11 +2,9 @@ declare(strict_types=1); -namespace Membrane\Tests\OpenAPI\Builder; +namespace Membrane\Tests\OpenAPI\Builder\Internal; use Generator; -use \Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; -use Membrane\Builder\Specification; use Membrane\Filter\String\ToUpperCase; use Membrane\OpenAPI\Builder\Internal; use Membrane\OpenAPI\Builder\Internal\Schema; @@ -16,6 +14,7 @@ use Membrane\OpenAPI\Specification\Response; use Membrane\OpenAPIReader\MembraneReader; use Membrane\OpenAPIReader\OpenAPIVersion; +use Membrane\OpenAPIReader\ValueObject\Valid\{V30, V31}; use Membrane\Processor; use Membrane\Processor\AllOf; use Membrane\Processor\AnyOf; @@ -92,9 +91,9 @@ #[UsesClass(IsList::class)] #[UsesClass(IsString::class)] #[UsesClass(\Membrane\Validator\Utility\AnyOf::class)] -class OpenAPIResponseBuilderTest extends TestCase +class ResponseTest extends TestCase { - public const DIR = __DIR__ . '/../../fixtures/OpenAPI/'; + public const DIR = __DIR__ . '/../../../fixtures/OpenAPI/'; #[Test, TestDox('It throws an exception if you try to use the keyword "not"')] public function throwsExceptionIfNotIsFound(): void From 927a81af1c24048363e393ec6c54512c8609b6ab Mon Sep 17 00:00:00 2001 From: John Charman Date: Wed, 23 Sep 2026 13:37:07 +0100 Subject: [PATCH 47/47] Simplify imports in tests --- tests/OpenAPI/Builder/Internal/RequestTest.php | 5 ++--- tests/OpenAPI/Builder/Internal/ResponseTest.php | 13 ++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/OpenAPI/Builder/Internal/RequestTest.php b/tests/OpenAPI/Builder/Internal/RequestTest.php index 68d744d1..4caf8d87 100644 --- a/tests/OpenAPI/Builder/Internal/RequestTest.php +++ b/tests/OpenAPI/Builder/Internal/RequestTest.php @@ -15,7 +15,6 @@ use Membrane\Filter\Type\ToInt; use Membrane\Filter\Type\ToNumber; use Membrane\OpenAPI\Builder\Internal; -use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Builder\RequestBuilder; use Membrane\OpenAPI\ContentType; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; @@ -74,12 +73,12 @@ use Psr\Http\Message\ServerRequestInterface; #[CoversClass(Request::class)] -#[CoversClass(Schema::class)] #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] +#[CoversClass(Internal\Schema::class)] #[UsesClass(HumanReadable::class)] // to render test failure messages #[UsesClass(RequestBuilder::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] +#[UsesClass(Internal\TrueFalse::class)] #[UsesClass(Internal\Request::class)] #[UsesClass(Internal\Arrays::class)] #[UsesClass(Internal\Numeric::class)] diff --git a/tests/OpenAPI/Builder/Internal/ResponseTest.php b/tests/OpenAPI/Builder/Internal/ResponseTest.php index 6ada7517..b5a6328c 100644 --- a/tests/OpenAPI/Builder/Internal/ResponseTest.php +++ b/tests/OpenAPI/Builder/Internal/ResponseTest.php @@ -7,7 +7,6 @@ use Generator; use Membrane\Filter\String\ToUpperCase; use Membrane\OpenAPI\Builder\Internal; -use Membrane\OpenAPI\Builder\Internal\Schema; use Membrane\OpenAPI\Exception\CannotProcessOpenAPI; use Membrane\OpenAPI\Exception\CannotProcessSpecification; use Membrane\OpenAPI\ExtractPathParameters\PathMatcher; @@ -56,12 +55,12 @@ #[CoversClass(Internal\Response::class)] #[CoversClass(CannotProcessSpecification::class)] #[CoversClass(CannotProcessOpenAPI::class)] -#[CoversClass(Schema::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Arrays::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Internal\TrueFalse::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Numeric::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Objects::class)] -#[UsesClass(\Membrane\OpenAPI\Builder\Internal\Strings::class)] +#[CoversClass(Internal\Schema::class)] +#[UsesClass(Internal\Arrays::class)] +#[UsesClass(Internal\TrueFalse::class)] +#[UsesClass(Internal\Numeric::class)] +#[UsesClass(Internal\Objects::class)] +#[UsesClass(Internal\Strings::class)] #[UsesClass(PathMatcher::class)] #[UsesClass(AllOf::class)] #[UsesClass(AnyOf::class)]