diff --git a/libs/client/package.json b/libs/client/package.json index cddbd2f..e372661 100644 --- a/libs/client/package.json +++ b/libs/client/package.json @@ -1,7 +1,7 @@ { "name": "@fal-ai/serverless-client", "description": "The fal serverless JS/TS client", - "version": "0.3.1", + "version": "0.3.2", "license": "MIT", "repository": { "type": "git", diff --git a/libs/client/src/index.ts b/libs/client/src/index.ts index 6bbf841..14a832b 100644 --- a/libs/client/src/index.ts +++ b/libs/client/src/index.ts @@ -4,4 +4,4 @@ export { withMiddleware, withProxy } from './middleware'; export type { RequestMiddleware } from './middleware'; export { ApiError, ValidationError } from './response'; export type { ResponseHandler } from './response'; -export type { QueueStatus } from './types'; +export type { QueueStatus, ValidationErrorInfo } from './types'; diff --git a/libs/client/src/response.ts b/libs/client/src/response.ts index cf9cf6b..f972d94 100644 --- a/libs/client/src/response.ts +++ b/libs/client/src/response.ts @@ -20,20 +20,45 @@ export class ApiError extends Error { } } -export class ValidationError extends ApiError { +type ValidationErrorBody = { + detail: ValidationErrorInfo[]; +}; + +export class ValidationError extends ApiError { constructor(args: ApiErrorArgs) { super(args); 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( response: Response ): Promise { const { status, statusText } = response; - const contentType = response.headers.get('Content-Type'); + const contentType = response.headers.get('Content-Type') ?? ""; if (!response.ok) { - if (contentType?.includes('application/json')) { + if (contentType.includes('application/json')) { const body = await response.json(); const ErrorType = status === 422 ? ValidationError : ApiError; throw new ErrorType({ @@ -44,13 +69,13 @@ export async function defaultResponseHandler( } throw new ApiError({ message: `HTTP ${status}: ${statusText}`, status }); } - if (contentType?.includes('application/json')) { + if (contentType.includes('application/json')) { return response.json() as Promise; } - if (contentType?.includes('text/html')) { + if (contentType.includes('text/html')) { return response.text() as Promise; } - if (contentType?.includes('application/octet-stream')) { + if (contentType.includes('application/octet-stream')) { return response.arrayBuffer() as Promise; } // TODO convert to either number or bool automatically diff --git a/libs/proxy/package.json b/libs/proxy/package.json index 656aad1..09ec2b0 100644 --- a/libs/proxy/package.json +++ b/libs/proxy/package.json @@ -1,6 +1,6 @@ { "name": "@fal-ai/serverless-proxy", - "version": "0.3.4", + "version": "0.3.5", "license": "MIT", "repository": { "type": "git", diff --git a/libs/proxy/src/express.ts b/libs/proxy/src/express.ts index 7a2efd2..efc829c 100644 --- a/libs/proxy/src/express.ts +++ b/libs/proxy/src/express.ts @@ -19,7 +19,7 @@ export const handler: RequestHandler = async (request, response, next) => { method: request.method, respondWith: (status, data) => typeof data === 'string' - ? response.status(status).json({ details: data }) + ? response.status(status).json({ detail: data }) : response.status(status).json(data), getHeaders: () => request.headers, getHeader: (name) => request.headers[name], diff --git a/libs/proxy/src/nextjs.ts b/libs/proxy/src/nextjs.ts index ea8b9f2..4c06352 100644 --- a/libs/proxy/src/nextjs.ts +++ b/libs/proxy/src/nextjs.ts @@ -19,7 +19,7 @@ export const handler: NextApiHandler = async (request, response) => { method: request.method, respondWith: (status, data) => typeof data === 'string' - ? response.status(status).json({ details: data }) + ? response.status(status).json({ detail: data }) : response.status(status).json(data), getHeaders: () => request.headers, getHeader: (name) => request.headers[name],