fix: validation error body format (#17)

* fix: validation error body format

* chore: bump proxy version

* chore: bump client version
This commit is contained in:
Daniel Rochetti 2023-10-10 23:19:29 -07:00 committed by GitHub
parent e02118f43f
commit 0e3cb85939
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 36 additions and 11 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "@fal-ai/serverless-client", "name": "@fal-ai/serverless-client",
"description": "The fal serverless JS/TS client", "description": "The fal serverless JS/TS client",
"version": "0.3.1", "version": "0.3.2",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -4,4 +4,4 @@ export { withMiddleware, withProxy } from './middleware';
export type { RequestMiddleware } from './middleware'; export type { RequestMiddleware } from './middleware';
export { ApiError, ValidationError } from './response'; export { ApiError, ValidationError } from './response';
export type { ResponseHandler } from './response'; export type { ResponseHandler } from './response';
export type { QueueStatus } from './types'; export type { QueueStatus, ValidationErrorInfo } from './types';

View File

@ -20,20 +20,45 @@ export class ApiError<Body> extends Error {
} }
} }
export class ValidationError extends ApiError<ValidationErrorInfo[]> { type ValidationErrorBody = {
detail: ValidationErrorInfo[];
};
export class ValidationError extends ApiError<ValidationErrorBody> {
constructor(args: ApiErrorArgs) { constructor(args: ApiErrorArgs) {
super(args); super(args);
this.name = 'ValidationError'; this.name = 'ValidationError';
} }
get fieldErrors(): ValidationErrorInfo[] {
// NOTE: this is a hack to support both FastAPI/Pydantic errors
// and some custom 422 errors that might not be in the Pydantic format.
if (typeof this.body.detail === 'string') {
return [
{
loc: ['body'],
msg: this.body.detail,
type: 'value_error',
},
];
}
return this.body.detail || [];
}
getFieldErrors(field: string): ValidationErrorInfo[] {
return this.fieldErrors.filter(
(error) => error.loc[error.loc.length - 1] === field
);
}
} }
export async function defaultResponseHandler<Output>( export async function defaultResponseHandler<Output>(
response: Response response: Response
): Promise<Output> { ): Promise<Output> {
const { status, statusText } = response; const { status, statusText } = response;
const contentType = response.headers.get('Content-Type'); const contentType = response.headers.get('Content-Type') ?? "";
if (!response.ok) { if (!response.ok) {
if (contentType?.includes('application/json')) { if (contentType.includes('application/json')) {
const body = await response.json(); const body = await response.json();
const ErrorType = status === 422 ? ValidationError : ApiError; const ErrorType = status === 422 ? ValidationError : ApiError;
throw new ErrorType({ throw new ErrorType({
@ -44,13 +69,13 @@ export async function defaultResponseHandler<Output>(
} }
throw new ApiError({ message: `HTTP ${status}: ${statusText}`, status }); throw new ApiError({ message: `HTTP ${status}: ${statusText}`, status });
} }
if (contentType?.includes('application/json')) { if (contentType.includes('application/json')) {
return response.json() as Promise<Output>; return response.json() as Promise<Output>;
} }
if (contentType?.includes('text/html')) { if (contentType.includes('text/html')) {
return response.text() as Promise<Output>; return response.text() as Promise<Output>;
} }
if (contentType?.includes('application/octet-stream')) { if (contentType.includes('application/octet-stream')) {
return response.arrayBuffer() as Promise<Output>; return response.arrayBuffer() as Promise<Output>;
} }
// TODO convert to either number or bool automatically // TODO convert to either number or bool automatically

View File

@ -1,6 +1,6 @@
{ {
"name": "@fal-ai/serverless-proxy", "name": "@fal-ai/serverless-proxy",
"version": "0.3.4", "version": "0.3.5",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -19,7 +19,7 @@ export const handler: RequestHandler = async (request, response, next) => {
method: request.method, method: request.method,
respondWith: (status, data) => respondWith: (status, data) =>
typeof data === 'string' typeof data === 'string'
? response.status(status).json({ details: data }) ? response.status(status).json({ detail: data })
: response.status(status).json(data), : response.status(status).json(data),
getHeaders: () => request.headers, getHeaders: () => request.headers,
getHeader: (name) => request.headers[name], getHeader: (name) => request.headers[name],

View File

@ -19,7 +19,7 @@ export const handler: NextApiHandler = async (request, response) => {
method: request.method, method: request.method,
respondWith: (status, data) => respondWith: (status, data) =>
typeof data === 'string' typeof data === 'string'
? response.status(status).json({ details: data }) ? response.status(status).json({ detail: data })
: response.status(status).json(data), : response.status(status).json(data),
getHeaders: () => request.headers, getHeaders: () => request.headers,
getHeader: (name) => request.headers[name], getHeader: (name) => request.headers[name],