Skip to content

Commit 51ba554

Browse files
committed
Add BooleanSchema
1 parent 33cd156 commit 51ba554

File tree

13 files changed

+343
-172
lines changed

13 files changed

+343
-172
lines changed

docs/object-simplifications.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# How The Reader Simplifies Your OpenAPI
2+
3+
Besides simply parsing your OpenAPI into `Validated` objects,
4+
the reader is designed to _simplify the developer experience_ of other OpenAPI tools.
5+
6+
## Standard Simplifications
7+
8+
### Never Worry About Uninitialized Properties
9+
Properties should be safe to access:
10+
All properties have a value.
11+
`null` represents an omitted field _if_ no other value can be safely assumed.
12+
13+
### Strong Typehints
14+
Data structures should be easily discoverable.
15+
All properties should have strong typehints.
16+
17+
## Opinionated Simplifications
18+
19+
## Narrow Typehints
20+
Typehints are narrowed, if and only if, it has no impact on expressiveness.
21+
22+
### String Metadata Is Always String
23+
24+
Optional metadata is expressed in two ways;
25+
1. There is data
26+
2. There is not
27+
28+
As such, the Reader structures metadata in two ways:
29+
1. A string containing non-whitespace characters
30+
2. An empty string `''`
31+
32+
When accessing string metadata only one check is necessary:
33+
34+
```php
35+
if ($metadata !== '') {
36+
// do something...
37+
}
38+
```
39+
40+
## Combined Fields
41+
Data is combined, if and only if, it has no impact on expressiveness.
42+
43+
### Maximum|Minimum are combined with ExclusiveMaximum|ExclusiveMinimum
44+
45+
The structure of numeric maximums and minimums vary between versions.
46+
However, both versions only express two things:
47+
- Is there a limit
48+
- Is it exclusive
49+
50+
As such the Reader combines the fields into:
51+
- A `Limit|null $maximum`.
52+
- A `Limit|null $minimum`.
53+
54+
Where A `Limit` has two properties:
55+
- `float|int $limit`
56+
- `bool $exclusive`

