Skip to content

Commit 30b6030

Browse files
authored
Merge pull request #5 from utopia-php/feat-integer-validator-bit-size-signedness
2 parents 4c34146 + 982d4fd commit 30b6030

3 files changed

Lines changed: 150 additions & 5 deletions

File tree

src/Validator/Integer.php

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,39 @@ class Integer extends Validator
1616
*/
1717
protected bool $loose = false;
1818

19+
/**
20+
* @var int
21+
*/
22+
protected int $bits = 32;
23+
24+
/**
25+
* @var bool
26+
*/
27+
protected bool $unsigned = false;
28+
1929
/**
2030
* Pass true to accept integer strings as valid integer values
2131
* This option is good for validating query string params.
2232
*
2333
* @param bool $loose
34+
* @param int $bits Integer bit size (8, 16, 32, or 64)
35+
* @param bool $unsigned Whether the integer is unsigned
36+
* @throws \InvalidArgumentException
2437
*/
25-
public function __construct(bool $loose = false)
38+
public function __construct(bool $loose = false, int $bits = 32, bool $unsigned = false)
2639
{
40+
if (!\in_array($bits, [8, 16, 32, 64])) {
41+
throw new \InvalidArgumentException('Bits must be 8, 16, 32, or 64');
42+
}
43+
44+
// 64-bit unsigned integers exceed PHP_INT_MAX and convert to floats with precision loss
45+
if ($bits === 64 && $unsigned) {
46+
throw new \InvalidArgumentException('64-bit unsigned integers are not supported due to PHP integer limitations');
47+
}
48+
2749
$this->loose = $loose;
50+
$this->bits = $bits;
51+
$this->unsigned = $unsigned;
2852
}
2953

3054
/**
@@ -36,7 +60,24 @@ public function __construct(bool $loose = false)
3660
*/
3761
public function getDescription(): string
3862
{
39-
return 'Value must be a valid integer';
63+
$signedness = $this->unsigned ? 'unsigned' : 'signed';
64+
65+
// Calculate min and max values based on bit size and signed/unsigned
66+
if ($this->unsigned) {
67+
$min = 0;
68+
$max = (2 ** $this->bits) - 1;
69+
} else {
70+
$min = -(2 ** ($this->bits - 1));
71+
$max = (2 ** ($this->bits - 1)) - 1;
72+
}
73+
74+
return \sprintf(
75+
'Value must be a valid %s %d-bit integer between %s and %s',
76+
$signedness,
77+
$this->bits,
78+
\number_format($min),
79+
\number_format($max)
80+
);
4081
}
4182

