Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
4ae8cc0
Remove maximum and minimum in NumericBuilder
charjr Sep 21, 2026
59a5400
Remove multipleOf from Numeric Specification
charjr Sep 21, 2026
f29fd66
Remove min and max length from Strings specification
charjr Sep 21, 2026
e1cfe87
Remove pattern from Strings Specification
charjr Sep 21, 2026
933199f
Remove uniqueItems from Arrays
charjr Sep 21, 2026
cdcbe05
Remove min and max properties from Arrays Specification
charjr Sep 21, 2026
1f0df39
Remove items from Arrays
charjr Sep 21, 2026
c6fc0e4
Remove unnecessary parenthesis
charjr Sep 21, 2026
5f5ba3d
Remove most keywords from Objects
charjr Sep 21, 2026
6b4ad84
Remove enum from APISchema
charjr Sep 21, 2026
492a13e
Remove format from APISchema
charjr Sep 21, 2026
9b655ad
Remove type check from Numeric Specification
charjr Sep 21, 2026
60ef107
Remove typecheck from Arrays specification
charjr Sep 21, 2026
5e25750
Remove type check from Objects Specification
charjr Sep 21, 2026
d46e5fa
Correct Objects builder exception
charjr Sep 21, 2026
1bc8e25
Remove type check from Strings Specification
charjr Sep 21, 2026
c86e271
Remove type check from TrueFalse Specification
charjr Sep 21, 2026
dbfbe31
Remove Numeric Specification
charjr Sep 21, 2026
29488b1
Remove TrueFalse Specification
charjr Sep 21, 2026
3db23eb
Remove Strings Specification
charjr Sep 21, 2026
98feefa
Remove Objects Specification
charjr Sep 22, 2026
c499572
Remove Arrays Specification
charjr Sep 22, 2026
1bbe47c
Refactor ObjectsTest
charjr Sep 22, 2026
c70afea
Optimize imports in Objects test
charjr Sep 22, 2026
268d85c
Reimplement old exception messages in build
charjr Sep 22, 2026
fb280a0
Remove PHP UsesClass attributes for removed Specifications
charjr Sep 22, 2026
26af1c2
Remove ParameterBuilder
charjr Sep 22, 2026
d2dc52f
Remove old PHPUnit attributes
charjr Sep 22, 2026
b1814eb
Remove parameter specification
charjr Sep 22, 2026
48cee73
Move all of internal type-specific schema classes to Internal namespace
charjr Sep 22, 2026
5da9b85
Remove useless lines
charjr Sep 22, 2026
e7af23e
Remove Parameter Specification class
charjr Sep 22, 2026
2e7d3b7
Add trailing comma
charjr Sep 22, 2026
b59a737
Move APIBuilder
charjr Sep 22, 2026
35c5706
Rename APIBuilder to Schema
charjr Sep 22, 2026
44921cb
Test unsupported media types on parameters
charjr Sep 22, 2026
6459a87
Rename OpenAPIRequestBuilder variable
charjr Sep 22, 2026
113335a
Unimplement builder in OpenAPIRequestBuilder
charjr Sep 22, 2026
ece7724
Rename Builder\OpenAPIRequestBuilder to Internal\Request
charjr Sep 23, 2026
87df7c3
Replace get method with get property hook
charjr Sep 23, 2026
bb2b084
Remove OpenAPIRequest Specification
charjr Sep 23, 2026
879665c
Rename OpenAPIResponseBuilder to Internal\Response
charjr Sep 23, 2026
8c28cd7
Remove OpenAPIResponse Specification
charjr Sep 23, 2026
1e999ba
Add internal doc block to Internal\Response
charjr Sep 23, 2026
1f8bc6b
Replace property hooks with getters
charjr Sep 23, 2026
228a11d
Move internal test for Internal\Response to correct namespace
charjr Sep 23, 2026
927a81a
Simplify imports in tests
charjr Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 15 additions & 28 deletions src/Console/Service/YieldsClassDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,19 @@

namespace Membrane\Console\Service;

use Membrane\Filter;
use Membrane\Console\Template;
use Membrane\OpenAPI\Builder\OpenAPIRequestBuilder;
use Membrane\OpenAPI\Builder\OpenAPIResponseBuilder;
use Membrane\Filter;
use Membrane\OpenAPI\Builder\Internal\Request;
use Membrane\OpenAPI\Builder\Internal\Response;
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 OpenAPIResponseBuilder $responseBuilder;
private Request $requestBuilder;
private Response $responseBuilder;

public function __construct(
private readonly \Psr\Log\LoggerInterface $logger,
Expand Down Expand Up @@ -61,11 +56,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,
)
);
}
Expand All @@ -80,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),
);
}
}
Expand All @@ -113,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<string,string> $existingClassNames */
Expand All @@ -143,20 +130,20 @@ 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;
}

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

