Skip to content

Commit 68fd013

Browse files
committed
Add AbstractObjectSchema, cause ObjectSchema and AssocSchema are nearly identical
1 parent 9e76321 commit 68fd013

File tree

3 files changed

+192
-315
lines changed

3 files changed

+192
-315
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Chubbyphp\Parsing\Schema;
6+
7+
use Chubbyphp\Parsing\Error;
8+
use Chubbyphp\Parsing\Errors;
9+
use Chubbyphp\Parsing\ErrorsException;
10+
11+
abstract class AbstractObjectSchema extends AbstractSchema implements ObjectSchemaInterface
12+
{
13+
public const string ERROR_TYPE_CODE = 'abstract_object.type';
14+
public const string ERROR_TYPE_TEMPLATE = 'Type should be "array|\stdClass|\Traversable", {{given}} given';
15+
16+
public const string ERROR_UNKNOWN_FIELD_CODE = 'abstract_object.unknownField';
17+
public const string ERROR_UNKNOWN_FIELD_TEMPLATE = 'Unknown field {{fieldName}}';
18+
19+
/**
20+
* @var array<string, SchemaInterface>
21+
*/
22+
private readonly array $fieldToSchema;
23+
24+
/**
25+
* @var null|array<string>
26+
*/
27+
private ?array $strict = null;
28+
29+
/**
30+
* @var null|array<string>
31+
*/
32+
private ?array $optional = null;
33+
34+
/**
35+
* @param array<mixed, mixed> $fieldToSchema
36+
*/
37+
public function __construct(array $fieldToSchema)
38+
{
39+
$typeCheckedFieldToSchema = [];
40+
41+
foreach ($fieldToSchema as $fieldName => $fieldSchema) {
42+
if (!\is_string($fieldName)) {
43+
throw new \InvalidArgumentException(
44+
\sprintf(
45+
'Argument #1 name #%s ($fieldToSchema) must be of type string, %s given',
46+
$fieldName,
47+
$this->getDataType($fieldName)
48+
)
49+
);
50+
}
51+
52+
if (!$fieldSchema instanceof SchemaInterface) {
53+
throw new \InvalidArgumentException(
54+
\sprintf(
55+
'Argument #1 value of #%s ($fieldToSchema) must be of type %s, %s given',
56+
$fieldName,
57+
SchemaInterface::class,
58+
$this->getDataType($fieldSchema)
59+
)
60+
);
61+
}
62+
63+
$typeCheckedFieldToSchema[$fieldName] = $fieldSchema;
64+
}
65+
66+
$this->fieldToSchema = $typeCheckedFieldToSchema;
67+
}
68+
69+
final public function parse(mixed $input): mixed
70+
{
71+
if ($input instanceof \stdClass || $input instanceof \Traversable) {
72+
$input = (array) $input;
73+
}
74+
75+
if ($input instanceof \JsonSerializable) {
76+
$input = $input->jsonSerialize();
77+
}
78+
79+
try {
80+
$input = $this->dispatchPreParses($input);
81+
82+
if (null === $input && $this->nullable) {
83+
return null;
84+
}
85+
86+
if (!\is_array($input)) {
87+
throw new ErrorsException(
88+
new Error(
89+
static::ERROR_TYPE_CODE,
90+
static::ERROR_TYPE_TEMPLATE,
91+
['given' => $this->getDataType($input)]
92+
)
93+
);
94+
}
95+
96+
/** @var array<string, mixed> $input */
97+
$childrenErrors = new Errors();
98+
99+
$this->unknownFields($input, $childrenErrors);
100+
101+
$output = $this->parseFields($input, $childrenErrors);
102+
103+
if ($childrenErrors->has()) {
104+
throw new ErrorsException($childrenErrors);
105+
}
106+
107+
return $this->dispatchPostParses($output);
108+
} catch (ErrorsException $e) {
109+
if ($this->catch) {
110+
return ($this->catch)($input, $e);
111+
}
112+
113+
throw $e;
114+
}
115+
}
116+
117+
/**
118+
* @return array<string, SchemaInterface>
119+
*/
120+
final public function getFieldToSchema(): array
121+
{
122+
return $this->fieldToSchema;
123+
}
124+
125+
final public function getFieldSchema(string $field): ?SchemaInterface
126+
{
127+
return $this->fieldToSchema[$field] ?? null;
128+
}
129+
130+
final public function strict(array $strict = []): static
131+
{
132+
$clone = clone $this;
133+
$clone->strict = $strict;
134+
135+
return $clone;
136+
}
137+
138+
/**
139+
* @param array<string> $optional
140+
*/
141+
final public function optional(array $optional = []): static
142+
{
143+
$clone = clone $this;
144+
$clone->optional = $optional;
145+
146+
return $clone;
147+
}
148+
149+
/**
150+
* @param array<string, mixed> $input
151+
*/
152+
abstract protected function parseFields(array $input, Errors $childrenErrors): mixed;
153+
154+
/**
155+
* @param array<string, mixed> $input
156+
*/
157+
final protected function skip(array $input, string $fieldName): bool
158+
{
159+
return !\array_key_exists($fieldName, $input)
160+
&& \is_array($this->optional)
161+
&& \in_array($fieldName, $this->optional, true);
162+
}
163+
164+
/**
165+
* @param array<string, mixed> $input
166+
*/
167+
private function unknownFields(array $input, Errors $childrenErrors): void
168+
{
169+
if (null === $this->strict) {
170+
return;
171+
}
172+
173+
foreach (array_keys($input) as $fieldName) {
174+
if (!\in_array($fieldName, $this->strict, true) && !isset($this->fieldToSchema[$fieldName])) {
175+
$childrenErrors->add(new Error(
176+
static::ERROR_UNKNOWN_FIELD_CODE,
177+
static::ERROR_UNKNOWN_FIELD_TEMPLATE,
178+
['fieldName' => $fieldName]
179+
), $fieldName);
180+
}
181+
}
182+
}
183+
}

