Add data normalization and validation. Add validation error error type.
Add lookup by code method to pis class.
This commit is contained in:
@@ -15,3 +15,10 @@ export class ApiError extends Error {
|
|||||||
this.code = errorObj.code || ("SERVER" as ApiErrorCode);
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/lib/validation.ts
Normal file
47
src/lib/validation.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 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 {
|
export class PisModule {
|
||||||
constructor(private client: BaseClient) {}
|
constructor(private client: BaseClient) {}
|
||||||
|
|
||||||
async getByStartEndCrs(startCrs: string, endCrs: string): Promise<ApiResult<ApiPisObject.PisObjects>> {
|
async getByStartEndCrs(startCrs: string, endCrs: string): Promise<ApiResult<ApiPisObject.PisObjects[]>> {
|
||||||
const path = `/pis/route/${encodeURIComponent(startCrs.toLowerCase())}/${encodeURIComponent(endCrs.toLowerCase())}`;
|
if (!IsValidCrs(startCrs)) {
|
||||||
|
throw new ValidationError("startCrs", "Invalid CRS Format")
|
||||||
|
}
|
||||||
|
if (!IsValidCrs(endCrs)) {
|
||||||
|
throw new ValidationError("endCrs", "Invalid CRS Format")
|
||||||
|
}
|
||||||
|
|
||||||
return this.client.request<ApiPisObject.PisObjects>(path, {
|
const path = `/pis/route/${encodeURIComponent(startCrs.toUpperCase())}/${encodeURIComponent(endCrs.toUpperCase())}`;
|
||||||
|
|
||||||
|
return this.client.request<ApiPisObject.PisObjects[]>(path, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getByCode(code: string): Promise<ApiResult<ApiPisObject.PisObjects[]>> {
|
||||||
|
if (!IsValidPis(code)) {
|
||||||
|
throw new ValidationError("code", "Invalid PIS Code Format")
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = `/pis/code/${encodeURIComponent(code)}`;
|
||||||
|
|
||||||
|
return this.client.request<ApiPisObject.PisObjects[]>(path, {
|
||||||
|
method: 'GET',
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { OwlBoardClient } from '../../src/lib/client';
|
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")
|
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);
|
const {data, producedAt} = await api.pis.getByStartEndCrs(start, end);
|
||||||
console.log('\n---Response Success---');
|
console.log('\n---Response Success---');
|
||||||
console.log(`Data Produced At: ${producedAt.toLocaleTimeString()}`);
|
console.log(`Data Produced At: ${producedAt.toLocaleTimeString()}`);
|
||||||
|
console.log(`Number of entries in response: ${data.length}`);
|
||||||
console.log(JSON.stringify(data));
|
console.log(JSON.stringify(data));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ApiError) {
|
if (err instanceof ApiError) {
|
||||||
@@ -38,12 +38,43 @@ console.log("Server is reachable. Running tests...")
|
|||||||
console.error(`Code: ${err.code}`);
|
console.error(`Code: ${err.code}`);
|
||||||
console.error(`Msg: ${err.message}`);
|
console.error(`Msg: ${err.message}`);
|
||||||
console.error(`Status: ${err.status}`);
|
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 {
|
} else {
|
||||||
console.error(`Unknown Error: ${err}`)
|
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();
|
main();
|
||||||
Reference in New Issue
Block a user