Skip to content

Commit 124f3a1

Browse files
committed
Fix tests, add path validator, fix optional behavior for chaining, minor fixes
Signed-off-by: rockito10 <[email protected]>
1 parent f65c6aa commit 124f3a1

File tree

7 files changed

+189
-55
lines changed

7 files changed

+189
-55
lines changed

config/config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,10 @@ const envDemoSchema = v.schema({
131131
GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT: v.str().notEmpty(),
132132
UPDATE_CLIENT_CREDENTIAL_SCRIPT: v.str().notEmpty(),
133133

134-
// AFJ_AGENT IN CASE OF DOCKER
135-
AFJ_AGENT_TOKEN_PATH: v.str().optional(),
136-
AFJ_AGENT_SPIN_UP: v.str().optional(),
137-
AFJ_AGENT_ENDPOINT_PATH: v.str().optional(),
134+
// AFJ_AGENT
135+
AFJ_AGENT_TOKEN_PATH: v.str().path(),
136+
AFJ_AGENT_SPIN_UP: v.str().path(),
137+
AFJ_AGENT_ENDPOINT_PATH: v.str().path(),
138138

139139
// AGENT_PROTOCOL
140140
AGENT_PROTOCOL: v.str().protocol(),

config/core/helpers/str-helpers.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
function _includes(inputs: string[], value: string | undefined): boolean {
2-
return inputs?.includes(value);
2+
return !Boolean(value) || inputs?.includes(value); // SIRVE?
33
}
44

55
function _isNumber(input: string | undefined): boolean {
6-
return !Number.isNaN(Number(input));
6+
return '' !== input?.trim() && !Number.isNaN(Number(input));
77
}
88

99
function _isNotEmpty(input: string | undefined): boolean {
10-
return '' !== input?.trim();
10+
return input !== undefined && '' !== input.trim();
1111
}
1212

1313
function _isOptional(): boolean {
1414
return true;
1515
}
1616

17+
function _isPath(input: string | undefined): boolean {
18+
return (
19+
input !== undefined &&
20+
'' !== input.trim() &&
21+
/^\/([a-zA-Z0-9_-]+\/)*([a-zA-Z0-9_-]+\/|[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)$/.test(input)
22+
);
23+
}
24+
1725
function _startsWith(input: string | undefined, prefix: string): boolean {
1826
return input?.startsWith(prefix);
1927
}
@@ -25,5 +33,6 @@ export const _STR = {
2533
_isNotEmpty,
2634
_isNumber,
2735
_isOptional,
36+
_isPath,
2837
_startsWith
2938
};

config/core/helpers/url-helpers.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import validator from 'validator';
33
// --------------------------------------------------------------------------------
44

55
function _isDomain(input: string | undefined): boolean {
6-
const regex = /^[a-zA-Z0-9.-]+$/;
7-
return regex.test(input || '');
6+
return validator.isFQDN(input);
87
}
98

109
function _isIP(input: string | undefined): boolean {
@@ -17,19 +16,26 @@ function _isPort(input: string | undefined): boolean {
1716
}
1817

1918
function _isLocalhost(input: string | undefined): boolean {
20-
const regex = /^(http:\/\/)?(localhost|127\.0\.0\.1|::1)(:\d{1,5})?(\/)?$/;
19+
const regex = /^(http:\/\/)?(\[?::1\]?|localhost|127\.0\.0\.1)(:\d{1,5})?(\/)?$/;
2120

2221
if (!regex.test(input || '')) {
2322
return false;
2423
}
2524

26-
const port = input.split(':').at(-1).split('/').at(0);
25+
const bracketlessInput = input.replace(/^\[?(.+?)\]?$/, '$1');
26+
const port = bracketlessInput.split(':').at(-1).split('/').at(0);
2727

2828
return _isPort(port);
2929
}
3030

31+
function _isLocalhostHost(input: string | undefined): boolean {
32+
// Está bien??
33+
const regex = /^(http:\/\/)?(\[?::1\]?|localhost|127\.0\.0\.1)(:\d{1,5})?(\/)?$/;
34+
return regex.test(input || '');
35+
}
36+
3137
function _isHost(input: string | undefined): boolean {
32-
return _isIP(input) || _isDomain(input) || _isLocalhost(input);
38+
return _isIP(input) || _isDomain(input) || _isLocalhostHost(input);
3339
}
3440

3541
function _isEndpoint(input: string | undefined): boolean {

config/core/index.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
_enum,
2+
path,
33
domain,
44
email,
55
endpoint,
@@ -30,11 +30,13 @@ export type SafeParseReturnType<T> =
3030
class StringBuilder {
3131
readonly #validators: Validator<string>[] = [];
3232

33-
enum(inputs: string[]): this {
34-
this.#validators.push(_enum(inputs));
35-
return this;
36-
}
37-
33+
/**
34+
* Adds a validator that checks if the string is not empty.
35+
*
36+
* This validator will fail if the string is `undefined`, `null`, or an empty string.
37+
*
38+
* This validator is meant to be used when there are no other validations being used, since all other validations have null/undefined/"" checks in them.
39+
*/
3840
notEmpty(): this {
3941
this.#validators.push(notEmpty());
4042
return this;
@@ -105,18 +107,31 @@ class StringBuilder {
105107
return this;
106108
}
107109

108-
// --
110+
path(): this {
111+
this.#validators.push(path());
112+
return this;
113+
}
109114

115+
/**
116+
* Adds a validator that allows the string to be optional.
117+
*
118+
* This validator prevent failure if the string is undefined.
119+
*
120+
* This is useful when you want to validate something that you know it can or can't be there,
121+
* adding other validators on top to validate when there actually is something.
122+
*/
110123
optional(): this {
111124
this.#validators.push(optional());
112125
return this;
113126
}
114127

115-
// --
116-
117128
safeParse(input: string): SafeParseReturnType<string> {
118129
const issues: Issue[] = [];
119130

131+
if (input === undefined && this.#validators.includes(optional())) {
132+
return { data: input, error: null, success: true };
133+
}
134+
120135
for (const validator of this.#validators) {
121136
const issue = validator(input);
122137
if (issue) {

config/core/validators.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,6 @@ export type Validator<T> = (input: T | undefined) => Issue | undefined;
1010
* STRING VALIDATORS (GENERIC)
1111
* -------------------------------------------------------------------------------- */
1212

13-
export function _enum(inputs: string[]): Validator<string> {
14-
return (input) => {
15-
const success = _STR._includes(inputs, input);
16-
17-
if (!success) {
18-
const expected = inputs.map((v) => `'${v}'`).join(' | ');
19-
20-
return {
21-
expected,
22-
received: input,
23-
message: `Expected one of ${expected}`
24-
};
25-
}
26-
};
27-
}
28-
29-
// --------------------------------------------------------------------------------
30-
3113
export function boolean(): Validator<string> {
3214
return (input) => {
3315
const success = _STR._includes(['true', 'false'], input);
@@ -228,9 +210,9 @@ export function localhost(): Validator<string> {
228210

229211
if (!success) {
230212
return {
231-
expected: 'A valid localhost (e.g., http://localhost:aPortNumber, 127.0.0.1:aPortNumber)',
213+
expected: 'A valid localhost (e.g., http://localhost:3000, http://127.0.0.1:3000, http://[::1]:3000)',
232214
received: input,
233-
message: 'Invalid Localhost.'
215+
message: 'Invalid localhost.'
234216
};
235217
}
236218
};
@@ -262,12 +244,26 @@ export function postgresUrl(): Validator<string> {
262244
if (!success) {
263245
return {
264246
expected:
265-
'A valid postgresURL with the format: postgresql://{postgres.user}:{postgres.password}@{your-ip}:{postgres.port}/{database-name}/schema={}',
247+
'A valid Postgres URL with the format: postgresql://{postgres.user}:{postgres.password}@{your-ip}:{postgres.port}/{database-name}/schema={}',
266248
received: input,
267-
message: 'Invalid collection of URLs.'
249+
message: 'Invalid PostgreSQL URL format.'
268250
};
269251
}
270252
};
271253
}
272254

273255
// --------------------------------------------------------------------------------
256+
257+
export function path(): Validator<string> {
258+
return (input) => {
259+
const success = _STR._isPath(input);
260+
261+
if (!success) {
262+
return {
263+
expected: 'A valid path. E.g: folder/anotherFolder',
264+
received: input,
265+
message: 'Invalid path.'
266+
};
267+
}
268+
};
269+
}

config/test/.env.test.sample

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,16 @@ TEST_PORT_1=1000 # FAIL
1717
TEST_PORT_2=99999 # FAIL
1818
TEST_PORT_3=3000 # PASS
1919

20-
TEST_OPTIONAL_1= # FAIL
21-
TEST_OPTIONAL_2=HELLO WORLD # PASS
20+
TEST_OPTIONAL_1= # PASS
21+
TEST_OPTIONAL_2=HI # PASS
22+
TEST_OPTIONAL_3=HELLO WORLD # FAIL
23+
TEST_OPTIONAL_4= # PASS
24+
TEST_OPTIONAL_5=true # PASS
2225

2326
TEST_NOT_EMPTY_1= # FAIL
2427
TEST_NOT_EMPTY_2=HELLO WORLD # PASS
28+
TEST_NOT_EMPTY_3= # FAIL
29+
TEST_NOT_EMPTY_4=true # PASS
2530

2631
TEST_EMAIL_1=HELLO WORLD # FAIL
2732
TEST_EMAIL_2=name@gmail # FAIL
@@ -37,6 +42,7 @@ TEST_BOOLEAN_6=false # PASS
3742

3843
TEST_NUMBER_1=HELLO WORLD # FAIL
3944
TEST_NUMBER_2=1000 # PASS
45+
TEST_NUMBER_3=" " # FAIL
4046

4147
TEST_ENDPOINT_1=HELLO WORLD # FAIL
4248
TEST_ENDPOINT_2=http://localhost:3000 # FAIL
@@ -55,7 +61,16 @@ TEST_POSTGRES_URL_4=postgresql://postgres:postgres@localhost:5432/credebl # PAS
5561
TEST_LOCALHOST_1=localhost # FAIL
5662
TEST_LOCALHOST_2=localhost:3000 # PASS
5763
TEST_LOCALHOST_3=http://localhost:3000 # PASS
64+
TEST_LOCALHOST_4=http://[::1]:8000 # PASS
5865

5966
TEST_START_WITH_1=HELLO # FAIL
6067
TEST_START_WITH_2=WORLD ! ! # PASS
6168

69+
TEST_ENUM_1= # FAIL
70+
TEST_ENUM_2=WORLD # FAIL
71+
TEST_ENUM_3=HELLO, WORLD # PASS
72+
73+
TEST_PATH_1=/folder/photos/2025/ # PASS
74+
TEST_PATH_2= # FAIL
75+
TEST_PATH_3=folder/ # FAIL
76+
TEST_PATH_4=/folder/photos/2025/photo/image.png # PASS

0 commit comments

Comments
 (0)