docs/validation-deviations.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Validation And How It Deviates From OpenAPI Specification
2+
This page documents where validation deviates from the OpenAPI Specification.
3+
4+
## Stricter Requirements
5+
It is stricter when it comes to providing an unambiguous specification.
6+
This helps to avoid confusion as well as simplify development for other OpenAPI tools.
7+
8+
### OperationId Is Required
9+
10+
Operations MUST have an [operationId](https://github.com/OAI/OpenAPI-Specification/blob/3.1.0/versions/3.0.3.md#fixed-fields-8).
11+
12+
`operationId` is a unique string used to identify an operation.
13+
By making it required, it serves as a _reliable_ method of identification.
14+
15+
### Query Strings Must Be Unambiguous
16+
17+
Operations MUST NOT use more than one _ambiguous parameter_.
18+
19+
In this context, an _ambiguous parameter_ is defined as being `in:query` with one of the following combinations:
20+
- `type:object` with `style:form` and `explode:true`
21+
- `type:object` or `type:array` with `style:spaceDelimited`
22+
- `type:object` or `type:array` with `style:pipeDelimited`
23+
24+
If everything else can be identified; then through a process of elimination, the ambiguous segment belongs to the _ambiguous parameter_
25+
26+
If an operation contains two or more _ambiguous parameters_, then there are multiple ways of interpreting the ambiguous segment.
27+
This ambiguity means the query string cannot be resolved deterministically.
28+
As such, it is not allowed.
29+
30+
## Looser Requirements
31+
32+
These requirements are looser than the OpenAPI Specification.
33+
Where the OpenAPI Specification would be invalid, the reader will add a warning to the `Validated` object.
34+
35+
### MultipleOf Can Be Negative
36+
37+
Normally `multipleOf` MUST be a positive non-zero number.
38+
39+
The Reader allows `multipleOf` to be any non-zero number.
40+
A multiple of a negative number is also a multiple of its absolute value.
41+
It's more confusing, but what is expressed is identical.
42+
43+
Therefore, if a negative value is given:
44+
- You will receive a Warning
45+
- The absolute value will be used
46+
47+
### AllOf, AnyOf and OneOf Can Be Empty
48+
49+
Normally, `allOf`, `anyOf` and `oneOf` MUST be non-empty.
50+
51+
The Reader allows them to be empty.
52+
53+
[] and null express basically the same thing, no value.
54+
By replacing null with [], we can narrow the typehint from `array|null` to `array`.
55+
Simplifies code using it.
56+
57+
### Required Can Be Empty
58+
59+
OpenAPI 3.0 states `required` MUST be non-empty.
60+
OpenAPI 3.1 allows `required` to be empty and defaults to empty if omitted.
61+
62+
The Reader always allows `required` to be empty and defaults to empty if omitted.
63+
This allows us to narrow the typehint for `required` to always being an array.
64+
65+
If an empty array is given:
66+
- If your API is using 3.0, you will receive a Warning
67+
- An empty array will be used
68+
69+
### Required Can Contain Duplicates
70+
71+
Normally `required` MUST contain unique values.
72+
73+
The Reader allows `required` to contain duplicates.
74+
75+
If a duplicate item is found:
76+
- You will receive a Warning
77+
- The duplicate will be removed.

docs/validation.md

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/ValueObject/Partial/Schema.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ public function __construct(
2020
* 3.0 https://datatracker.ietf.org/doc/html/draft-wright-json-schema-validation-00#autoid-11
2121
* 3.1 https://json-schema.org/draft/2020-12/json-schema-validation#name-validation-keywords-for-any
2222
*/
23-
/** @var null|string|array<string> */
24-
public null|string|array $type = null,
23+
/** @var string[]|string|null */
24+
public array|string|null $type = null,
2525
/** @var array<Value>|null */
2626
public array|null $enum = null,
2727
public Value|null $const = null,
@@ -81,7 +81,7 @@ public function __construct(
8181
public array|null $anyOf = null,
8282
/** @var array<Schema>|null */
8383
public array|null $oneOf = null,
84-
public Schema|null $not = null,
84+
public bool|Schema|null $not = null,
8585
/**
8686
* Keywords for applying subschemas conditionally
8787
* 3.0 https://datatracker.ietf.org/doc/html/draft-wright-json-schema-validation-00#section-5.22

src/ValueObject/Valid/Schema.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@
66

77
interface Schema
88
{
9-
109
}

src/ValueObject/Valid/V30/Keywords.php

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,10 @@ final class Keywords extends Validated implements Valid\Schema
6363

6464
public function __construct(
6565
Identifier $identifier,
66+
Valid\Warnings $warnings,
6667
Partial\Schema $schema
6768
) {
68-
parent::__construct($identifier);
69+
parent::__construct($identifier, $warnings);
6970

7071
$this->types = $this->validateTypes($schema->type, $schema->nullable);
7172
$this->enum = $this->reviewEnum($this->types, $schema->enum);
@@ -102,11 +103,11 @@ public function __construct(
102103
$this->properties = $this->validateProperties($schema->properties);
103104
$this->additionalProperties = new Schema(
104105
$this->appendedIdentifier('additionalProperties'),
105-
$schema->additionalProperties
106+
$schema->additionalProperties,
106107
);
107108

108109

109-
// make empty arrays instead of null
110+
//TODO throw ShouldBeBooleanSchema::false if allOf contains false schema
110111
$this->allOf = $this->validateSubSchemas('allOf', $schema->allOf);
111112
$this->anyOf = $this->validateSubSchemas('anyOf', $schema->anyOf);
112113
$this->oneOf = $this->validateSubSchemas('oneOf', $schema->oneOf);
@@ -297,7 +298,7 @@ private function validateMinMax(
297298
if (is_float($exclusiveMinMax) || is_integer($exclusiveMinMax)) {
298299
throw InvalidOpenAPI::numericExclusiveMinMaxIn30(
299300
$this->getIdentifier(),
300-
$keyword,
301+
$exclusiveKeyword,
301302
);
302303
}
303304

@@ -349,7 +350,7 @@ private function reviewItems(
349350

350351
return new Schema(
351352
$this->getIdentifier()->append('items'),
352-
$items ?? new Partial\Schema(),
353+
$items ?? true,
353354
);
354355
}
355356

@@ -393,12 +394,13 @@ private function validateSubSchemas(string $keyword, ?array $subSchemas): array
393394
}
394395

395396
$result = [];
396-
//TODO if all subschemas have a title, use that instead of their index
397397
foreach ($subSchemas as $index => $subSchema) {
398-
$result[] = new Schema(
399-
$this->getIdentifier()->append("$keyword($index)"),
400-
$subSchema
401-
);
398+
$identifier = $this->appendedIdentifier($keyword, sprintf(
399+
empty(trim($subSchema->title ?? '')) ? '%s' : '%2$s[%1$s]',
400+
$index,
401+
trim($subSchema->title ?? ''),
402+
));
403+
$result[] = new Schema($identifier, $subSchema);
402404
}
403405

404406
return $result;

src/ValueObject/Valid/V30/Schema.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ public function __construct(
2626
$this->value = $schema;
2727
} else {
2828
try {
29-
$this->value = new Keywords($identifier, $schema);
29+
$this->value = new Keywords(
30+
$this->getIdentifier(),
31+
$this->getWarnings(),
32+
$schema
33+
);
3034
} catch (SchemaShouldBeBoolean $e) {
3135
$this->addWarning($e->getMessage(), Valid\Warning::IMPOSSIBLE_SCHEMA);
3236
$this->value = $e->getCode() === SchemaShouldBeBoolean::ALWAYS_TRUE;

src/ValueObject/Valid/Validated.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Membrane\OpenAPIReader\ValueObject\Valid;
66

77
/**
8-
* A Validated object **may** make _opinionated optimizations_ to improve DX.
8+
* A Validated object **may** make _opinionated simplifications_ to improve DX.
99
* - It **may** change _appearance_ from its OpenAPI counterpart.
1010
* - It **must** express the same _intent_ as its OpenAPI counterpart.
1111
*/
@@ -15,8 +15,9 @@ abstract class Validated implements HasIdentifier, HasWarnings
1515

1616
public function __construct(
1717
private readonly Identifier $identifier,
18+
Warnings|null $warnings = null,
1819
) {
19-
$this->warnings = new Warnings($this->identifier);
20+
$this->warnings = $warnings ?? new Warnings($this->identifier);
2021
}
2122

2223
public function getIdentifier(): Identifier

src/ValueObject/Value.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,4 @@ public function __toString(): string
4545
return json_encode($this->value) ?:
4646
throw new RuntimeException('Failed to encode value');
4747
}
48-
4948
}

0 commit comments

Comments
 (0)