Skip to content

Commit 4bfe8b9

Browse files
author
Michael Hahn
authored
Merge pull request #41 from slackhq/better-defaults
If a default is specified, the property should not be optional in the shape
2 parents 7d946d3 + 7616399 commit 4bfe8b9

File tree

5 files changed

+173
-9
lines changed

5 files changed

+173
-9
lines changed

src/Codegen/Constraints/ObjectBuilder.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ protected function getCheckMethodCode(
395395
return $hb;
396396
}
397397

398+
<<__Memoize>>
398399
private function getDefaults(): dict<string, mixed> {
399400
$properties = $this->typed_schema['properties'] ?? null;
400401
$defaults = dict[];
@@ -450,6 +451,7 @@ private function codegenType(
450451
if ($property_classes is nonnull) {
451452
$required = $this->typed_schema['required'] ?? vec[];
452453
$additional_properties = $this->typed_schema['additionalProperties'] ?? null;
454+
$defaults = $this->getDefaults();
453455

454456
$allow_subtyping = $additional_properties is nonnull && $additional_properties is bool
455457
? $additional_properties
@@ -458,7 +460,7 @@ private function codegenType(
458460
$members = vec[];
459461
foreach ($property_classes as $property => $builder) {
460462
$member = new CodegenShapeMember($property, $builder->getType());
461-
if (!C\contains($required, $property)) {
463+
if (!C\contains($required, $property) && !C\contains_key($defaults, $property)) {
462464
$member->setIsOptional();
463465
}
464466

tests/ObjectSchemaValidatorTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,37 @@ public function testSinglePatternPropertyObject(): void {
124124
expect($property['S_string_value']['sample'])->toBeSame('test');
125125
}
126126

127+
public function testDefaultsDefaultValue(): void {
128+
$validator = new ObjectSchemaValidator(dict[
129+
'defaults' => dict[
130+
'required_string' => 'string value',
131+
],
132+
]);
133+
134+
$validator->validate();
135+
expect($validator->isValid())->toBeTrue();
136+
137+
$validated = $validator->getValidatedInput();
138+
$defaults = $validated['defaults'] ?? null as nonnull;
139+
expect($defaults['default_string'])->toBeSame('test');
140+
}
141+
142+
public function testDefaultsProvideDefaultValue(): void {
143+
$validator = new ObjectSchemaValidator(dict[
144+
'defaults' => dict[
145+
'required_string' => 'string value',
146+
'default_string' => 'provided',
147+
],
148+
]);
149+
150+
$validator->validate();
151+
expect($validator->isValid())->toBeTrue();
152+
153+
$validated = $validator->getValidatedInput();
154+
$defaults = $validated['defaults'] ?? null as nonnull;
155+
expect($defaults['default_string'])->toBeSame('provided');
156+
}
157+
127158
public function testOnlyPatternPropertiesValid(): void {
128159
$validator = new ObjectSchemaValidator(dict[
129160
'only_pattern_properties' => dict[

tests/examples/codegen/ObjectSchemaValidator.php

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* To re-generate this file run `make test`
66
*
77
*
8-
* @generated SignedSource<<766d50148a61a631dcd0c430fed23430>>
8+
* @generated SignedSource<<fd534ebd94b67fe23410f2616abaa107>>
99
*/
1010
namespace Slack\Hack\JsonSchema\Tests\Generated;
1111
use namespace Slack\Hack\JsonSchema;
@@ -16,9 +16,14 @@
1616
type TObjectSchemaValidatorPropertiesOnlyNoAdditionalProperties = dict<string, mixed>;
1717

1818
type TObjectSchemaValidatorPropertiesOnlyProperties = shape(
19-
?'string' => string,
20-
?'number' => num,
19+
'string' => string,
20+
'number' => num,
21+
'required_string' => string,
22+
);
23+
24+
type TObjectSchemaValidatorPropertiesDefaults = shape(
2125
'required_string' => string,
26+
'default_string' => string,
2227
);
2328

2429
type TObjectSchemaValidatorPropertiesOnlyPatternProperties = dict<string, mixed>;
@@ -88,6 +93,7 @@
8893
?'only_additional_properties' => TObjectSchemaValidatorPropertiesOnlyAdditionalProperties,
8994
?'only_no_additional_properties' => TObjectSchemaValidatorPropertiesOnlyNoAdditionalProperties,
9095
?'only_properties' => TObjectSchemaValidatorPropertiesOnlyProperties,
96+
?'defaults' => TObjectSchemaValidatorPropertiesDefaults,
9197
?'only_pattern_properties' => TObjectSchemaValidatorPropertiesOnlyPatternProperties,
9298
?'single_pattern_property_string' => TObjectSchemaValidatorPropertiesSinglePatternPropertyString,
9399
?'single_pattern_property_object' => TObjectSchemaValidatorPropertiesSinglePatternPropertyObject,
@@ -276,6 +282,106 @@ public static function check(
276282
}
277283
}
278284

285+
final class ObjectSchemaValidatorPropertiesDefaultsPropertiesRequiredString {
286+
287+
private static bool $coerce = false;
288+
289+
public static function check(mixed $input, string $pointer): string {
290+
$typed = Constraints\StringConstraint::check($input, $pointer, self::$coerce);
291+
292+
return $typed;
293+
}
294+
}
295+
296+
final class ObjectSchemaValidatorPropertiesDefaultsPropertiesDefaultString {
297+
298+
private static bool $coerce = false;
299+
300+
public static function check(mixed $input, string $pointer): string {
301+
$typed = Constraints\StringConstraint::check($input, $pointer, self::$coerce);
302+
303+
return $typed;
304+
}
305+
}
306+
307+
final class ObjectSchemaValidatorPropertiesDefaults {
308+
309+
private static keyset<string> $required = keyset[
310+
'required_string',
311+
];
312+
private static bool $coerce = false;
313+
private static keyset<string> $properties = keyset[
314+
'required_string',
315+
'default_string',
316+
];
317+
318+
public static function check(
319+
mixed $input,
320+
string $pointer,
321+
): TObjectSchemaValidatorPropertiesDefaults {
322+
$typed = Constraints\ObjectConstraint::check($input, $pointer, self::$coerce);
323+
324+
$defaults = dict[
325+
'default_string' => 'test',
326+
];
327+
$typed = \HH\Lib\Dict\merge($defaults, $typed);
328+
329+
Constraints\ObjectRequiredConstraint::check(
330+
$typed,
331+
self::$required,
332+
$pointer,
333+
);
334+
335+
$errors = vec[];
336+
$output = shape();
337+
338+
if (\HH\Lib\C\contains_key($typed, 'required_string')) {
339+
try {
340+
$output['required_string'] = ObjectSchemaValidatorPropertiesDefaultsPropertiesRequiredString::check(
341+
$typed['required_string'],
342+
JsonSchema\get_pointer($pointer, 'required_string'),
343+
);
344+
} catch (JsonSchema\InvalidFieldException $e) {
345+
$errors = \HH\Lib\Vec\concat($errors, $e->errors);
346+
}
347+
}
348+
349+
if (\HH\Lib\C\contains_key($typed, 'default_string')) {
350+
try {
351+
$output['default_string'] = ObjectSchemaValidatorPropertiesDefaultsPropertiesDefaultString::check(
352+
$typed['default_string'],
353+
JsonSchema\get_pointer($pointer, 'default_string'),
354+
);
355+
} catch (JsonSchema\InvalidFieldException $e) {
356+
$errors = \HH\Lib\Vec\concat($errors, $e->errors);
357+
}
358+
}
359+
360+
/*HHAST_IGNORE_ERROR[UnusedVariable] Some loops generated with this statement do not use their $value*/
361+
foreach ($typed as $key => $value) {
362+
if (\HH\Lib\C\contains_key(self::$properties, $key)) {
363+
continue;
364+
}
365+
366+
$errors[] = shape(
367+
'code' => JsonSchema\FieldErrorCode::FAILED_CONSTRAINT,
368+
'message' => "invalid additional property: {$key}",
369+
'constraint' => shape(
370+
'type' => JsonSchema\FieldErrorConstraint::ADDITIONAL_PROPERTIES,
371+
'got' => $key,
372+
),
373+
);
374+
}
375+
376+
if (\HH\Lib\C\count($errors)) {
377+
throw new JsonSchema\InvalidFieldException($pointer, $errors);
378+
}
379+
380+
/* HH_IGNORE_ERROR[4163] */
381+
return $output;
382+
}
383+
}
384+
279385
final class ObjectSchemaValidatorPropertiesOnlyPatternPropertiesPatternProperties0 {
280386

281387
private static bool $coerce = false;
@@ -1455,6 +1561,17 @@ public static function check(
14551561
}
14561562
}
14571563

1564+
if (\HH\Lib\C\contains_key($typed, 'defaults')) {
1565+
try {
1566+
$output['defaults'] = ObjectSchemaValidatorPropertiesDefaults::check(
1567+
$typed['defaults'],
1568+
JsonSchema\get_pointer($pointer, 'defaults'),
1569+
);
1570+
} catch (JsonSchema\InvalidFieldException $e) {
1571+
$errors = \HH\Lib\Vec\concat($errors, $e->errors);
1572+
}
1573+
}
1574+
14581575
if (\HH\Lib\C\contains_key($typed, 'only_pattern_properties')) {
14591576
try {
14601577
$output['only_pattern_properties'] = ObjectSchemaValidatorPropertiesOnlyPatternProperties::check(

tests/examples/codegen/UntypedSchemaValidator.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* To re-generate this file run `make test`
66
*
77
*
8-
* @generated SignedSource<<2b4426bd8e81874c4012b0c5fca9082c>>
8+
* @generated SignedSource<<9b56eeba365dbe20f14089dfd10e3b57>>
99
*/
1010
namespace Slack\Hack\JsonSchema\Tests\Generated;
1111
use namespace Slack\Hack\JsonSchema;
@@ -29,24 +29,24 @@
2929
type TUntypedSchemaValidatorPropertiesAllOfCoerce = mixed;
3030

3131
type TUntypedSchemaValidatorPropertiesAllOfDefaultAllOf0 = shape(
32-
?'property' => string,
32+
'property' => string,
3333
...
3434
);
3535

3636
type TUntypedSchemaValidatorPropertiesAllOfDefaultAllOf1 = shape(
37-
?'numerical_property' => num,
37+
'numerical_property' => num,
3838
...
3939
);
4040

4141
type TUntypedSchemaValidatorPropertiesAllOfDefault = mixed;
4242

4343
type TUntypedSchemaValidatorPropertiesAllOfDefaultFirstSchemaWinsAllOf0 = shape(
44-
?'property' => string,
44+
'property' => string,
4545
...
4646
);
4747

4848
type TUntypedSchemaValidatorPropertiesAllOfDefaultFirstSchemaWinsAllOf1 = shape(
49-
?'property' => string,
49+
'property' => string,
5050
...
5151
);
5252

tests/examples/object-schema.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@
1919
"required_string": { "type": "string", "default": "required_default" }
2020
}
2121
},
22+
"defaults": {
23+
"type": "object",
24+
"additionalProperties": false,
25+
"required": ["required_string"],
26+
"properties": {
27+
"required_string": {
28+
"type": "string"
29+
},
30+
"default_string": {
31+
"type": "string",
32+
"default": "test"
33+
}
34+
}
35+
},
2236
"only_pattern_properties": {
2337
"type": "object",
2438
"patternProperties": {

0 commit comments

Comments
 (0)