diff --git a/src/Validator/Integer.php b/src/Validator/Integer.php index ed88521..01b14a3 100755 --- a/src/Validator/Integer.php +++ b/src/Validator/Integer.php @@ -16,15 +16,39 @@ class Integer extends Validator */ protected bool $loose = false; + /** + * @var int + */ + protected int $bits = 32; + + /** + * @var bool + */ + protected bool $unsigned = false; + /** * Pass true to accept integer strings as valid integer values * This option is good for validating query string params. * * @param bool $loose + * @param int $bits Integer bit size (8, 16, 32, or 64) + * @param bool $unsigned Whether the integer is unsigned + * @throws \InvalidArgumentException */ - public function __construct(bool $loose = false) + public function __construct(bool $loose = false, int $bits = 32, bool $unsigned = false) { + if (!\in_array($bits, [8, 16, 32, 64])) { + throw new \InvalidArgumentException('Bits must be 8, 16, 32, or 64'); + } + + // 64-bit unsigned integers exceed PHP_INT_MAX and convert to floats with precision loss + if ($bits === 64 && $unsigned) { + throw new \InvalidArgumentException('64-bit unsigned integers are not supported due to PHP integer limitations'); + } + $this->loose = $loose; + $this->bits = $bits; + $this->unsigned = $unsigned; } /** @@ -36,7 +60,24 @@ public function __construct(bool $loose = false) */ public function getDescription(): string { - return 'Value must be a valid integer'; + $signedness = $this->unsigned ? 'unsigned' : 'signed'; + + // Calculate min and max values based on bit size and signed/unsigned + if ($this->unsigned) { + $min = 0; + $max = (2 ** $this->bits) - 1; + } else { + $min = -(2 ** ($this->bits - 1)); + $max = (2 ** ($this->bits - 1)) - 1; + } + + return \sprintf( + 'Value must be a valid %s %d-bit integer between %s and %s', + $signedness, + $this->bits, + \number_format($min), + \number_format($max) + ); } /** @@ -63,10 +104,47 @@ public function getType(): string return self::TYPE_INTEGER; } + /** + * Get Bits + * + * Returns the bit size of the integer. + * + * @return int + */ + public function getBits(): int + { + return $this->bits; + } + + /** + * Is Unsigned + * + * Returns whether the integer is unsigned. + * + * @return bool + */ + public function isUnsigned(): bool + { + return $this->unsigned; + } + + /** + * Get Format + * + * Returns the OpenAPI/JSON Schema format string for this integer configuration. + * + * @return string + */ + public function getFormat(): string + { + $prefix = $this->isUnsigned() ? 'uint' : 'int'; + return $prefix . $this->bits; + } + /** * Is valid * - * Validation will pass when $value is integer. + * Validation will pass when $value is integer and within the specified bit range. * * @param mixed $value * @return bool @@ -83,6 +161,20 @@ public function isValid(mixed $value): bool return false; } + // Calculate min and max values based on bit size and signed/unsigned + if ($this->unsigned) { + $min = 0; + $max = (2 ** $this->bits) - 1; + } else { + $min = -(2 ** ($this->bits - 1)); + $max = (2 ** ($this->bits - 1)) - 1; + } + + // Check if value is within range + if ($value < $min || $value > $max) { + return false; + } + return true; } } diff --git a/tests/Validator/ArrayListTest.php b/tests/Validator/ArrayListTest.php index 1fca894..4d8efb1 100755 --- a/tests/Validator/ArrayListTest.php +++ b/tests/Validator/ArrayListTest.php @@ -10,11 +10,11 @@ public function testDescription(): void { $arrayList = new ArrayList(new Integer()); $this->assertFalse($arrayList->isValid(['text'])); - $this->assertSame('Value must a valid array and Value must be a valid integer', $arrayList->getDescription()); + $this->assertSame('Value must a valid array and Value must be a valid signed 32-bit integer between -2,147,483,648 and 2,147,483,647', $arrayList->getDescription()); $arrayList = new ArrayList(new Integer(), 3); $this->assertFalse($arrayList->isValid(['a', 'b', 'c', 'd'])); - $this->assertSame('Value must a valid array no longer than 3 items and Value must be a valid integer', $arrayList->getDescription()); + $this->assertSame('Value must a valid array no longer than 3 items and Value must be a valid signed 32-bit integer between -2,147,483,648 and 2,147,483,647', $arrayList->getDescription()); } public function testCanValidateTextValues(): void diff --git a/tests/Validator/IntegerTest.php b/tests/Validator/IntegerTest.php index fac8462..c1b02e9 100755 --- a/tests/Validator/IntegerTest.php +++ b/tests/Validator/IntegerTest.php @@ -33,4 +33,57 @@ public function testCanValidateLoosely() $this->assertFalse($validator->isArray()); $this->assertSame(\Utopia\Validator::TYPE_INTEGER, $validator->getType()); } + + public function testBitSizeAndSignedness() + { + // Default: 32-bit signed + $validator = new Integer(); + $this->assertSame(32, $validator->getBits()); + $this->assertFalse($validator->isUnsigned()); + $this->assertSame('int32', $validator->getFormat()); + + // 8-bit signed: -128 to 127 + $validator8 = new Integer(false, 8); + $this->assertSame('int8', $validator8->getFormat()); + $this->assertTrue($validator8->isValid(-128)); + $this->assertTrue($validator8->isValid(127)); + $this->assertFalse($validator8->isValid(-129)); + $this->assertFalse($validator8->isValid(128)); + + // 8-bit unsigned: 0 to 255 + $validator8u = new Integer(false, 8, true); + $this->assertSame('uint8', $validator8u->getFormat()); + $this->assertTrue($validator8u->isValid(0)); + $this->assertTrue($validator8u->isValid(255)); + $this->assertFalse($validator8u->isValid(-1)); + $this->assertFalse($validator8u->isValid(256)); + + // 16-bit unsigned: 0 to 65535 + $validator16u = new Integer(false, 16, true); + $this->assertSame('uint16', $validator16u->getFormat()); + $this->assertTrue($validator16u->isValid(65535)); + $this->assertFalse($validator16u->isValid(65536)); + + // 32-bit unsigned + $validator32u = new Integer(false, 32, true); + $this->assertSame('uint32', $validator32u->getFormat()); + + // 64-bit signed + $validator64 = new Integer(false, 64); + $this->assertSame('int64', $validator64->getFormat()); + } + + public function testInvalidBitSize() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Bits must be 8, 16, 32, or 64'); + new Integer(false, 128); + } + + public function test64BitUnsignedNotSupported() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('64-bit unsigned integers are not supported due to PHP integer limitations'); + new Integer(false, 64, true); + } }