Compare commits
4 Commits
4059082416
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 16bb7d3510 | |||
| edca2698a7 | |||
| 4fd95189ce | |||
| 4f13309cdc |
@@ -6,6 +6,7 @@
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": ["dist"],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -13,7 +14,9 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "tsc",
|
||||
"test-pis": "NODE_TLS_REJECT_UNAUTHORIZED=0 tsx tests/manual/pis.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -24,8 +27,10 @@
|
||||
],
|
||||
"author": "Frederick Boniface",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@owlboard/api-schema-types": "^3.0.1-alpha3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@owlboard/api-schema-types": "^3.0.1-alpha3",
|
||||
"@types/node": "^25.3.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.21.0",
|
||||
|
||||
11
src/app.d.ts
vendored
Normal file
11
src/app.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { OwlBoardClient } from "./lib/client";
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
interface Locals {
|
||||
api: OwlBoardClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -8,16 +8,36 @@ export interface ApiResult<T> {
|
||||
}
|
||||
|
||||
export class BaseClient {
|
||||
protected readonly baseUrl: string;
|
||||
|
||||
constructor(
|
||||
protected readonly baseUrl: string,
|
||||
baseUrl: string,
|
||||
protected readonly apiKey?: string
|
||||
) {}
|
||||
) {
|
||||
const root = baseUrl.replace(/\/$/, '');
|
||||
this.baseUrl = root.endsWith('/api/v3')
|
||||
? root
|
||||
: `${root}/api/v3`;
|
||||
}
|
||||
|
||||
public async ping(): Promise<boolean> {
|
||||
try {
|
||||
await fetch(this.baseUrl, {
|
||||
method: 'HEAD',
|
||||
signal: AbortSignal.timeout(3000)
|
||||
});
|
||||
return true
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the Envelope logic
|
||||
*/
|
||||
public async request<T>(path: string, options: RequestInit = {}): Promise<ApiResult<T>> {
|
||||
const url = `${this.baseUrl}${path}`;
|
||||
console.debug(`[API DEBUG] Calling: ${url}`);
|
||||
|
||||
const headers = new Headers(options.headers);
|
||||
headers.set('Content-Type', 'application/json');
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
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 { 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>> {
|
||||
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")
|
||||
}
|
||||
|
||||
const path = `/pis/route/${encodeURIComponent(startCrs.toUpperCase())}/${encodeURIComponent(endCrs.toUpperCase())}`;
|
||||
|
||||
return this.client.request<ApiPisObject.PisObjects>(path, {
|
||||
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',
|
||||
})
|
||||
}
|
||||
}
|
||||
80
tests/manual/pis.ts
Normal file
80
tests/manual/pis.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { OwlBoardClient } from '../../src/lib/client';
|
||||
import { ApiError, ValidationError } from '../../src/lib/errors';
|
||||
|
||||
console.log("Manual PIS Tests are running")
|
||||
|
||||
const TEST_SERVER: string = "https://owlboard.rke2-gw.svc.fjla.net"
|
||||
console.log(`Using TEST_SERVER: ${TEST_SERVER}`)
|
||||
|
||||
const api = new OwlBoardClient(TEST_SERVER);
|
||||
|
||||
async function main() {
|
||||
// Test server reachability
|
||||
console.log("Testing server reachability")
|
||||
if (!(await api.ping())) {
|
||||
console.error('Server not reachable. Check it is running and is reachable at the provided address')
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Server is reachable. Running tests...")
|
||||
|
||||
// Run code here.
|
||||
|
||||
{
|
||||
// Test PIS Route
|
||||
const start = 'bri';
|
||||
const end = 'svb';
|
||||
|
||||
console.log(`Querying PIS Route: ${start} > ${end}...`);
|
||||
try {
|
||||
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) {
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
Reference in New Issue
Block a user