UNPKG

@proofkit/fmodata

Version:

FileMaker OData API client

445 lines (444 loc) 15.1 kB
import { ValidationError, RecordCountMismatchError, ResponseStructureError } from "./errors.js"; async function validateAndTransformInput(data, inputSchema) { if (!inputSchema) { return data; } const transformedData = { ...data }; const allIssues = []; const failedFields = []; for (const [fieldName, fieldSchema] of Object.entries(inputSchema)) { if (!fieldSchema) { continue; } if (fieldName in data) { const inputValue = data[fieldName]; try { let result = fieldSchema["~standard"].validate(inputValue); if (result instanceof Promise) { result = await result; } if (result.issues) { for (const issue of result.issues) { allIssues.push({ ...issue, path: issue.path ? [fieldName, ...issue.path] : [fieldName] }); } failedFields.push(fieldName); continue; } transformedData[fieldName] = result.value; } catch (error) { if (error instanceof ValidationError) { for (const issue of error.issues) { allIssues.push({ ...issue, path: issue.path ? [fieldName, ...issue.path] : [fieldName] }); } } else { allIssues.push({ message: error instanceof Error ? error.message : String(error), path: [fieldName] }); } failedFields.push(fieldName); } } } if (allIssues.length > 0) { throw new ValidationError( `Input validation failed for field${failedFields.length > 1 ? "s" : ""} '${failedFields.join("', '")}'`, allIssues, { field: failedFields[0] } ); } return transformedData; } async function validateRecord(record, schema, selectedFields, expandConfigs, includeSpecialColumns) { var _a, _b, _c, _d; const { "@id": id, "@editLink": editLink, ...rest } = record; const metadata = {}; if (id) { metadata["@id"] = id; } if (editLink) { metadata["@editLink"] = editLink; } if (!schema) { const { ROWID: ROWID2, ROWMODID: ROWMODID2, ...restWithoutSystemFields2 } = rest; const specialColumns2 = {}; if (includeSpecialColumns) { if (ROWID2 !== void 0) { specialColumns2.ROWID = ROWID2; } if (ROWMODID2 !== void 0) { specialColumns2.ROWMODID = ROWMODID2; } } return { valid: true, data: { ...restWithoutSystemFields2, ...specialColumns2, ...metadata } }; } const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest; const specialColumns = {}; if (includeSpecialColumns) { if (ROWID !== void 0) { specialColumns.ROWID = ROWID; } if (ROWMODID !== void 0) { specialColumns.ROWMODID = ROWMODID; } } if (selectedFields && selectedFields.length > 0) { const validatedRecord2 = {}; const allIssues2 = []; const failedFields2 = []; for (const field of selectedFields) { const fieldName = String(field); const fieldSchema = schema[fieldName]; if (fieldSchema) { const input = rest[fieldName]; try { let result = fieldSchema["~standard"].validate(input); if (result instanceof Promise) { result = await result; } if (result.issues) { for (const issue of result.issues) { allIssues2.push({ ...issue, path: issue.path ? [fieldName, ...issue.path] : [fieldName] }); } failedFields2.push(fieldName); continue; } validatedRecord2[fieldName] = result.value; } catch (originalError) { if (originalError instanceof ValidationError) { for (const issue of originalError.issues) { allIssues2.push({ ...issue, path: issue.path ? [fieldName, ...issue.path] : [fieldName] }); } } else { allIssues2.push({ message: originalError instanceof Error ? originalError.message : String(originalError), path: [fieldName] }); } failedFields2.push(fieldName); } } else { if (fieldName === "ROWID" || fieldName === "ROWMODID") { if (fieldName === "ROWID" && ROWID !== void 0) { validatedRecord2[fieldName] = ROWID; } else if (fieldName === "ROWMODID" && ROWMODID !== void 0) { validatedRecord2[fieldName] = ROWMODID; } } else { validatedRecord2[fieldName] = rest[fieldName]; } } } if (allIssues2.length > 0) { return { valid: false, error: new ValidationError( `Validation failed for field${failedFields2.length > 1 ? "s" : ""} '${failedFields2.join("', '")}'`, allIssues2, { field: failedFields2[0], value: record } ) }; } if (expandConfigs && expandConfigs.length > 0) { for (const expandConfig of expandConfigs) { const expandValue = rest[expandConfig.relation]; if (expandValue === void 0) { if (Array.isArray(rest.error) && rest.error.length > 0) { const errorDetail = (_a = rest.error[0]) == null ? void 0 : _a.error; if (errorDetail == null ? void 0 : errorDetail.message) { const errorMessage = errorDetail.message; const isRelatedToExpand = errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) || ((_b = expandConfig.selectedFields) == null ? void 0 : _b.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase()))); if (isRelatedToExpand) { return { valid: false, error: new ValidationError( `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`, [], { field: expandConfig.relation } ) }; } } } } else { if (Array.isArray(expandValue)) { const validatedExpandedItems = []; for (let i = 0; i < expandValue.length; i++) { const item = expandValue[i]; const itemValidation = await validateRecord( item, expandConfig.targetSchema, expandConfig.selectedFields, expandConfig.nestedExpands, includeSpecialColumns ); if (!itemValidation.valid) { return { valid: false, error: new ValidationError( `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`, itemValidation.error.issues, { field: expandConfig.relation, cause: itemValidation.error.cause } ) }; } validatedExpandedItems.push(itemValidation.data); } validatedRecord2[expandConfig.relation] = validatedExpandedItems; } else { const itemValidation = await validateRecord( expandValue, expandConfig.targetSchema, expandConfig.selectedFields, expandConfig.nestedExpands, includeSpecialColumns ); if (!itemValidation.valid) { return { valid: false, error: new ValidationError( `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`, itemValidation.error.issues, { field: expandConfig.relation, cause: itemValidation.error.cause } ) }; } validatedRecord2[expandConfig.relation] = itemValidation.data; } } } } return { valid: true, data: { ...validatedRecord2, ...specialColumns, ...metadata } }; } const validatedRecord = { ...restWithoutSystemFields }; const allIssues = []; const failedFields = []; for (const [fieldName, fieldSchema] of Object.entries(schema)) { if (!fieldSchema) { continue; } const input = rest[fieldName]; try { let result = fieldSchema["~standard"].validate(input); if (result instanceof Promise) { result = await result; } if (result.issues) { for (const issue of result.issues) { allIssues.push({ ...issue, path: issue.path ? [fieldName, ...issue.path] : [fieldName] }); } failedFields.push(fieldName); continue; } validatedRecord[fieldName] = result.value; } catch (originalError) { if (originalError instanceof ValidationError) { for (const issue of originalError.issues) { allIssues.push({ ...issue, path: issue.path ? [fieldName, ...issue.path] : [fieldName] }); } } else { allIssues.push({ message: originalError instanceof Error ? originalError.message : String(originalError), path: [fieldName] }); } failedFields.push(fieldName); } } if (allIssues.length > 0) { return { valid: false, error: new ValidationError( `Validation failed for field${failedFields.length > 1 ? "s" : ""} '${failedFields.join("', '")}'`, allIssues, { field: failedFields[0], value: record } ) }; } if (expandConfigs && expandConfigs.length > 0) { for (const expandConfig of expandConfigs) { const expandValue = rest[expandConfig.relation]; if (expandValue === void 0) { if (Array.isArray(rest.error) && rest.error.length > 0) { const errorDetail = (_c = rest.error[0]) == null ? void 0 : _c.error; if (errorDetail == null ? void 0 : errorDetail.message) { const errorMessage = errorDetail.message; const isRelatedToExpand = errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) || ((_d = expandConfig.selectedFields) == null ? void 0 : _d.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase()))); if (isRelatedToExpand) { return { valid: false, error: new ValidationError( `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`, [], { field: expandConfig.relation } ) }; } } } } else { if (Array.isArray(expandValue)) { const validatedExpandedItems = []; for (let i = 0; i < expandValue.length; i++) { const item = expandValue[i]; const itemValidation = await validateRecord( item, expandConfig.targetSchema, expandConfig.selectedFields, expandConfig.nestedExpands, includeSpecialColumns ); if (!itemValidation.valid) { return { valid: false, error: new ValidationError( `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`, itemValidation.error.issues, { field: expandConfig.relation, cause: itemValidation.error.cause } ) }; } validatedExpandedItems.push(itemValidation.data); } validatedRecord[expandConfig.relation] = validatedExpandedItems; } else { const itemValidation = await validateRecord( expandValue, expandConfig.targetSchema, expandConfig.selectedFields, expandConfig.nestedExpands, includeSpecialColumns ); if (!itemValidation.valid) { return { valid: false, error: new ValidationError( `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`, itemValidation.error.issues, { field: expandConfig.relation, cause: itemValidation.error.cause } ) }; } validatedRecord[expandConfig.relation] = itemValidation.data; } } } } return { valid: true, data: { ...validatedRecord, ...specialColumns, ...metadata } }; } async function validateListResponse(response, schema, selectedFields, expandConfigs, includeSpecialColumns) { if (!response || typeof response !== "object") { return { valid: false, error: new ResponseStructureError("an object", response) }; } const { "@context": context, value, ..._rest } = response; if (!Array.isArray(value)) { return { valid: false, error: new ResponseStructureError("'value' property to be an array", value) }; } const validatedRecords = []; for (const record of value) { const validation = await validateRecord(record, schema, selectedFields, expandConfigs, includeSpecialColumns); if (!validation.valid) { return { valid: false, error: validation.error }; } validatedRecords.push(validation.data); } return { valid: true, data: validatedRecords }; } async function validateSingleResponse(response, schema, selectedFields, expandConfigs, mode = "maybe", includeSpecialColumns) { var _a; if (response.value && Array.isArray(response.value) && response.value.length > 1) { return { valid: false, error: new RecordCountMismatchError(mode === "exact" ? "one" : "at-most-one", response.value.length) }; } if (!response || response.value && response.value.length === 0) { if (mode === "exact") { return { valid: false, error: new RecordCountMismatchError("one", 0) }; } return { valid: true, data: null }; } const record = ((_a = response.value) == null ? void 0 : _a[0]) ?? response; const validation = await validateRecord(record, schema, selectedFields, expandConfigs, includeSpecialColumns); if (!validation.valid) { return validation; } return { valid: true, data: validation.data }; } export { validateAndTransformInput, validateListResponse, validateRecord, validateSingleResponse }; //# sourceMappingURL=validation.js.map