declare(strict_types=1);

namespace Membrane\OpenAPI\Builder;
namespace Membrane\OpenAPI\Builder\Internal;

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;
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;
use Membrane\Processor;
use Membrane\Processor\BeforeSet;
use Membrane\Processor\Collection;
Expand All @@ -19,38 +22,47 @@
use Membrane\Validator\Collection\Unique;
use Membrane\Validator\Type\IsList;

class Arrays extends APIBuilder
/**
* @internal see README.md
*/
class Arrays extends Schema
{
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);
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 CannotProcessSpecification::mismatchedType(
['array'],
array_map(fn($t) => $t->value, $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(',')
Expand All @@ -64,30 +76,39 @@ public function build(Specification $specification): Processor

$beforeChain[] = new IsList();

if ($specification->enum !== null) {
$beforeChain[] = new Contained($specification->enum);
if (
$keywords->enum !== null
) {
$beforeChain[] = new Contained(array_map(
fn(Value $v) => $v->value,
$keywords->enum,
));
}

if ($specification->minItems > 0 || $specification->maxItems !== null) {
$beforeChain[] = new Count($specification->minItems, $specification->maxItems);
if (
$keywords->minItems > 0
|| $keywords->maxItems !== null
) {
$beforeChain[] = new Count(
$keywords->minItems,
$keywords->maxItems,
);
}

if ($specification->uniqueItems === true) {
if ($keywords->uniqueItems === true) {
$beforeChain[] = new Unique();
}

$beforeSet = new BeforeSet(...$beforeChain);

$collection = new Collection(
$specification->fieldName,
return new Collection(
$fieldName,
$beforeSet,
$this->fromSchema(
$specification->items,
$keywords->items,
'',
$specification->convertFromString,
$convertFromString,
)
);

return $collection;
}
}
144 changes: 144 additions & 0 deletions src/OpenAPI/Builder/Internal/Numeric.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php

declare(strict_types=1);

namespace Membrane\OpenAPI\Builder\Internal;

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\Exception\CannotProcessSpecification;
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;
use Membrane\Processor;
use Membrane\Processor\Field;
use Membrane\Validator;
use Membrane\Validator\Collection\Contained;
use Membrane\Validator\Numeric\Maximum;
use Membrane\Validator\Numeric\Minimum;
use Membrane\Validator\Numeric\MultipleOf;
use Membrane\Validator\String\IntString;
use Membrane\Validator\String\NumericString;
use Membrane\Validator\Type\IsFloat;
use Membrane\Validator\Type\IsInt;
use Membrane\Validator\Type\IsNumber;

/**
* @internal see README.md
*/
final readonly class Numeric
{
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)) {
$type = Type::Number->value;
} else {
throw CannotProcessSpecification::mismatchedType(
['number', 'integer'],
array_map(fn($t) => $t->value, $keywords->types),
);
}

$chain = $convertFromArray ?
[new Filter\String\Implode(',')] :
[];

if (isset($style)) {
switch (Style::tryFrom($style)) {
case Style::Matrix:
$chain[] = new Matrix($type, false);
break;
case Style::Label:
$chain[] = new LeftTrim('.');
break;
case Style::Form:
case Style::SpaceDelimited:
case Style::PipeDelimited:
$chain[] = new Form($type, false);
break;
}
}

$chain = array_merge($chain, $type === 'number'
? $this->handleNumber($keywords, $convertFromString)
: $this->handleInteger($keywords, $convertFromString));

if (
$keywords->enum !== null
) {
$chain[] = new Contained(array_map(
fn(Value $v) => $v->value,
$keywords->enum,
));
}

$chain = array_merge(
$chain,
$this->handleNumericConstraints($keywords),
);

return new Field($fieldName, ...$chain);
}

/** @return Filter[]|Validator[] */
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 $convertFromString ? [new NumericString(), new ToNumber()] : [new IsNumber()];
}
}

/** @return Filter[]|Validator[] */
private function handleInteger(
V30\Keywords | V31\Keywords $keywords,
bool $convertFromString,
): array {
return $convertFromString ? [new IntString(), new ToInt()] : [new IsInt()];
}

/** @return Validator[] */
private function handleNumericConstraints(
V30\Keywords | V31\Keywords $keywords,
): array {
$chain = [];
// if keywords->maximum !== null
// keywords->maximum->limit
if ($keywords->maximum !== null) {
$chain[] = new Maximum(
$keywords->maximum->limit,
$keywords->maximum->exclusive,
);
}

if ($keywords->minimum !== null) {
$chain[] = new Minimum(
$keywords->minimum->limit,
$keywords->minimum->exclusive,
);
}

if ($keywords->multipleOf !== null) {
$chain[] = new MultipleOf($keywords->multipleOf);
}

return $chain;
}
}
Loading
Loading