diff --git a/composer.lock b/composer.lock index 77dafe7..004cd50 100644 --- a/composer.lock +++ b/composer.lock @@ -441,6 +441,7 @@ }, { "name": "laminas/laminas-stdlib", +<<<<<<< HEAD "version": "3.16.1", "source": { "type": "git", @@ -451,6 +452,18 @@ "type": "zip", "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/f4f773641807c7ccee59b758bfe4ac4ba33ecb17", "reference": "f4f773641807c7ccee59b758bfe4ac4ba33ecb17", +======= + "version": "3.9.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "1df1cc0b9c2b8c7d1e322da1669a576386b5da46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/1df1cc0b9c2b8c7d1e322da1669a576386b5da46", + "reference": "1df1cc0b9c2b8c7d1e322da1669a576386b5da46", +>>>>>>> b9f023e... Responses "shasum": "" }, "require": { @@ -496,7 +509,11 @@ "type": "community_bridge" } ], +<<<<<<< HEAD "time": "2022-12-03T18:48:01+00:00" +======= + "time": "2022-06-08T11:33:13+00:00" +>>>>>>> b9f023e... Responses }, { "name": "league/openapi-psr7-validator", diff --git a/openapi-client.yaml b/openapi-client.yaml index 2c25a1b..cbfb742 100644 --- a/openapi-client.yaml +++ b/openapi-client.yaml @@ -1,3 +1,4 @@ -spec: https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.yaml +#spec: https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.yaml +spec: file:///home/wyrihaximus/Projects/PHPAPIClients/openapi-client-generator/api.github.com.yaml namespace: ApiClients\Client\Github destination: generated diff --git a/src/Generator.php b/src/Generator.php index 5b4b419..393eb10 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -110,8 +110,10 @@ private function all(string $namespace): iterable $path, $method, $this->dirname($namespace . 'Operation/' . $operationClassName), + $namespace, $this->basename($namespace . 'Operation/' . $operationClassName), - $operation + $operation, + $schemaClassNameMap ); [$operationGroup, $operationOperation] = explode('/', $operationClassName); diff --git a/src/Generator/Client.php b/src/Generator/Client.php index 4aae30a..a6b1ed7 100644 --- a/src/Generator/Client.php +++ b/src/Generator/Client.php @@ -22,10 +22,44 @@ public static function generate(string $operationGroup, string $namespace, strin $factory = new BuilderFactory(); $stmt = $factory->namespace($namespace); - $class = $factory->class($className)->makeFinal(); + $class = $factory->class($className)->makeFinal()->addStmt( + $factory->property('requestSchemaValidator')->setType('\League\OpenAPIValidation\Schema\SchemaValidator')->makeReadonly()->makePrivate() + )->addStmt( + $factory->property('responseSchemaValidator')->setType('\League\OpenAPIValidation\Schema\SchemaValidator')->makeReadonly()->makePrivate() + )->addStmt( + $factory->method('__construct')->makePublic()->addParam( + (new Param('requestSchemaValidator'))->setType('\League\OpenAPIValidation\Schema\SchemaValidator') + )->addStmt( + new Node\Expr\Assign( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'requestSchemaValidator' + ), + new Node\Expr\Variable('requestSchemaValidator'), + ) + )->addParam( + (new Param('responseSchemaValidator'))->setType('\League\OpenAPIValidation\Schema\SchemaValidator') + )->addStmt( + new Node\Expr\Assign( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'responseSchemaValidator' + ), + new Node\Expr\Variable('responseSchemaValidator'), + ) + ) + ); foreach ($operations as $operationOperation => $operationDetails) { $params = []; + $params[] = new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'requestSchemaValidator' + ); + $params[] = new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'responseSchemaValidator' + ); $cn = str_replace('/', '\\', '\\' . $namespace . '\\' . $operationDetails['class']); $method = $factory->method(lcfirst($operationOperation))->setReturnType($cn)->makePublic(); foreach ($operationDetails['operation']->parameters as $parameter) { diff --git a/src/Generator/Clients.php b/src/Generator/Clients.php index 110f05b..fe922a0 100644 --- a/src/Generator/Clients.php +++ b/src/Generator/Clients.php @@ -26,7 +26,45 @@ public static function generate(string $namespace, array $clients): iterable $factory = new BuilderFactory(); $stmt = $factory->namespace(rtrim($namespace, '\\')); - $class = $factory->class('Client')->makeFinal(); + $class = $factory->class('Client')->makeFinal()->addStmt( + $factory->property('requestSchemaValidator')->setType('\League\OpenAPIValidation\Schema\SchemaValidator')->makeReadonly()->makePrivate() + )->addStmt( + $factory->property('responseSchemaValidator')->setType('\League\OpenAPIValidation\Schema\SchemaValidator')->makeReadonly()->makePrivate() + )->addStmt( + $factory->method('__construct')->makePublic()->addStmt( + new Node\Expr\Assign( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'requestSchemaValidator' + ), + new Node\Expr\New_( + new Node\Name('\League\OpenAPIValidation\Schema\SchemaValidator'), + [ + new Node\Arg(new Node\Expr\ClassConstFetch( + new Node\Name('\League\OpenAPIValidation\Schema\SchemaValidator'), + new Node\Name('VALIDATE_AS_REQUEST'), + )) + ] + ), + ) + )->addStmt( + new Node\Expr\Assign( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'responseSchemaValidator' + ), + new Node\Expr\New_( + new Node\Name('\League\OpenAPIValidation\Schema\SchemaValidator'), + [ + new Node\Arg(new Node\Expr\ClassConstFetch( + new Node\Name('\League\OpenAPIValidation\Schema\SchemaValidator'), + new Node\Name('VALIDATE_AS_RESPONSE'), + )) + ] + ), + ) + ) + ); foreach ($clients as $operationGroup => $operations) { @@ -38,6 +76,16 @@ public static function generate(string $namespace, array $clients): iterable new Node\Name( $cn ), + [ + new Node\Arg(new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'requestSchemaValidator' + )), + new Node\Arg(new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'responseSchemaValidator' + )), + ] ) ) )->makePublic() diff --git a/src/Generator/Operation.php b/src/Generator/Operation.php index 7f1e144..3b57ada 100644 --- a/src/Generator/Operation.php +++ b/src/Generator/Operation.php @@ -6,10 +6,14 @@ use cebe\openapi\spec\Operation as OpenAPiOperation; use PhpParser\Builder\Param; use PhpParser\BuilderFactory; +use PhpParser\Comment\Doc; use PhpParser\Node; +use PhpParser\Node\Arg; use PhpParser\Node\Stmt\Class_; use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; use RingCentral\Psr7\Request; +use WyriHaximus\Hydrator\Hydrator; final class Operation { @@ -21,7 +25,7 @@ final class Operation * @param OpenAPiOperation $operation * @return iterable */ - public static function generate(string $path, string $method, string $namespace, string $className, OpenAPiOperation $operation): iterable + public static function generate(string $path, string $method, string $namespace, string $rootNamespace, string $className, OpenAPiOperation $operation, array $schemaClassNameMap): iterable { $factory = new BuilderFactory(); $stmt = $factory->namespace($namespace); @@ -47,10 +51,33 @@ public static function generate(string $path, string $method, string $namespace, ) ) ) -// )->setDocComment('/**' . var_export($operation, true) . '**/'); + )->addStmt( + $factory->property('requestSchemaValidator')->setType('\League\OpenAPIValidation\Schema\SchemaValidator')->makeReadonly()->makePrivate() + )->addStmt( + $factory->property('responseSchemaValidator')->setType('\League\OpenAPIValidation\Schema\SchemaValidator')->makeReadonly()->makePrivate() ); - $constructor = $factory->method('__construct'); + $constructor = $factory->method('__construct')->makePublic()->addParam( + (new Param('requestSchemaValidator'))->setType('\League\OpenAPIValidation\Schema\SchemaValidator') + )->addStmt( + new Node\Expr\Assign( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'requestSchemaValidator' + ), + new Node\Expr\Variable('requestSchemaValidator'), + ) + )->addParam( + (new Param('responseSchemaValidator'))->setType('\League\OpenAPIValidation\Schema\SchemaValidator') + )->addStmt( + new Node\Expr\Assign( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'responseSchemaValidator' + ), + new Node\Expr\Variable('responseSchemaValidator'), + ) + ); $requestReplaces = []; $query = []; foreach ($operation->parameters as $parameter) { @@ -110,30 +137,133 @@ public static function generate(string $path, string $method, string $namespace, } } $class->addStmt($constructor); - $class->addStmt( - $factory->method('createRequest')->setReturnType('\\' . RequestInterface::class)->addStmt( - new Node\Stmt\Return_( - new Node\Expr\New_( - new Node\Name( - '\\' . Request::class + $requestParameters = [ + new Node\Arg(new Node\Scalar\String_($method)), + new Node\Arg(new Node\Expr\FuncCall( + new Node\Name('\str_replace'), + [ + new Node\Expr\Array_(array_map(static fn (string $key): Node\Expr\ArrayItem => new Node\Expr\ArrayItem(new Node\Scalar\String_($key)), array_keys($requestReplaces))), + new Node\Expr\Array_(array_values($requestReplaces)), + new Node\Scalar\String_(rtrim($path . '?' . implode('&', $query), '?')), + ] + )), + ]; + + $createRequestMethod = $factory->method('createRequest')->setReturnType('\\' . RequestInterface::class)->addParam( + $factory->param('data')->setType('array')->setDefault([]) + ); + + if ($operation->requestBody !== null) { + foreach ($operation->requestBody->content as $requestBodyContentType => $requestBodyContent) { + $requestParameters[] = new Node\Expr\Array_([ + new Node\Expr\ArrayItem(new Node\Scalar\String_($requestBodyContentType), new Node\Scalar\String_('Content-Type')) + ]); + $requestParameters[] = new Node\Expr\FuncCall(new Node\Name('json_encode'), [new Arg(new Node\Expr\Variable('data'))]); + $createRequestMethod->addStmt( + new Node\Stmt\Expression(new Node\Expr\MethodCall( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'requestSchemaValidator' ), + new Node\Name('validate'), [ - new Node\Arg(new Node\Scalar\String_($method)), - new Node\Arg(new Node\Expr\FuncCall( - new Node\Name('\str_replace'), - [ - new Node\Expr\Array_(array_map(static fn (string $key): Node\Expr\ArrayItem => new Node\Expr\ArrayItem(new Node\Scalar\String_($key)), array_keys($requestReplaces))), - new Node\Expr\Array_(array_values($requestReplaces)), - new Node\Scalar\String_(rtrim($path . '?' . implode('&', $query), '?')), - ] - )), + new Node\Arg(new Node\Expr\Variable('data')), + new Node\Arg(new Node\Expr\StaticCall(new Node\Name('\cebe\openapi\Reader'), new Node\Name('readFromJson'), [new Node\Scalar\String_(json_encode($requestBodyContent->schema->getSerializableData())), new Node\Scalar\String_('\cebe\openapi\spec\Schema')])), ] - ) + )) + ); + break; + } + } + + $createRequestMethod->addStmt( + new Node\Stmt\Return_( + new Node\Expr\New_( + new Node\Name( + '\\' . Request::class + ), + $requestParameters ) ) ); + $class->addStmt( - $factory->method('validateResponse') + $createRequestMethod + ); + $cases = []; + $returnType = []; + foreach ($operation->responses as $code => $spec) { + $contentTypeCases = []; + foreach ($spec->content as $contentType => $contentTypeSchema) { + $returnType[] = $object = '\\' . $rootNamespace . 'Schema\\' . $schemaClassNameMap[spl_object_hash($contentTypeSchema->schema)]; + $ctc = new Node\Stmt\Case_( + new Node\Scalar\String_($contentType), + [ + new Node\Stmt\Expression(new Node\Expr\MethodCall( + new Node\Expr\PropertyFetch( + new Node\Expr\Variable('this'), + 'responseSchemaValidator' + ), + new Node\Name('validate'), + [ + new Node\Arg(new Node\Expr\Variable('body')), + new Node\Arg(new Node\Expr\StaticCall(new Node\Name('\cebe\openapi\Reader'), new Node\Name('readFromJson'), [new Node\Scalar\String_(json_encode($contentTypeSchema->schema->getSerializableData())), new Node\Scalar\String_('\cebe\openapi\spec\Schema')])), + ] + )), + new Node\Stmt\Return_(new Node\Expr\MethodCall( + new Node\Expr\Variable('hydrator'), + new Node\Name('hydrate'), + [ + new Node\Arg(new Node\Scalar\String_($object)), + new Node\Arg(new Node\Expr\Variable('body')), + ] + )), +// new Node\Stmt\Break_() + ] + ); + $contentTypeCases[] = $ctc; +// $ctc->setDocComment(new Doc('/**' . @var_export($contentTypeSchema->getSerializableData(), true) . '**/')); + } + $case = new Node\Stmt\Case_( + new Node\Scalar\LNumber($code), + [ + new Node\Stmt\Switch_( + new Node\Expr\Variable('contentType'), + $contentTypeCases + ), + new Node\Stmt\Break_() + ] + ); + $cases[] = $case; + $case->setDocComment(new Doc('/**' . $spec->description . '**/')); +// $case->setDocComment(new Doc('/**' . @var_export($spec->getSerializableData(), true) . '**/')); + } + $class->addStmt( + $factory->method('createResponse')->addParam( + $factory->param('response')->setType('\\' . ResponseInterface::class) + )->setReturnType( + new Node\UnionType(array_map(static fn (string $object): Node\Name => new Node\Name($object), array_unique($returnType))) + )->addStmt( + new Node\Expr\Assign(new Node\Expr\Variable('contentType'), new Node\Expr\MethodCall(new Node\Expr\Variable('response'), 'getHeaderLine', [new Arg(new Node\Scalar\String_('Content-Type'))])) + )->addStmt( + new Node\Expr\Assign(new Node\Expr\Variable('body'), new Node\Expr\FuncCall(new Node\Name('json_decode'), [new Node\Expr\MethodCall(new Node\Expr\MethodCall(new Node\Expr\Variable('response'), 'getBody'), 'getContents'), new Node\Expr\ConstFetch(new Node\Name('true'))])) + )->addStmt( + new Node\Expr\Assign(new Node\Expr\Variable('hydrator'), new Node\Expr\New_(new Node\Name('\\' . Hydrator::class))) + )->addStmt( + new Node\Stmt\Switch_( + new Node\Expr\MethodCall(new Node\Expr\Variable('response'), 'getStatusCode'), + $cases + ) + )->addStmt( + new Node\Stmt\Throw_( + new Node\Expr\New_( + new Node\Name('\\' . \RuntimeException::class), + [ + new Arg(new Node\Scalar\String_('Unable to find matching reponse code and content type')) + ] + ) + ) + ) ); yield new File($namespace . '\\' . $className, $stmt->addStmt($class)->getNode());