diff --git a/src/Constraints/NumberMaximumConstraint.php b/src/Constraints/NumberMaximumConstraint.php index 2d555e5..a4fa4b9 100644 --- a/src/Constraints/NumberMaximumConstraint.php +++ b/src/Constraints/NumberMaximumConstraint.php @@ -1,8 +1,8 @@ $maximum, 'got' => $input, ), - 'message' => Str\format('must be less than %s', (string)$maximum), + 'message' => Str\format('must be less than or equal to %s', (string)$maximum), ); throw new JsonSchema\InvalidFieldException($pointer, vec[$error]); } diff --git a/src/Constraints/NumberMinimumConstraint.php b/src/Constraints/NumberMinimumConstraint.php index 79b7f37..e129b83 100644 --- a/src/Constraints/NumberMinimumConstraint.php +++ b/src/Constraints/NumberMinimumConstraint.php @@ -15,7 +15,7 @@ public static function check(num $input, num $minimum, string $pointer): void { 'expected' => $minimum, 'got' => $input, ), - 'message' => Str\format('must be greater than %s', (string)$minimum), + 'message' => Str\format('must be greater than or equal to %s', (string)$minimum), ); throw new JsonSchema\InvalidFieldException($pointer, vec[$error]); diff --git a/src/Constraints/StringMaxLengthConstraint.php b/src/Constraints/StringMaxLengthConstraint.php index f9913e3..2212d73 100644 --- a/src/Constraints/StringMaxLengthConstraint.php +++ b/src/Constraints/StringMaxLengthConstraint.php @@ -2,6 +2,7 @@ namespace Slack\Hack\JsonSchema\Constraints; +use namespace HH\Lib\Str; use namespace Slack\Hack\JsonSchema; class StringMaxLengthConstraint { @@ -14,7 +15,7 @@ public static function check(int $length, int $maximum, string $pointer): void { 'expected' => $maximum, 'got' => $length, ), - 'message' => 'must be less than '.($maximum + 1).' characters', + 'message' => Str\format('must be less than or equal to %s characters', (string)$maximum), ); throw new JsonSchema\InvalidFieldException($pointer, vec[$error]); } diff --git a/src/Constraints/StringMinLengthConstraint.php b/src/Constraints/StringMinLengthConstraint.php index 75fd361..4d2f2ae 100644 --- a/src/Constraints/StringMinLengthConstraint.php +++ b/src/Constraints/StringMinLengthConstraint.php @@ -2,6 +2,7 @@ namespace Slack\Hack\JsonSchema\Constraints; +use namespace HH\Lib\Str; use namespace Slack\Hack\JsonSchema; class StringMinLengthConstraint { @@ -14,7 +15,7 @@ public static function check(int $length, int $minimum, string $pointer): void { 'expected' => $minimum, 'got' => $length, ), - 'message' => 'must be more than '.($minimum - 1).' characters', + 'message' => Str\format('must be greater than or equal to %s characters', (string)$minimum), ); throw new JsonSchema\InvalidFieldException($pointer, vec[$error]); } diff --git a/tests/NumericalSchemaValidatorTest.php b/tests/NumericalSchemaValidatorTest.php index edf8e0f..f39bd11 100644 --- a/tests/NumericalSchemaValidatorTest.php +++ b/tests/NumericalSchemaValidatorTest.php @@ -23,12 +23,18 @@ public function testInteger(): void { $cases = vec[ shape( 'input' => darray['integer' => 1000], - 'output' => darray['integer' => 1000], + 'output' => darray[ + 'integer_limits' => 5, + 'integer' => 1000 + ], 'valid' => true, ), shape( 'input' => darray['integer' => 0], - 'output' => darray['integer' => 0], + 'output' => darray[ + 'integer_limits' => 5, + 'integer' => 0 + ], 'valid' => true, ), shape('input' => darray['integer' => '1000'], 'valid' => false), @@ -42,12 +48,18 @@ public function testNumber(): void { $cases = vec[ shape( 'input' => darray['number' => 1000], - 'output' => darray['number' => 1000], + 'output' => darray[ + 'integer_limits' => 5, + 'number' => 1000 + ], 'valid' => true, ), shape( 'input' => darray['number' => 1000.00], - 'output' => darray['number' => 1000.00], + 'output' => darray[ + 'integer_limits' => 5, + 'number' => 1000.00 + ], 'valid' => true, ), shape('input' => darray['number' => '1000'], 'valid' => false), @@ -61,12 +73,18 @@ public function testIntegerCoerce(): void { $cases = vec[ shape( 'input' => darray['integer_coerce' => 1], - 'output' => darray['integer_coerce' => 1], + 'output' => darray[ + 'integer_limits' => 5, + 'integer_coerce' => 1 + ], 'valid' => true, ), shape( 'input' => darray['integer_coerce' => '100'], - 'output' => darray['integer_coerce' => 100], + 'output' => darray[ + 'integer_limits' => 5, + 'integer_coerce' => 100 + ], 'valid' => true, ), shape( @@ -86,17 +104,26 @@ public function testNumberCoerce(): void { $cases = vec[ shape( 'input' => darray['number_coerce' => 1.0], - 'output' => darray['number_coerce' => 1.0], + 'output' => darray[ + 'integer_limits' => 5, + 'number_coerce' => 1.0 + ], 'valid' => true, ), shape( 'input' => darray['number_coerce' => '100'], - 'output' => darray['number_coerce' => 100], + 'output' => darray[ + 'integer_limits' => 5, + 'number_coerce' => 100 + ], 'valid' => true, ), shape( 'input' => darray['number_coerce' => '100.0'], - 'output' => darray['number_coerce' => 100.0], + 'output' => darray[ + 'integer_limits' => 5, + 'number_coerce' => 100.0 + ], 'valid' => true, ), shape( @@ -112,12 +139,18 @@ public function testHackEnum(): void { $cases = vec[ shape( 'input' => darray['hack_enum' => 1], - 'output' => darray['hack_enum' => TestIntEnum::ABC], + 'output' => darray[ + 'integer_limits' => 5, + 'hack_enum' => TestIntEnum::ABC + ], 'valid' => true, ), shape( 'input' => darray['hack_enum' => 2], - 'output' => darray['hack_enum' => TestIntEnum::DEF], + 'output' => darray[ + 'integer_limits' => 5, + 'hack_enum' => TestIntEnum::DEF + ], 'valid' => true, ), shape('input' => darray['hack_enum' => 0], 'valid' => false), diff --git a/tests/TopLevelRefValidatorTest.hack b/tests/TopLevelRefValidatorTest.hack index ff15376..0ae11dd 100644 --- a/tests/TopLevelRefValidatorTest.hack +++ b/tests/TopLevelRefValidatorTest.hack @@ -26,12 +26,18 @@ final class TopLevelRefValidatorTest extends BaseCodegenTestCase { $cases = vec[ shape( 'input' => darray['integer' => 1000], - 'output' => darray['integer' => 1000], + 'output' => darray[ + 'integer_limits' => 5, + 'integer' => 1000 + ], 'valid' => true, ), shape( 'input' => darray['integer' => 0], - 'output' => darray['integer' => 0], + 'output' => darray[ + 'integer_limits' => 5, + 'integer' => 0 + ], 'valid' => true, ), shape('input' => darray['integer' => '1000'], 'valid' => false), diff --git a/tests/examples/codegen/ExamplesNumericalSchema.php b/tests/examples/codegen/ExamplesNumericalSchema.php index faf17c9..dec71d9 100644 --- a/tests/examples/codegen/ExamplesNumericalSchema.php +++ b/tests/examples/codegen/ExamplesNumericalSchema.php @@ -5,7 +5,7 @@ * To re-generate this file run `make test` * * - * @generated SignedSource<<69e5c34e78272d88aec25b725df66a41>> + * @generated SignedSource<<4e71e0ec7d07d93792cc2bd02d3a3c8d>> */ namespace Slack\Hack\JsonSchema\Tests\Generated; use namespace Slack\Hack\JsonSchema; @@ -17,6 +17,7 @@ ?'integer_coerce' => int, ?'number_coerce' => num, ?'hack_enum' => \Slack\Hack\JsonSchema\Tests\TestIntEnum, + 'integer_limits' => int, ... ); @@ -86,6 +87,30 @@ public static function check( } } +final class ExamplesNumericalSchemaPropertiesIntegerLimits { + + private static int $maximum = 10; + private static int $minimum = 1; + private static bool $coerce = false; + + public static function check(mixed $input, string $pointer): int { + $typed = + Constraints\IntegerConstraint::check($input, $pointer, self::$coerce); + + Constraints\NumberMaximumConstraint::check( + $typed, + self::$maximum, + $pointer, + ); + Constraints\NumberMinimumConstraint::check( + $typed, + self::$minimum, + $pointer, + ); + return $typed; + } +} + final class ExamplesNumericalSchema extends JsonSchema\BaseValidator { @@ -97,6 +122,12 @@ public static function check( ): TExamplesNumericalSchema { $typed = Constraints\ObjectConstraint::check($input, $pointer, self::$coerce); + $defaults = dict[ + 'integer_limits' => 5, + ]; + $typed = \HH\Lib\Dict\merge($defaults, $typed); + + $errors = vec[]; $output = shape(); @@ -161,6 +192,17 @@ public static function check( } } + if (\HH\Lib\C\contains_key($typed, 'integer_limits')) { + try { + $output['integer_limits'] = ExamplesNumericalSchemaPropertiesIntegerLimits::check( + $typed['integer_limits'], + JsonSchema\get_pointer($pointer, 'integer_limits'), + ); + } catch (JsonSchema\InvalidFieldException $e) { + $errors = \HH\Lib\Vec\concat($errors, $e->errors); + } + } + if (\HH\Lib\C\count($errors)) { throw new JsonSchema\InvalidFieldException($pointer, $errors); } diff --git a/tests/examples/codegen/NumericalSchemaValidator.php b/tests/examples/codegen/NumericalSchemaValidator.php index 013b8dc..828b7ed 100644 --- a/tests/examples/codegen/NumericalSchemaValidator.php +++ b/tests/examples/codegen/NumericalSchemaValidator.php @@ -5,7 +5,7 @@ * To re-generate this file run `make test` * * - * @generated SignedSource<<5d96bf78c70de4ebc3454eec9a957550>> + * @generated SignedSource<<922cf6e7d1d33858846294df801f20f3>> */ namespace Slack\Hack\JsonSchema\Tests\Generated; use namespace Slack\Hack\JsonSchema; @@ -17,6 +17,7 @@ ?'integer_coerce' => int, ?'number_coerce' => num, ?'hack_enum' => \Slack\Hack\JsonSchema\Tests\TestIntEnum, + 'integer_limits' => int, ... ); @@ -86,6 +87,30 @@ public static function check( } } +final class NumericalSchemaValidatorPropertiesIntegerLimits { + + private static int $maximum = 10; + private static int $minimum = 1; + private static bool $coerce = false; + + public static function check(mixed $input, string $pointer): int { + $typed = + Constraints\IntegerConstraint::check($input, $pointer, self::$coerce); + + Constraints\NumberMaximumConstraint::check( + $typed, + self::$maximum, + $pointer, + ); + Constraints\NumberMinimumConstraint::check( + $typed, + self::$minimum, + $pointer, + ); + return $typed; + } +} + final class NumericalSchemaValidator extends JsonSchema\BaseValidator { @@ -97,6 +122,12 @@ public static function check( ): TNumericalSchemaValidator { $typed = Constraints\ObjectConstraint::check($input, $pointer, self::$coerce); + $defaults = dict[ + 'integer_limits' => 5, + ]; + $typed = \HH\Lib\Dict\merge($defaults, $typed); + + $errors = vec[]; $output = shape(); @@ -161,6 +192,17 @@ public static function check( } } + if (\HH\Lib\C\contains_key($typed, 'integer_limits')) { + try { + $output['integer_limits'] = NumericalSchemaValidatorPropertiesIntegerLimits::check( + $typed['integer_limits'], + JsonSchema\get_pointer($pointer, 'integer_limits'), + ); + } catch (JsonSchema\InvalidFieldException $e) { + $errors = \HH\Lib\Vec\concat($errors, $e->errors); + } + } + if (\HH\Lib\C\count($errors)) { throw new JsonSchema\InvalidFieldException($pointer, $errors); } diff --git a/tests/examples/numerical-schema.json b/tests/examples/numerical-schema.json index 8fc9ffa..7bbfa25 100644 --- a/tests/examples/numerical-schema.json +++ b/tests/examples/numerical-schema.json @@ -18,6 +18,12 @@ "hack_enum": { "type": "integer", "hackEnum": "Slack\\Hack\\JsonSchema\\Tests\\TestIntEnum" + }, + "integer_limits": { + "type": "integer", + "minimum": 1, + "maximum": 10, + "default": 5 } } -} +} \ No newline at end of file diff --git a/tests/examples/string-schema.json b/tests/examples/string-schema.json index bd24ac6..9d12336 100644 --- a/tests/examples/string-schema.json +++ b/tests/examples/string-schema.json @@ -40,9 +40,13 @@ "type": "string", "hackEnum": "Slack\\Hack\\JsonSchema\\Tests\\TestStringEnum" }, + "min_length": { + "type": "string", + "minLength": 1 + }, "max_length": { "type": "string", "maxLength": 10 } } -} +} \ No newline at end of file diff --git a/tests/examples/untyped-schema.json b/tests/examples/untyped-schema.json index 880ecc8..5812d06 100644 --- a/tests/examples/untyped-schema.json +++ b/tests/examples/untyped-schema.json @@ -115,13 +115,17 @@ "anyOf": [ { "type": "object", - "required": ["type"], + "required": [ + "type" + ], "additionalProperties": false, "coerce": false, "properties": { "type": { "type": "string", - "enum": ["first"] + "enum": [ + "first" + ] }, "string": { "type": "string" @@ -130,13 +134,17 @@ }, { "type": "object", - "required": ["type"], + "required": [ + "type" + ], "additionalProperties": false, "coerce": false, "properties": { "type": { "type": "string", - "enum": ["second"] + "enum": [ + "second" + ] }, "integer": { "type": "integer" @@ -181,4 +189,4 @@ ] } } -} +} \ No newline at end of file