Skip to content

Commit 9e5e309

Browse files
authored
Merge pull request #3 from skybluesofa/Laravel-10
Laravel 10 Updates
2 parents a84c84d + a0c1231 commit 9e5e309

File tree

8 files changed

+196
-55
lines changed

8 files changed

+196
-55
lines changed

README.md

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,124 @@ This library builds on the _outstanding_ JsonSchema validator [opis/json-schema]
77

88
The entire intent of this library is to make JsonSchema feel like a first class citizen in a Laravel project.
99

10-
It adds a new config file, `config/json-schema.php` to configure your root directory for self-hosted schema files.
10+
- Adds a new config file, `config/json-schema.php`, to configure your root directory for self-hosted schema files.
11+
- Adds the `SchemaValidator` facade that can be used to instantiate the validator with appropriate loaders.
12+
- Adds new PhpUnit assertions in the `Carsdotcom\JsonSchemaValidation\Traits\JsonSchemaAssertions` trait, such as validating that a mixed item validates for a specific schema.
13+
- Most interestingly, it lets you use JsonSchema to validate incoming Requests bodies, and/or validate your own outgoing response bodies, all using JsonSchema schemas that you can then export into OpenAPI documentation.
1114

12-
It adds a Facade, `SchemaValidator` that can be used to instantiate the validator with appropriate loaders.
15+
## Laravel Version Compatibility
1316

14-
It adds new PhpUnit assertions in `JsonSchemaAssertions` like validating that a mixed item validates for a specific schema.
17+
This package supports Laravel `v9` and `v10`
1518

16-
Most interestingly, it lets you use JsonSchema to validate incoming Requests bodies, and/or validate your own outgoing response bodies, all using JsonSchema schemas that you can then export into OpenAPI documentation.
19+
## Installation
1720

18-
## Coming Soon
21+
```
22+
composer require carsdotcom/laravel-json-schema
23+
```
1924

20-
More documentation will be coming soon, including some more projects that build on this, including Guzzle outgoing- and incoming-body validation, and a new kind of Laravel Model that persists to JSON instead of to a relational database.
25+
## Using Laravel JSON Schema
26+
27+
### Setup
28+
29+
#### Config File
30+
Copy the `json-schema.php` file from the `vendor/carsdotcom/laravel-json-schema/config` folder to your application's `config` folder.
31+
32+
#### Schema Storage
33+
1. Create a `Schemas` folder under your application root folder, such as `app/Schemas`.
34+
2. Create a new storage disk under the `disks` key within your application's `config/filesystem.php` file:
35+
36+
```
37+
'disks' => [
38+
'schemas' => [
39+
'driver' => 'local',
40+
'root' => app_path('app/Schemas'), // must match the 'config.json-schema.local_base_prefix' value
41+
]
42+
]
43+
```
44+
3. Add your schema files to the `app/Schemas` folder. You may create subfolders to keep things organized.
45+
46+
#### Generate Enum Schemas
47+
48+
_This is an optional step, but can be super helpful._
49+
50+
Note: Enums must be created either as a built-in PHP `enum` object or a `MyCLabs\Enum\Enum` class.
51+
52+
1. Add `use Carsdotcom\JsonSchemaValidation\Traits\GeneratesSchemaTrait;` to the declarations in the Enum.
53+
2. Add a `SCHEMA` constant to the enum. It's value will be the relative path to your schema file, such as: `const SCHEMA = '/Acme/Enums/item_type.json';`
54+
3. Run the `schema:generate` Artisan command.
55+
56+
## Validating JSON Data Against a Schema
57+
58+
For this example, we'll be using these objects:
59+
60+
### Hosted JSON Schema File
61+
62+
This is assumed to be stored in your `app/Schemas` folder as `Product.json`.
63+
64+
```
65+
{
66+
"$schema": "https://json-schema.org/draft/2020-12/schema",
67+
"$id": "https://example.com/product.schema.json",
68+
"title": "Product",
69+
"description": "A product from Acme's catalog",
70+
"type": "object",
71+
"properties": {
72+
"productId": {
73+
"description": "The unique identifier for a product",
74+
"type": "integer"
75+
},
76+
"productName": {
77+
"description": "Name of the product",
78+
"type": "string"
79+
},
80+
"price": {
81+
"description": "The price of the product",
82+
"type": "number",
83+
"exclusiveMinimum": 0
84+
}
85+
},
86+
"required": [ "productId", "productName", "price" ]
87+
}
88+
```
89+
90+
### JSON Data to be Validated
91+
92+
```
93+
{
94+
"productId": 1,
95+
"productName": "An ice sculpture",
96+
"price": 12.50
97+
}
98+
```
99+
100+
### Application Code for Validation
101+
102+
```
103+
use Carsdotcom\JsonSchemaValidation\SchemaValidator;
104+
105+
SchemaValidator::validateOrThrow($json, 'Product.json');
106+
```
107+
108+
## Additional Functionality
109+
110+
### Getting the Content of a Schema File
111+
112+
```
113+
use Carsdotcom\JsonSchemaValidation\SchemaValidator;
114+
115+
SchemaValidator::getSchemaContents('Product.json');
116+
```
117+
118+
### Storing a Schema File at a Specific Location
119+
120+
```
121+
use Carsdotcom\JsonSchemaValidation\SchemaValidator;
122+
123+
SchemaValidator::getSchemaContents('Customer.json', $jsonSchemaForCustomer);
124+
```
125+
126+
### Adding an In-Memory Schema File
127+
128+
```
129+
$schemaKey = (new SchemaValidatorService)->registerRawSchema($jsonSchema);
130+
```

