Implement main class & fetch function - including robust error handling.
This commit is contained in:
parent
292cdbd069
commit
24d8173548
9
src/clients/LdbClientV2.ts
Normal file
9
src/clients/LdbClientV2.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BaseOwlBoardClient } from "./client";
|
||||||
|
|
||||||
|
export class LdbClientV2 {
|
||||||
|
private client: BaseOwlBoardClient;
|
||||||
|
|
||||||
|
constructor(client: BaseOwlBoardClient) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
}
|
9
src/clients/LocationReferenceClientV2.ts
Normal file
9
src/clients/LocationReferenceClientV2.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BaseOwlBoardClient } from "./client";
|
||||||
|
|
||||||
|
export class LocationReferenceClientV2 {
|
||||||
|
private client: BaseOwlBoardClient;
|
||||||
|
|
||||||
|
constructor(client: BaseOwlBoardClient) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
}
|
9
src/clients/MiscClientV2.ts
Normal file
9
src/clients/MiscClientV2.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BaseOwlBoardClient } from "./client";
|
||||||
|
|
||||||
|
export class MiscClientV2 {
|
||||||
|
private client: BaseOwlBoardClient;
|
||||||
|
|
||||||
|
constructor(client: BaseOwlBoardClient) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
}
|
19
src/clients/PisClientV2.ts
Normal file
19
src/clients/PisClientV2.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { BaseOwlBoardClient } from "./client";
|
||||||
|
import { ValidationError } from "../errors";
|
||||||
|
|
||||||
|
export class PisClientV2 {
|
||||||
|
private client: BaseOwlBoardClient;
|
||||||
|
|
||||||
|
constructor(client: BaseOwlBoardClient) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStopsByPis(code: string): Promise<any> {
|
||||||
|
const codeRegex = /^\d{4}$/;
|
||||||
|
if (!codeRegex.test(code)) {
|
||||||
|
throw new ValidationError("Invalid input: code must be a four-character string consisting of only numerals")
|
||||||
|
}
|
||||||
|
const path = `/api/v2/pis/byCode/${code}`
|
||||||
|
return this.client.makeRequest("GET", path);
|
||||||
|
}
|
||||||
|
}
|
9
src/clients/TrainClientV2.ts
Normal file
9
src/clients/TrainClientV2.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BaseOwlBoardClient } from "./client";
|
||||||
|
|
||||||
|
export class TrainClientV2 {
|
||||||
|
private client: BaseOwlBoardClient;
|
||||||
|
|
||||||
|
constructor(client: BaseOwlBoardClient) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
}
|
9
src/clients/UserClientV2.ts
Normal file
9
src/clients/UserClientV2.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { BaseOwlBoardClient } from "./client";
|
||||||
|
|
||||||
|
export class UserClientV2 {
|
||||||
|
private client: BaseOwlBoardClient;
|
||||||
|
|
||||||
|
constructor(client: BaseOwlBoardClient) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
}
|
96
src/clients/client.ts
Normal file
96
src/clients/client.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { version } from "../constants";
|
||||||
|
import { LdbClientV2 } from "./LdbClientV2";
|
||||||
|
import { LocationReferenceClientV2 } from "./LocationReferenceClientV2";
|
||||||
|
import { MiscClientV2 } from "./MiscClientV2";
|
||||||
|
import { PisClientV2 } from "./PisClientV2";
|
||||||
|
import { TrainClientV2 } from "./TrainClientV2";
|
||||||
|
import { UserClientV2 } from "./UserClientV2";
|
||||||
|
|
||||||
|
import { ApiError, NetworkError } from "../errors";
|
||||||
|
|
||||||
|
export class BaseOwlBoardClient {
|
||||||
|
protected baseUrl: string;
|
||||||
|
protected apiKey: string;
|
||||||
|
protected headers: Record<string, string>;
|
||||||
|
|
||||||
|
constructor(baseUrl: string, apiKey: string) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
this.headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"User-Agent": `OwlBoardTS/v${version}`,
|
||||||
|
"uuid": this.apiKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async makeRequest<T>(method: string, path: string, body?: any): Promise<T> {
|
||||||
|
const url = `${this.baseUrl}${path}`;
|
||||||
|
const options: RequestInit = {
|
||||||
|
method,
|
||||||
|
headers: this.headers,
|
||||||
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
let attempt = 0;
|
||||||
|
const retries = 3;
|
||||||
|
const delay = 500;
|
||||||
|
const retryableStatusCodes = [408, 500, 502, 503, 504];
|
||||||
|
|
||||||
|
while (attempt < retries) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
if (!response.ok) {
|
||||||
|
const responseBody = await response.text();
|
||||||
|
const apiError = new ApiError("API Request Failed", response.status, responseBody);
|
||||||
|
if (retryableStatusCodes.includes(response.status)) {
|
||||||
|
attempt++;
|
||||||
|
if (attempt >= retries) { // Max attempts reached
|
||||||
|
throw apiError;
|
||||||
|
}
|
||||||
|
// Wait before retrying
|
||||||
|
await new Promise(res => setTimeout(res, delay));
|
||||||
|
continue; // Retry
|
||||||
|
} else { // Non-retryable status codes
|
||||||
|
throw apiError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await response.json()) as T;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof TypeError) { // Indicates network errors with fetch
|
||||||
|
if (attempt < retries) {
|
||||||
|
attempt ++;
|
||||||
|
await new Promise(res => setTimeout(res, delay));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Throw network error if max tries reached handling TypeError
|
||||||
|
throw new NetworkError("Network request failed");
|
||||||
|
};
|
||||||
|
|
||||||
|
throw error; // Rethrow any other error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// This code should never be reached.
|
||||||
|
throw new Error("Unexpected error after multiple retries.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OwlBoardClientV2 extends BaseOwlBoardClient {
|
||||||
|
pis: PisClientV2;
|
||||||
|
train: TrainClientV2;
|
||||||
|
user: UserClientV2;
|
||||||
|
locationReference: LocationReferenceClientV2;
|
||||||
|
misc: MiscClientV2;
|
||||||
|
ldb: LdbClientV2;
|
||||||
|
|
||||||
|
constructor(baseUrl: string, apiKey: string) {
|
||||||
|
super(baseUrl, apiKey);
|
||||||
|
this.pis = new PisClientV2(this);
|
||||||
|
this.train = new TrainClientV2(this);
|
||||||
|
this.user = new UserClientV2(this);
|
||||||
|
this.locationReference = new LocationReferenceClientV2(this);
|
||||||
|
this.misc = new MiscClientV2(this);
|
||||||
|
this.ldb = new LdbClientV2(this);
|
||||||
|
}
|
||||||
|
}
|
1
src/constants.ts
Normal file
1
src/constants.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const version: string = "0.0.1"
|
28
src/errors.ts
Normal file
28
src/errors.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export class ApiError extends Error {
|
||||||
|
statusCode: number;
|
||||||
|
responseBody: string;
|
||||||
|
|
||||||
|
constructor(message: string, statusCode: number, responseBody: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'ApiError';
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.responseBody = responseBody;
|
||||||
|
Object.setPrototypeOf(this, ApiError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ValidationError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'ValidationError';
|
||||||
|
Object.setPrototypeOf(this, ValidationError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NetworkError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'NetworkError';
|
||||||
|
Object.setPrototypeOf(this, NetworkError.prototype);
|
||||||
|
}
|
||||||
|
}
|
@ -15,3 +15,5 @@ OwlBoardTS: An OwlBoard API Client written in TypeScript
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export { OwlBoardClientV2 } from './clients/client'
|
@ -107,6 +107,6 @@
|
|||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*.ts"],
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"],
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user