Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 95 additions & 3 deletions src/Validator/Integer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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)
);
}

/**
Expand All @@ -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
Expand All @@ -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;
}
}
4 changes: 2 additions & 2 deletions tests/Validator/ArrayListTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions tests/Validator/IntegerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}