Skip to content

Commit 336a338

Browse files
committed
feat: improve over hapijs#3011
1 parent b532225 commit 336a338

File tree

4 files changed

+105
-10
lines changed

4 files changed

+105
-10
lines changed

API.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2823,8 +2823,9 @@ Requires the string value to be a valid hexadecimal string.
28232823

28242824
- `options` - optional settings:
28252825
- `byteAligned` - Boolean specifying whether you want to check that the hexadecimal string is byte aligned. If `convert` is `true`, a `0` will be added in front of the string in case it needs to be aligned. Defaults to `false`.
2826+
- `prefix` - Boolean or `optional`. When `true`, the string will be considered valid if prefixed with `0x` or `0X`. When `false`, the prefix is forbidden. When `optional`, the string will be considered valid if prefixed or not prefixed at all. Defaults to `false`.
28262827
```js
2827-
const schema = Joi.string().hex();
2828+
const schema = Joi.string().hex({ prefix: 'optional' });
28282829
```
28292830

28302831
Possible validation errors: [`string.hex`](#stringhex), [`string.hexAlign`](#stringhexalign)

lib/index.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,15 @@ declare namespace Joi {
368368
* @default false
369369
*/
370370
byteAligned?: boolean;
371+
/**
372+
* controls whether the prefix `0x` or `0X` is allowed (or required) on hex strings.
373+
* When `true`, the prefix must be provided.
374+
* When `false`, the prefix is forbidden.
375+
* When `optional`, the prefix is allowed but not required.
376+
*
377+
* @default false
378+
*/
379+
prefix?: boolean | 'optional';
371380
}
372381

373382
interface IpOptions {

lib/types/string.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ const internals = {
2828
},
2929
dataUriRegex: /^data:[\w+.-]+\/[\w+.-]+;((charset=[\w-]+|base64),)?(.*)$/,
3030
hexRegex: {
31-
withPrefix: /^(0x)?[0-9a-f]+$/i,
31+
withPrefix: /^0x[0-9a-f]+$/i,
32+
withOptionalPrefix: /^(?:0x)?[0-9a-f]+$/i,
3233
withoutPrefix: /^[0-9a-f]+$/i
3334
},
3435
ipRegex: Ip.regex({ cidr: 'forbidden' }).regex,
@@ -371,17 +372,21 @@ module.exports = Any.extend({
371372
hex: {
372373
method(options = {}) {
373374

374-
Common.assertOptions(options, ['byteAligned', 'withPrefix']);
375+
Common.assertOptions(options, ['byteAligned', 'prefix']);
375376

376-
options = { byteAligned: false, withPrefix: false, ...options };
377+
options = { byteAligned: false, prefix: false, ...options };
377378
Assert(typeof options.byteAligned === 'boolean', 'byteAligned must be boolean');
378-
Assert(typeof options.withPrefix === 'boolean', 'withPrefix must be boolean');
379+
Assert(typeof options.prefix === 'boolean' || options.prefix === 'optional', 'prefix must be boolean or "optional"');
379380

380381
return this.$_addRule({ name: 'hex', args: { options } });
381382
},
382383
validate(value, helpers, { options }) {
383384

384-
const re = options.withPrefix ? internals.hexRegex.withPrefix : internals.hexRegex.withoutPrefix;
385+
const re = options.prefix === 'optional' ?
386+
internals.hexRegex.withOptionalPrefix :
387+
options.prefix === true ?
388+
internals.hexRegex.withPrefix :
389+
internals.hexRegex.withoutPrefix;
385390
if (!re.test(value)) {
386391
return helpers.error('string.hex');
387392
}

test/types/string.js

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,58 @@ describe('string', () => {
12151215
]
12161216
});
12171217
});
1218+
1219+
it('describes a hex string', () => {
1220+
1221+
expect(Joi.string().hex().describe()).to.equal({
1222+
type: 'string',
1223+
rules: [{
1224+
name: 'hex',
1225+
args: {
1226+
options: {
1227+
byteAligned: false,
1228+
prefix: false
1229+
}
1230+
}
1231+
}]
1232+
});
1233+
expect(Joi.string().hex({ byteAligned: true }).describe()).to.equal({
1234+
type: 'string',
1235+
rules: [{
1236+
name: 'hex',
1237+
args: {
1238+
options: {
1239+
byteAligned: true,
1240+
prefix: false
1241+
}
1242+
}
1243+
}]
1244+
});
1245+
expect(Joi.string().hex({ prefix: true }).describe()).to.equal({
1246+
type: 'string',
1247+
rules: [{
1248+
name: 'hex',
1249+
args: {
1250+
options: {
1251+
byteAligned: false,
1252+
prefix: true
1253+
}
1254+
}
1255+
}]
1256+
});
1257+
expect(Joi.string().hex({ prefix: 'optional' }).describe()).to.equal({
1258+
type: 'string',
1259+
rules: [{
1260+
name: 'hex',
1261+
args: {
1262+
options: {
1263+
byteAligned: false,
1264+
prefix: 'optional'
1265+
}
1266+
}
1267+
}]
1268+
});
1269+
});
12181270
});
12191271

12201272
describe('domain()', () => {
@@ -4490,15 +4542,43 @@ describe('string', () => {
44904542

44914543
it('validates an hexadecimal string with prefix explicitly required', () => {
44924544

4493-
const rule = Joi.string().hex({ withPrefix: true }).strict();
4545+
const rule = Joi.string().hex({ prefix: true }).strict();
44944546
Helper.validate(rule, [
4547+
['0123456789abcdef', false, {
4548+
message: '"value" must only contain hexadecimal characters',
4549+
path: [],
4550+
type: 'string.hex',
4551+
context: { value: '0123456789abcdef', label: 'value' }
4552+
}],
44954553
['0x0123456789abcdef', true],
4496-
['123456789abcdef', true],
4497-
['0123afg', false, {
4554+
['0X0123456789abcdef', true]
4555+
]);
4556+
});
4557+
4558+
it('validates an hexadecimal string with optional prefix', () => {
4559+
4560+
const rule = Joi.string().hex({ prefix: 'optional' }).strict();
4561+
Helper.validate(rule, [
4562+
['0123456789abcdef', true],
4563+
['0x0123456789abcdef', true],
4564+
['0X0123456789abcdef', true],
4565+
['0123456789abcdefg', false, {
44984566
message: '"value" must only contain hexadecimal characters',
44994567
path: [],
45004568
type: 'string.hex',
4501-
context: { value: '0123afg', label: 'value' }
4569+
context: { value: '0123456789abcdefg', label: 'value' }
4570+
}],
4571+
['0x0123456789abcdefg', false, {
4572+
message: '"value" must only contain hexadecimal characters',
4573+
path: [],
4574+
type: 'string.hex',
4575+
context: { value: '0x0123456789abcdefg', label: 'value' }
4576+
}],
4577+
['0X0123456789abcdefg', false, {
4578+
message: '"value" must only contain hexadecimal characters',
4579+
path: [],
4580+
type: 'string.hex',
4581+
context: { value: '0X0123456789abcdefg', label: 'value' }
45024582
}]
45034583
]);
45044584
});

0 commit comments

Comments
 (0)