4283
/**
@@ -63,10 +104,47 @@ public function getType(): string
63104
return self::TYPE_INTEGER;
64105
}
65106

107+
/**
108+
* Get Bits
109+
*
110+
* Returns the bit size of the integer.
111+
*
112+
* @return int
113+
*/
114+
public function getBits(): int
115+
{
116+
return $this->bits;
117+
}
118+
119+
/**
120+
* Is Unsigned
121+
*
122+
* Returns whether the integer is unsigned.
123+
*
124+
* @return bool
125+
*/
126+
public function isUnsigned(): bool
127+
{
128+
return $this->unsigned;
129+
}
130+
131+
/**
132+
* Get Format
133+
*
134+
* Returns the OpenAPI/JSON Schema format string for this integer configuration.
135+
*
136+
* @return string
137+
*/
138+
public function getFormat(): string
139+
{
140+
$prefix = $this->isUnsigned() ? 'uint' : 'int';
141+
return $prefix . $this->bits;
142+
}
143+
66144
/**
67145
* Is valid
68146
*
69-
* Validation will pass when $value is integer.
147+
* Validation will pass when $value is integer and within the specified bit range.
70148
*
71149
* @param mixed $value
72150
* @return bool
@@ -83,6 +161,20 @@ public function isValid(mixed $value): bool
83161
return false;
84162
}
85163

164+
// Calculate min and max values based on bit size and signed/unsigned
165+
if ($this->unsigned) {
166+
$min = 0;
167+
$max = (2 ** $this->bits) - 1;
168+
} else {
169+
$min = -(2 ** ($this->bits - 1));
170+
$max = (2 ** ($this->bits - 1)) - 1;
171+
}
172+
173+
// Check if value is within range
174+
if ($value < $min || $value > $max) {
175+
return false;
176+
}
177+
86178
return true;
87179
}
88180
}

tests/Validator/ArrayListTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ public function testDescription(): void
1010
{
1111
$arrayList = new ArrayList(new Integer());
1212
$this->assertFalse($arrayList->isValid(['text']));
13-
$this->assertSame('Value must a valid array and Value must be a valid integer', $arrayList->getDescription());
13+
$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());
1414

1515
$arrayList = new ArrayList(new Integer(), 3);
1616
$this->assertFalse($arrayList->isValid(['a', 'b', 'c', 'd']));
17-
$this->assertSame('Value must a valid array no longer than 3 items and Value must be a valid integer', $arrayList->getDescription());
17+
$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());
1818
}
1919

2020
public function testCanValidateTextValues(): void

tests/Validator/IntegerTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,57 @@ public function testCanValidateLoosely()
3333
$this->assertFalse($validator->isArray());
3434
$this->assertSame(\Utopia\Validator::TYPE_INTEGER, $validator->getType());
3535
}
36+
37+
public function testBitSizeAndSignedness()
38+
{
39+
// Default: 32-bit signed
40+
$validator = new Integer();
41+
$this->assertSame(32, $validator->getBits());
42+
$this->assertFalse($validator->isUnsigned());
43+
$this->assertSame('int32', $validator->getFormat());
44+
45+
// 8-bit signed: -128 to 127
46+
$validator8 = new Integer(false, 8);
47+
$this->assertSame('int8', $validator8->getFormat());
48+
$this->assertTrue($validator8->isValid(-128));
49+
$this->assertTrue($validator8->isValid(127));
50+
$this->assertFalse($validator8->isValid(-129));
51+
$this->assertFalse($validator8->isValid(128));
52+
53+
// 8-bit unsigned: 0 to 255
54+
$validator8u = new Integer(false, 8, true);
55+
$this->assertSame('uint8', $validator8u->getFormat());
56+
$this->assertTrue($validator8u->isValid(0));
57+
$this->assertTrue($validator8u->isValid(255));
58+
$this->assertFalse($validator8u->isValid(-1));
59+
$this->assertFalse($validator8u->isValid(256));
60+
61+
// 16-bit unsigned: 0 to 65535
62+
$validator16u = new Integer(false, 16, true);
63+
$this->assertSame('uint16', $validator16u->getFormat());
64+
$this->assertTrue($validator16u->isValid(65535));
65+
$this->assertFalse($validator16u->isValid(65536));
66+
67+
// 32-bit unsigned
68+
$validator32u = new Integer(false, 32, true);
69+
$this->assertSame('uint32', $validator32u->getFormat());
70+
71+
// 64-bit signed
72+
$validator64 = new Integer(false, 64);
73+
$this->assertSame('int64', $validator64->getFormat());
74+
}
75+
76+
public function testInvalidBitSize()
77+
{
78+
$this->expectException(\InvalidArgumentException::class);
79+
$this->expectExceptionMessage('Bits must be 8, 16, 32, or 64');
80+
new Integer(false, 128);
81+
}
82+
83+
public function test64BitUnsignedNotSupported()
84+
{
85+
$this->expectException(\InvalidArgumentException::class);
86+
$this->expectExceptionMessage('64-bit unsigned integers are not supported due to PHP integer limitations');
87+
new Integer(false, 64, true);
88+
}
3689
}

0 commit comments

Comments
 (0)