@proofkit/fmodata
Version:
FileMaker OData API client
445 lines (444 loc) • 15.1 kB
JavaScript
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