Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand All @@ -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();
Expand All @@ -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]
Expand Down
79 changes: 59 additions & 20 deletions src/ValidatorBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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),
};
}

/**
Expand All @@ -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
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}
Expand Down
21 changes: 15 additions & 6 deletions tests/ValidatorBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'],
];
}

Expand All @@ -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(
Expand All @@ -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);
Expand Down