import { HTTP } from './http'; export interface APIError { code: number; message?: string; detail?: string; } export function isAPIError(data: any): data is APIError { return data && (data.detail !== undefined || data.message !== undefined); } export function getErrorMessage(apiError: APIError): string { if (apiError.message) { return apiError.message; } if (apiError.detail) { return apiError.detail; } console.log('Unexpected error:', apiError); return 'Unexpected error!'; } export interface Paginated { count: 0; next: string; previous: string; results: K[]; } export interface ModelID { id?: string | number; } interface ValidationError { [key: string]: string[]; } export function isValidationError(data: any): data is ValidationError { return data && Object.keys(data).every((key) => Array.isArray(data[key])); } export function getValidationErrorMessage(data: ValidationError): string { return Object.keys(data) .map((key) => `${key}: ${data[key].join(', ')}`) .join('\n'); } export abstract class ModelClient { version: string; client: HTTP; memo: { [id: string]: Promise }; abstract pluralName: string; constructor(client: HTTP, version = 'v1') { this.version = version; this.client = client; this.memo = {}; } isPaginated(data: any): Paginated { return ( data.count !== undefined && data.previous !== undefined && data.next !== undefined && data.results.map(this.validate.bind(this)) ); } validate(data: any): Model { if (isAPIError(data)) { throw new Error(getErrorMessage(data)); } if (isValidationError(data)) { throw new Error(getValidationErrorMessage(data)); } return data as Model; } clearMemo(): void { this.memo = {}; } async getNoMemo(id: string): Promise { const url = `/${this.version}/${this.pluralName}/${id}`; const response = await this.client.get(url); return this.validate(response.data); } async get(id: string): Promise { try { if (!this.memo[id]) { this.memo[id] = this.getNoMemo(id); } return await this.memo[id]; } catch (e) { this.clearMemo(); throw e; } } async listPage(url: string, queryParams?: any): Promise> { const options = queryParams ? { params: { ...queryParams } } : {}; const response = await this.client.get(url, options); if (!this.isPaginated(response.data)) { throw new Error(`Unexpected response from ${url}`); } return response.data; } async list(queryParams?: any): Promise { const response = await this.listPage( `/${this.version}/${this.pluralName}`, queryParams ); const results = response.results; if (response.next) { const nextPage = await this.listPage(response.next); return [...results, ...nextPage.results]; } return results; } async create(model: NewModel): Promise { const url = `/${this.version}/${this.pluralName}`; const response = await this.client.post(url, model); if (isValidationError(response.data)) { throw new Error(getValidationErrorMessage(response.data)); } return this.validate(response.data); } async update(model: Model): Promise { const url = `/${this.version}/${this.pluralName}/${model.id}`; const response = await this.client.patch(url, model); if (isValidationError(response.data)) { throw new Error(getValidationErrorMessage(response.data)); } this.clearMemo(); return this.validate(response.data); } async del(id: string): Promise { const url = `/${this.version}/${this.pluralName}/${id}`; const response = await this.client.delete(url); if (isAPIError(response.data)) { throw new Error(getErrorMessage(response.data)); } this.clearMemo(); return response.data; } }