import { string } from 'yup'; import type { ObjectSchema, Schema, ValidateOptions } from 'yup'; import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError'; import { createErrorMessage } from '../../util/errors/ErrorUtil'; import { isUrl } from '../../util/StringUtil'; import type { Json } from './InteractionUtil'; import Dict = NodeJS.Dict; // The builtin `url` validator of `yup` does not support localhost URLs, so we create a custom one here. // The reason for having a URL validator on the WebID is to prevent us from generating invalid ACL, // which would break the pod creation causing us to have an incomplete pod. export const URL_SCHEMA = string().trim().optional().test({ name: 'url', message: (value): string => `"${value.value}" is not a valid URL`, test(value): boolean { if (!value) { return true; } return isUrl(value); }, }); function isObjectSchema(schema: Schema): schema is ObjectSchema { return schema.type === 'object'; } // `T` can't extend Schema since it could also be a Reference, which is a type `yup` doesn't export type SchemaType = T extends ObjectSchema ? ObjectType : { required: boolean; type: string }; // The type of the fields in an object schema type FieldType> = T extends { fields: Record } ? R : never; // Simplified type we use to represent yup objects type ObjectType> = { required: boolean; type: 'object'; fields: {[ K in FieldType ]: SchemaType }}; /** * Recursive function used when generating yup schema representations. */ function parseSchemaDescription(schema: T): SchemaType { const result: Dict = { required: !schema.spec.optional, type: schema.type }; if (isObjectSchema(schema)) { result.fields = {}; for (const [ field, description ] of Object.entries(schema.fields)) { // We never use references so this cast is fine result.fields[field] = parseSchemaDescription(description as Schema); } } return result as SchemaType; } /** * Generates a simplified representation of a yup schema. */ export function parseSchema>(schema: T): Pick, 'fields'> { const result = parseSchemaDescription(schema); return { fields: result.fields }; } /** * Same functionality as the yup validate function, but throws a {@link BadRequestHttpError} if there is an error. */ export async function validateWithError>(schema: T, data: unknown, options?: ValidateOptions): Promise { try { return await schema.validate(data, options); } catch (error: unknown) { throw new BadRequestHttpError(createErrorMessage(error)); } }