src/Schema/AssocSchema.php

Lines changed: 4 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -4,183 +4,26 @@
44

55
namespace Chubbyphp\Parsing\Schema;
66

7-
use Chubbyphp\Parsing\Error;
87
use Chubbyphp\Parsing\Errors;
98
use Chubbyphp\Parsing\ErrorsException;
109

11-
final class AssocSchema extends AbstractSchema implements ObjectSchemaInterface
10+
final class AssocSchema extends AbstractObjectSchema implements ObjectSchemaInterface
1211
{
1312
public const string ERROR_TYPE_CODE = 'assoc.type';
14-
public const string ERROR_TYPE_TEMPLATE = 'Type should be "array|\stdClass|\Traversable", {{given}} given';
15-
1613
public const string ERROR_UNKNOWN_FIELD_CODE = 'assoc.unknownField';
17-
public const string ERROR_UNKNOWN_FIELD_TEMPLATE = 'Unknown field {{fieldName}}';
18-
19-
/**
20-
* @var array<string, SchemaInterface>
21-
*/
22-
private array $fieldToSchema = [];
23-
24-
/**
25-
* @var null|array<string>
26-
*/
27-
private ?array $strict = null;
28-
29-
/**
30-
* @var null|array<string>
31-
*/
32-
private ?array $optional = null;
33-
34-
/**
35-
* @param array<mixed, mixed> $fieldToSchema
36-
*/
37-
public function __construct(array $fieldToSchema)
38-
{
39-
foreach ($fieldToSchema as $fieldName => $fieldSchema) {
40-
if (!\is_string($fieldName)) {
41-
throw new \InvalidArgumentException(
42-
\sprintf(
43-
'Argument #1 name #%s ($fieldToSchema) must be of type string, %s given',
44-
$fieldName,
45-
$this->getDataType($fieldName)
46-
)
47-
);
48-
}
49-
50-
if (!$fieldSchema instanceof SchemaInterface) {
51-
throw new \InvalidArgumentException(
52-
\sprintf(
53-
'Argument #1 value of #%s ($fieldToSchema) must be of type %s, %s given',
54-
$fieldName,
55-
SchemaInterface::class,
56-
$this->getDataType($fieldSchema)
57-
)
58-
);
59-
}
60-
61-
$this->fieldToSchema[$fieldName] = $fieldSchema;
62-
}
63-
}
64-
65-
public function parse(mixed $input): mixed
66-
{
67-
if ($input instanceof \stdClass || $input instanceof \Traversable) {
68-
$input = (array) $input;
69-
}
70-
71-
if ($input instanceof \JsonSerializable) {
72-
$input = $input->jsonSerialize();
73-
}
74-
75-
try {
76-
$input = $this->dispatchPreParses($input);
77-
78-
if (null === $input && $this->nullable) {
79-
return null;
80-
}
81-
82-
if (!\is_array($input)) {
83-
throw new ErrorsException(
84-
new Error(
85-
self::ERROR_TYPE_CODE,
86-
self::ERROR_TYPE_TEMPLATE,
87-
['given' => $this->getDataType($input)]
88-
)
89-
);
90-
}
91-
92-
/** @var array<string, mixed> $input */
93-
$childrenErrors = new Errors();
94-
95-
$this->unknownFields($input, $childrenErrors);
96-
97-
$output = $this->parseFields($input, $childrenErrors);
98-
99-
if ($childrenErrors->has()) {
100-
throw new ErrorsException($childrenErrors);
101-
}
102-
103-
return $this->dispatchPostParses($output);
104-
} catch (ErrorsException $e) {
105-
if ($this->catch) {
106-
return ($this->catch)($input, $e);
107-
}
108-
109-
throw $e;
110-
}
111-
}
112-
113-
/**
114-
* @return array<string, SchemaInterface>
115-
*/
116-
public function getFieldToSchema(): array
117-
{
118-
return $this->fieldToSchema;
119-
}
120-
121-
public function getFieldSchema(string $field): ?SchemaInterface
122-
{
123-
return $this->fieldToSchema[$field] ?? null;
124-
}
125-
126-
/**
127-
* @param array<string> $optional
128-
*/
129-
public function optional(array $optional = []): static
130-
{
131-
$clone = clone $this;
132-
$clone->optional = $optional;
133-
134-
return $clone;
135-
}
136-
137-
/**
138-
* @param array<string> $strict
139-
*/
140-
public function strict(array $strict = []): static
141-
{
142-
$clone = clone $this;
143-
$clone->strict = $strict;
144-
145-
return $clone;
146-
}
147-
148-
/**
149-
* @param array<string, mixed> $input
150-
*/
151-
private function unknownFields(array $input, Errors $childrenErrors): void
152-
{
153-
if (null === $this->strict) {
154-
return;
155-
}
156-
157-
foreach (array_keys($input) as $fieldName) {
158-
if (!\in_array($fieldName, $this->strict, true) && !isset($this->fieldToSchema[$fieldName])) {
159-
$childrenErrors->add(new Error(
160-
self::ERROR_UNKNOWN_FIELD_CODE,
161-
self::ERROR_UNKNOWN_FIELD_TEMPLATE,
162-
['fieldName' => $fieldName]
163-
), $fieldName);
164-
}
165-
}
166-
}
16714

16815
/**
16916
* @param array<string, mixed> $input
17017
*
17118
* @return array<string, mixed>
17219
*/
173-
private function parseFields(array $input, Errors $childrenErrors): array
20+
protected function parseFields(array $input, Errors $childrenErrors): array
17421
{
17522
$output = [];
17623

177-
foreach ($this->fieldToSchema as $fieldName => $fieldSchema) {
24+
foreach ($this->getFieldToSchema() as $fieldName => $fieldSchema) {
17825
try {
179-
if (
180-
!\array_key_exists($fieldName, $input)
181-
&& \is_array($this->optional)
182-
&& \in_array($fieldName, $this->optional, true)
183-
) {
26+
if ($this->skip($input, $fieldName)) {
18427
continue;
18528
}
18629

0 commit comments

Comments
 (0)