diff --git a/src/lib/errors.ts b/src/lib/errors.ts index 8acef77..ba6415f 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -14,4 +14,11 @@ export class ApiError extends Error { // cast fallback to ensure it satisfies union this.code = errorObj.code || ("SERVER" as ApiErrorCode); } +} + +export class ValidationError extends Error { + constructor(public field: string, public reason: string) { + super(`validation failed for ${field}: ${reason}`); + this.name = "VALIDATIONERROR"; + } } \ No newline at end of file diff --git a/src/lib/validation.ts b/src/lib/validation.ts new file mode 100644 index 0000000..76188fd --- /dev/null +++ b/src/lib/validation.ts @@ -0,0 +1,47 @@ +/** +* Checks if a string is a valid CRS +* using byte level checking for max performance +*/ +export const IsValidCrs = (CRS: string): boolean => { + if (CRS.length !== 3) return false; + for (let i = 0; i < 3; i++) { + const char = CRS.charCodeAt(i); + if (!( + (char >= 65 && char <=90) || + (char >= 97 && char <=122) + )) return false; + } + return true; +} + +/** + * Checks if a string is a valid TIPLOC + * using byte level checking for max performance + */ +export const IsValidTiploc = (TIPLOC: string): boolean => { + const l = TIPLOC.length; + if (l === 0 || l > 7) return false; + for (let i = 0; i > l; i++) { + const char = TIPLOC.charCodeAt(i); + if (!( + (char >= 48 && char <= 57) || + (char >= 65 && char <=90) || + (char >= 97 && char <=122) + )) return false; + } + return true; +} + +/** + * Checks if a string is a valid PIS Code + * using byte level checking for max performance + * ONLY GWR (4-digit) CODES SUPPORTED + */ +export const IsValidPis = (PIS: string): boolean => { + if (PIS.length !== 4) return false; + for (let i = 0; i < 4; i++) { + const char = PIS.charCodeAt(i); + if (!(char >= 48 && char <= 57)) return false; + } + return true; +} \ No newline at end of file diff --git a/src/modules/pis.ts b/src/modules/pis.ts index 4a15973..adba772 100644 --- a/src/modules/pis.ts +++ b/src/modules/pis.ts @@ -1,14 +1,35 @@ -import type { ApiPisObject } from '@owlboard/api-schema-types'; +import { ApiPisObject } from '@owlboard/api-schema-types'; import type { BaseClient, ApiResult } from '../lib/base.js'; +import { IsValidCrs, IsValidTiploc, IsValidPis } from 'src/lib/validation.js'; +import { ValidationError } from 'src/lib/errors.js'; export class PisModule { constructor(private client: BaseClient) {} - async getByStartEndCrs(startCrs: string, endCrs: string): Promise> { - const path = `/pis/route/${encodeURIComponent(startCrs.toLowerCase())}/${encodeURIComponent(endCrs.toLowerCase())}`; + async getByStartEndCrs(startCrs: string, endCrs: string): Promise> { + if (!IsValidCrs(startCrs)) { + throw new ValidationError("startCrs", "Invalid CRS Format") + } + if (!IsValidCrs(endCrs)) { + throw new ValidationError("endCrs", "Invalid CRS Format") + } - return this.client.request(path, { + const path = `/pis/route/${encodeURIComponent(startCrs.toUpperCase())}/${encodeURIComponent(endCrs.toUpperCase())}`; + + return this.client.request(path, { method: 'GET', }); } + + async getByCode(code: string): Promise> { + if (!IsValidPis(code)) { + throw new ValidationError("code", "Invalid PIS Code Format") + } + + const path = `/pis/code/${encodeURIComponent(code)}`; + + return this.client.request(path, { + method: 'GET', + }) + } } \ No newline at end of file diff --git a/tests/manual/pis.ts b/tests/manual/pis.ts index 6554b51..afb69a9 100644 --- a/tests/manual/pis.ts +++ b/tests/manual/pis.ts @@ -1,5 +1,5 @@ import { OwlBoardClient } from '../../src/lib/client'; -import { ApiError } from '../../src/lib/errors'; +import { ApiError, ValidationError } from '../../src/lib/errors'; console.log("Manual PIS Tests are running") @@ -30,7 +30,7 @@ console.log("Server is reachable. Running tests...") const {data, producedAt} = await api.pis.getByStartEndCrs(start, end); console.log('\n---Response Success---'); console.log(`Data Produced At: ${producedAt.toLocaleTimeString()}`); - + console.log(`Number of entries in response: ${data.length}`); console.log(JSON.stringify(data)); } catch (err) { if (err instanceof ApiError) { @@ -38,12 +38,43 @@ console.log("Server is reachable. Running tests...") console.error(`Code: ${err.code}`); console.error(`Msg: ${err.message}`); console.error(`Status: ${err.status}`); + } else if (err instanceof ValidationError) { + console.error('\n--- Local Validation Error ---'); + console.error(`Error Field: ${err.field}`); + console.error(`Message: ${err.reason}`); + console.error(`Request not sent`); } else { console.error(`Unknown Error: ${err}`) } } } +// Test PIS Code +{ + const code = 5001; + console.log(`Querying PIS Code: ${code}`); + try { + const {data, producedAt} = await api.pis.getByCode(code.toString()); + console.log("\n--- Response Success ---"); + console.log(`Data produced at: ${producedAt.toLocaleString()}`); + console.log(`Number of results: ${data.length}`); + console.log(JSON.stringify(data)); + } catch (err) { + if (err instanceof ApiError) { + console.error('\n--- API Error ---'); + console.error(`Code: ${err.code}`); + console.error(`Msg: ${err.message}`); + console.error(`Status: ${err.status}`); + } else if (err instanceof ValidationError) { + console.error('\n--- Local Validation Error ---'); + console.error(`Error Field: ${err.field}`); + console.error(`Message: ${err.reason}`); + console.error(`Request not sent`); + } else { + console.error(`Unknown Error: ${err}`) + } + } +} }; main(); \ No newline at end of file