app/SchemaValidatorService.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace Carsdotcom\JsonSchemaValidation;
1010

1111
use Carsdotcom\JsonSchemaValidation\Exceptions\JsonSchemaValidationException;
12+
use DomainException;
1213
use Illuminate\Contracts\Filesystem\FileNotFoundException;
1314
use Illuminate\Support\Facades\Log;
1415
use Illuminate\Support\Facades\Storage;
@@ -31,6 +32,12 @@ class SchemaValidatorService
3132
protected function getValidator(): Validator
3233
{
3334
if (!$this->validator) {
35+
if (empty(config('json-schema.base_url'))) {
36+
throw new DomainException(
37+
'Laravel JSON Schema base_url is empty. This can be updated in /config/json-schema.php'
38+
);
39+
}
40+
3441
$this->validator = new Validator();
3542
$this->validator->loader()->setBaseUri(Uri::parse(config('json-schema.base_url')));
3643
$this->validator->resolver()->registerPrefix(

app/Traits/GeneratesSchemaTrait.php

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
use Carsdotcom\JsonSchemaValidation\Helpers\FriendlyClassName;
1313
use Carsdotcom\JsonSchemaValidation\SchemaValidator;
14-
use MyCLabs\Enum\Enum;
14+
use DomainException;
15+
use Illuminate\Support\Facades\Log;
16+
use ReflectionClass;
17+
use UnitEnum;
1518

1619
trait GeneratesSchemaTrait
1720
{
@@ -21,39 +24,74 @@ trait GeneratesSchemaTrait
2124
*/
2225
public static function generateSchema(): void
2326
{
24-
if (!is_a(static::class, Enum::class, true)) {
25-
throw new \DomainException(static::class . ' must descend from ' . Enum::class . ' to generate a schema');
27+
if (! static::isPhpBuiltInEnum() && ! static::isMyCLabsEnum()) {
28+
throw new DomainException(
29+
static::class.' must descend from either '.UnitEnum::class.' or '.static::getMyCLabsClass().' to generate a schema'
30+
);
2631
}
2732

28-
if (!defined(static::class . '::SCHEMA')) {
29-
throw new \DomainException(static::class . " can't generate a schema, SCHEMA class constant is undefined");
33+
if (! defined(static::class.'::SCHEMA')) {
34+
throw new DomainException(
35+
static::class." can't generate a schema; the SCHEMA class constant is undefined"
36+
);
3037
}
3138

3239
$schema = [
3340
'$schema' => 'http://json-schema.org/draft-07/schema#',
3441
'title' => (new FriendlyClassName())(static::class),
35-
'description' =>
36-
'Enumerated values for ' .
37-
(new FriendlyClassName())(static::class) .
38-
'. Note this schema is automatically generated from ' .
39-
static::class .
42+
'description' => 'Enumerated values for '.
43+
(new FriendlyClassName())(static::class).
44+
'. Note this schema is automatically generated from '.
45+
static::class.
4046
', DO NOT modify by hand.',
41-
'enum' => array_values(array_unique(static::toArray())),
47+
'enum' => static::generateEnumArray(),
4248
'type' => 'string',
4349
];
4450

45-
SchemaValidator::putSchemaContents(static::SCHEMA, $schema);
51+
$reflection = new ReflectionClass(static::class);
52+
$schemaPath = $reflection->getConstant('SCHEMA');
53+
if (SchemaValidator::putSchemaContents($schemaPath, $schema)) {
54+
Log::info('Saving Schema', ['schema filename' => $schemaPath, 'success' => true]);
55+
} else {
56+
Log::error('Saving Schema', ['schema filename' => $schemaPath, 'success' => false]);
57+
}
58+
4659
}
4760

48-
/**
49-
* Returns all possible values as an array
50-
*
51-
* @return array Constant name in key, constant value in value
52-
*/
53-
public static function toArray(): array
61+
public static function generateEnumArray(): array
62+
{
63+
if (static::isMyCLabsEnum()) {
64+
return static::generateMyCLabsEnumArray();
65+
}
66+
67+
return static::generatePhpBuiltInEnumArray();
68+
}
69+
70+
protected static function generatePhpBuiltInEnumArray(): array
71+
{
72+
return array_column(self::cases(), 'value') ?: array_column(self::cases(), 'name');
73+
}
74+
75+
protected static function generateMyCLabsEnumArray(): array
5476
{
5577
$array = parent::toArray();
5678
unset($array['SCHEMA']);
57-
return $array;
79+
80+
return array_values(array_unique($array));
81+
}
82+
83+
protected static function isPhpBuiltInEnum(): bool
84+
{
85+
return is_a(static::class, UnitEnum::class, true);
86+
}
87+
88+
protected static function getMyCLabsClass(): string
89+
{
90+
return 'MyCLabs\Enum\Enum';
91+
}
92+
93+
protected static function isMyCLabsEnum(): bool
94+
{
95+
return is_a(static::class, static::getMyCLabsClass(), true);
5896
}
59-
}
97+
}

composer.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
"name": "carsdotcom/laravel-json-schema",
33
"type": "library",
44
"description": "Json Schema validation for Laravel projects",
5-
"keywords": ["library", "jsonschema", "laravel", "validation"],
5+
"keywords": [
6+
"library",
7+
"jsonschema",
8+
"laravel",
9+
"validation"
10+
],
611
"license": "MIT",
712
"require": {
8-
"laravel/framework": "^9.19",
13+
"laravel/framework": "^9.19 || ^10.0",
914
"opis/json-schema": "^2.3",
1015
"php": "^8.1"
1116
},
@@ -47,4 +52,4 @@
4752
},
4853
"minimum-stability": "dev",
4954
"prefer-stable": true
50-
}
55+
}

config/json-schema.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
| Base URL
77
|--------------------------------------------------------------------------
88
|
9-
| This is the base URL where schemas will be accessible.
9+
| This is an absolute base URL where schemas will be accessible.
1010
|
1111
| (URL our local schemas must be relative to)
1212
|
1313
*/
1414

15-
'base_url' => 'https://schemas.dealerinspire.com/online-shopper/',
15+
'base_url' => 'file://localhost',
1616

1717
/*
1818
|--------------------------------------------------------------------------
@@ -34,7 +34,7 @@
3434
|
3535
*/
3636

37-
'local_base_prefix_tests' => base_path('tests/schemas/'),
37+
'local_base_prefix_tests' => base_path('tests/Schemas/'),
3838

3939
/*
4040
|--------------------------------------------------------------------------

tests/BaseTestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class BaseTestCase extends TestCase
1515
protected function defineEnvironment($app)
1616
{
1717
// Setup default database to use sqlite :memory:
18-
$app['config']->set('json-schema.base_url', 'https://schemas.dealerinspire.com/online-shopper/');
18+
$app['config']->set('json-schema.base_url', 'http://localhost/');
1919
$app['config']->set('json-schema.local_base_prefix', dirname(__FILE__) . '/../tests/Schemas');
2020
$app['config']->set('json-schema.local_base_prefix_tests', dirname(__FILE__) . '/../tests/Schemas');
2121
$app['config']->set('database.connections.testbench', [

tests/Feature/SchemaValidatorServiceTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
class SchemaValidatorServiceTest extends BaseTestCase
2020
{
21-
public function testValidateUriSchema()
21+
public function testValidateUriSchema(): void
2222
{
2323
$data = Vehicle::factory()->make();
2424

@@ -54,13 +54,13 @@ public function testValidateStringSchema(): void
5454
* @param $schema
5555
* @dataProvider normalizeDataProvider
5656
*/
57-
public function testNormalizeData($data, $schema)
57+
public function testNormalizeData($data, $schema): void
5858
{
5959
$validator = new SchemaValidatorService();
6060
self::assertTrue($validator->validate($data, $schema));
6161
}
6262

63-
public function normalizeDataProvider()
63+
public function normalizeDataProvider(): array
6464
{
6565
return [
6666
'Collection becomes array' => [collect([1, 2, 3]), '{"type":"array","minItems":3}'],

tests/Unit/Traits/ValidatesWithJsonSchemaTest.php

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,6 @@
1717
*/
1818
class ValidatesWithJsonSchemaTest extends BaseTestCase
1919
{
20-
/**
21-
* Define environment setup.
22-
*
23-
* @param \Illuminate\Foundation\Application $app
24-
* @return void
25-
*/
26-
protected function defineEnvironment($app)
27-
{
28-
// Setup default database to use sqlite :memory:
29-
$app['config']->set('json-schema.base_url', 'https://schemas.dealerinspire.com/online-shopper/');
30-
$app['config']->set('json-schema.local_base_prefix', dirname(__FILE__) . '/../../../tests/Schemas');
31-
$app['config']->set('json-schema.local_base_prefix_tests', dirname(__FILE__) . '/../../../tests/Schemas');
32-
$app['config']->set('database.connections.testbench', [
33-
'driver' => 'sqlite',
34-
'database' => ':memory:',
35-
'prefix' => '',
36-
]);
37-
}
38-
3920
public function testValidateSucceedsImmediatelyIfSchemaUndefined(): void
4021
{
4122
$traitUser = new class {

0 commit comments

Comments
 (0)