Add data normalization and validation. Add validation error error type.

Add lookup by code method to pis class.
This commit is contained in:
2026-02-21 19:28:26 +00:00
parent edca2698a7
commit 16bb7d3510
4 changed files with 112 additions and 6 deletions

View File

@@ -15,3 +15,10 @@ export class ApiError extends Error {
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
View 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;
}

View File

@@ -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<ApiResult<ApiPisObject.PisObjects>> {
const path = `/pis/route/${encodeURIComponent(startCrs.toLowerCase())}/${encodeURIComponent(endCrs.toLowerCase())}`;
async getByStartEndCrs(startCrs: string, endCrs: string): Promise<ApiResult<ApiPisObject.PisObjects[]>> {
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',
});
}
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',
})
}
}

View File

@@ -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();