From a3f33ac02e7e416173bbe3c002bf2892d796be21 Mon Sep 17 00:00:00 2001 From: osteel Date: Thu, 4 Dec 2025 10:40:46 +0000 Subject: [PATCH] fixed cache support and refactored public API --- README.md | 16 +++++-- src/ValidatorBuilder.php | 79 +++++++++++++++++++++++++--------- tests/ValidatorBuilderTest.php | 21 ++++++--- 3 files changed, 86 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 647de79..af0be1a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ It converts HttpFoundation request and response objects to PSR-7 messages using ## Installation > [!NOTE] -> This package is mostly intended to be used as part of an API test suite. +> This package supports PHP 8.0 and above. Via Composer: @@ -40,6 +40,9 @@ composer require --dev osteel/openapi-httpfoundation-testing ## Usage +> [!NOTE] +> This package is mostly intended to be used as part of an API test suite. + Import the builder class: ```php @@ -49,7 +52,7 @@ use Osteel\OpenApi\Testing\ValidatorBuilder; Use the builder to create a [`\Osteel\OpenApi\Testing\Validator`](/src/Validator.php) object, using one of the available factory methods for YAML or JSON: ```php -// From a local file or URL: +// From a local file: $validator = ValidatorBuilder::fromYamlFile($yamlFile)->getValidator(); $validator = ValidatorBuilder::fromJsonFile($jsonFile)->getValidator(); @@ -59,10 +62,15 @@ $validator = ValidatorBuilder::fromJsonFile($jsonFile)->getValidator(); $validator = ValidatorBuilder::fromYamlString($yamlString)->getValidator(); $validator = ValidatorBuilder::fromJsonString($jsonString)->getValidator(); +// From a URL: + +$validator = ValidatorBuilder::fromYamlUrl($yamlUrl)->getValidator(); +$validator = ValidatorBuilder::fromJsonUrl($jsonUrl)->getValidator(); + // Automatic detection (slower): -$validator = ValidatorBuilder::fromYaml($yamlFileOrString)->getValidator(); -$validator = ValidatorBuilder::fromJson($jsonFileOrString)->getValidator(); +$validator = ValidatorBuilder::fromYaml($yamlFileOrStringOrUrl)->getValidator(); +$validator = ValidatorBuilder::fromJson($jsonFileOrStringOrUrl)->getValidator(); ``` > [!TIP] diff --git a/src/ValidatorBuilder.php b/src/ValidatorBuilder.php index aa52482..0272c18 100644 --- a/src/ValidatorBuilder.php +++ b/src/ValidatorBuilder.php @@ -4,14 +4,13 @@ namespace Osteel\OpenApi\Testing; -use cebe\openapi\Reader; -use cebe\openapi\ReferenceContext; use InvalidArgumentException; use League\OpenAPIValidation\PSR7\ValidatorBuilder as BaseValidatorBuilder; use Osteel\OpenApi\Testing\Adapters\HttpFoundationAdapter; use Osteel\OpenApi\Testing\Adapters\MessageAdapterInterface; use Osteel\OpenApi\Testing\Cache\CacheAdapterInterface; use Osteel\OpenApi\Testing\Cache\Psr16Adapter; +use RuntimeException; /** * This class creates Validator objects based on OpenAPI definitions. @@ -35,9 +34,11 @@ public function __construct(private BaseValidatorBuilder $validatorBuilder) */ public static function fromYaml(string $definition): ValidatorBuilderInterface { - return self::isUrl($definition) || is_file($definition) - ? self::fromYamlFile($definition) - : self::fromYamlString($definition); + return match (true) { + self::isUrl($definition) => self::fromYamlUrl($definition), + is_file($definition) => self::fromYamlFile($definition), + default => self::fromYamlString($definition), + }; } /** @@ -47,9 +48,11 @@ public static function fromYaml(string $definition): ValidatorBuilderInterface */ public static function fromJson(string $definition): ValidatorBuilderInterface { - return self::isUrl($definition) || is_file($definition) - ? self::fromJsonFile($definition) - : self::fromJsonString($definition); + return match (true) { + self::isUrl($definition) => self::fromJsonUrl($definition), + is_file($definition) => self::fromJsonFile($definition), + default => self::fromJsonString($definition), + }; } private static function isUrl(string $value): bool @@ -70,7 +73,7 @@ private static function isUrl(string $value): bool */ public static function fromYamlFile(string $definition): ValidatorBuilderInterface { - return self::fromMethod('readFromYamlFile', $definition); + return self::fromMethod('fromYamlFile', $definition); } /** @@ -80,7 +83,7 @@ public static function fromYamlFile(string $definition): ValidatorBuilderInterfa */ public static function fromJsonFile(string $definition): ValidatorBuilderInterface { - return self::fromMethod('readFromJsonFile', $definition); + return self::fromMethod('fromJsonFile', $definition); } /** @@ -90,7 +93,7 @@ public static function fromJsonFile(string $definition): ValidatorBuilderInterfa */ public static function fromYamlString(string $definition): ValidatorBuilderInterface { - return self::fromMethod('readFromYaml', $definition, resolveReferences: true); + return self::fromMethod('fromYaml', $definition); } /** @@ -100,23 +103,59 @@ public static function fromYamlString(string $definition): ValidatorBuilderInter */ public static function fromJsonString(string $definition): ValidatorBuilderInterface { - return self::fromMethod('readFromJson', $definition, resolveReferences: true); + return self::fromMethod('fromJson', $definition); } /** - * Create a Validator object based on an OpenAPI definition. + * @inheritDoc * - * @param string $method the ValidatorBuilder object's method to use - * @param string $definition the OpenAPI definition - * @param bool $resolveReferences whether to resolve references in the definition + * @param string $definition the OpenAPI definition's URL + * + * @throws InvalidArgumentException if the URL is invalid + * @throws RuntimeException if the content of the URL cannot be read */ - private static function fromMethod(string $method, string $definition, bool $resolveReferences = false): ValidatorBuilderInterface + public static function fromYamlUrl(string $definition): ValidatorBuilderInterface { - $specObject = Reader::{$method}($definition); + return self::fromMethod('fromYaml', self::getUrlContent($definition)); + } - $resolveReferences && $specObject->resolveReferences(new ReferenceContext($specObject, '/')); + /** + * @inheritDoc + * + * @param string $definition the OpenAPI definition's URL + * + * @throws InvalidArgumentException if the URL is invalid + * @throws RuntimeException if the content of the URL cannot be read + */ + public static function fromJsonUrl(string $definition): ValidatorBuilderInterface + { + return self::fromMethod('fromJson', self::getUrlContent($definition)); + } - $builder = (new BaseValidatorBuilder())->fromSchema($specObject); + /** + * @throws InvalidArgumentException if the URL is invalid + * @throws RuntimeException if the content of the URL cannot be read + */ + private static function getUrlContent(string $url): string + { + self::isUrl($url) || throw new InvalidArgumentException(sprintf('Invalid URL: %s', $url)); + + if (($content = file_get_contents($url)) === false) { + throw new RuntimeException(sprintf('Failed to read URL %s', $url)); + } + + return $content; + } + + /** + * Create a Validator object based on an OpenAPI definition. + * + * @param string $method the ValidatorBuilder object's method to use + * @param string $definition the OpenAPI definition + */ + private static function fromMethod(string $method, string $definition): ValidatorBuilderInterface + { + $builder = (new BaseValidatorBuilder())->{$method}($definition); return new ValidatorBuilder($builder); } diff --git a/tests/ValidatorBuilderTest.php b/tests/ValidatorBuilderTest.php index d51c636..3520e1f 100644 --- a/tests/ValidatorBuilderTest.php +++ b/tests/ValidatorBuilderTest.php @@ -9,6 +9,7 @@ use Osteel\OpenApi\Testing\Cache\CacheAdapterInterface; use Osteel\OpenApi\Testing\Validator; use Osteel\OpenApi\Testing\ValidatorBuilder; +use Psr\SimpleCache\CacheInterface; use stdClass; class ValidatorBuilderTest extends TestCase @@ -17,17 +18,17 @@ public function definitionProvider(): array { return [ ['fromYaml', self::$yamlDefinition], - ['fromYaml', 'https://raw.githubusercontent.com/osteel/openapi-httpfoundation-testing/refs/heads/main/tests/stubs/example.yaml'], ['fromYaml', file_get_contents(self::$yamlDefinition)], + ['fromYaml', 'https://raw.githubusercontent.com/osteel/openapi-httpfoundation-testing/refs/heads/main/tests/stubs/example.yaml'], ['fromYamlFile', self::$yamlDefinition], - ['fromYamlFile', 'https://raw.githubusercontent.com/osteel/openapi-httpfoundation-testing/refs/heads/main/tests/stubs/example.yaml'], ['fromYamlString', file_get_contents(self::$yamlDefinition)], + ['fromYamlUrl', 'https://raw.githubusercontent.com/osteel/openapi-httpfoundation-testing/refs/heads/main/tests/stubs/example.yaml'], ['fromJson', self::$jsonDefinition], - ['fromJson', 'https://raw.githubusercontent.com/osteel/openapi-httpfoundation-testing/refs/heads/main/tests/stubs/example.json'], ['fromJson', file_get_contents(self::$jsonDefinition)], + ['fromJson', 'https://raw.githubusercontent.com/osteel/openapi-httpfoundation-testing/refs/heads/main/tests/stubs/example.json'], ['fromJsonFile', self::$jsonDefinition], - ['fromJsonFile', 'https://raw.githubusercontent.com/osteel/openapi-httpfoundation-testing/refs/heads/main/tests/stubs/example.json'], ['fromJsonString', file_get_contents(self::$jsonDefinition)], + ['fromJsonUrl', 'https://raw.githubusercontent.com/osteel/openapi-httpfoundation-testing/refs/heads/main/tests/stubs/example.json'], ]; } @@ -46,7 +47,15 @@ public function test_it_builds_a_validator(string $method, string $definition) $this->assertTrue($result->get($response, static::PATH)); } - public function test_it_does_not_set_the_adapter_because_its_type_is_invalid() + /** @dataProvider definitionProvider */ + public function test_it_sets_the_cache(string $method, string $definition) + { + ValidatorBuilder::$method($definition)->setCache($this->createMock(CacheInterface::class))->getValidator(); + + $this->addToAssertionCount(1); + } + + public function test_it_does_not_set_the_message_adapter_because_its_type_is_invalid() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage(sprintf( @@ -58,7 +67,7 @@ public function test_it_does_not_set_the_adapter_because_its_type_is_invalid() ValidatorBuilder::fromYaml(self::$yamlDefinition)->setMessageAdapter(stdClass::class); } - public function test_it_sets_the_adapter() + public function test_it_sets_the_message_adapter() { ValidatorBuilder::fromYaml(self::$yamlDefinition) ->setMessageAdapter($this->createMock(MessageAdapterInterface::class)::class);