feat(provider-contract): define materializable L2 graph blueprints
This commit is contained in:
@@ -144,6 +144,8 @@ const EXPRESSION_KEYS = new Set([
|
||||
]);
|
||||
const GEOMETRY_KEYS = new Set([
|
||||
"type", "longitude", "latitude", "strategy", "paths", "allowedTypes", "omitIfInvalid",
|
||||
"kindPath", "polygonPointsPath", "corridorPointsPath", "radiusPath",
|
||||
"coordinateOrder", "circleSegments",
|
||||
]);
|
||||
const DERIVATION_KEYS = new Set(["kind", "rules", "default", "parameters"]);
|
||||
const TEMPLATE_KEYS = new Set([
|
||||
@@ -593,9 +595,18 @@ function validateCollectionProfileTemplate(value, path, errors) {
|
||||
if (!isPlainObject(value.retry)) {
|
||||
errors.push(`${path}.retry_must_be_object`);
|
||||
} else {
|
||||
rejectUnknownKeys(value.retry, new Set(["maxAttempts", "backoff"]), `${path}.retry`, errors);
|
||||
rejectUnknownKeys(value.retry, new Set(["maxAttempts", "backoff", "delayMs"]), `${path}.retry`, errors);
|
||||
if (!Number.isInteger(value.retry.maxAttempts) || value.retry.maxAttempts < 1 || value.retry.maxAttempts > 10) errors.push(`${path}.retry.maxAttempts_invalid`);
|
||||
if (value.retry.backoff !== "exponential_with_jitter") errors.push(`${path}.retry.backoff_invalid`);
|
||||
if (!["exponential_with_jitter", "fixed_delay"].includes(value.retry.backoff)) {
|
||||
errors.push(`${path}.retry.backoff_invalid`);
|
||||
}
|
||||
if (value.retry.backoff === "fixed_delay") {
|
||||
if (!Number.isInteger(value.retry.delayMs) || value.retry.delayMs < 100 || value.retry.delayMs > 60_000) {
|
||||
errors.push(`${path}.retry.delayMs_invalid`);
|
||||
}
|
||||
} else if (value.retry.delayMs !== undefined) {
|
||||
errors.push(`${path}.retry.delayMs_requires_fixed_delay`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,13 +683,29 @@ function validateMappingContract(value, path, errors) {
|
||||
errors.push(`${path}.fact.geometry.point_shape_invalid`);
|
||||
}
|
||||
} else if (value.fact.geometry.type === "GeoJSON") {
|
||||
if (value.fact.geometry.strategy !== "gelios_geozone_v1") errors.push(`${path}.fact.geometry.strategy_invalid`);
|
||||
requiredSourcePathArray(value.fact.geometry.paths, `${path}.fact.geometry.paths`, errors);
|
||||
if (value.fact.geometry.strategy !== "bounded_zone_geometry_v1") errors.push(`${path}.fact.geometry.strategy_invalid`);
|
||||
for (const key of ["kindPath", "polygonPointsPath", "corridorPointsPath", "radiusPath"]) {
|
||||
if (!isCanonicalSourcePath(value.fact.geometry[key])) {
|
||||
errors.push(`${path}.fact.geometry.${key}_invalid`);
|
||||
}
|
||||
}
|
||||
if (!["latitude_longitude", "longitude_latitude"].includes(value.fact.geometry.coordinateOrder)) {
|
||||
errors.push(`${path}.fact.geometry.coordinateOrder_invalid`);
|
||||
}
|
||||
if (!Number.isInteger(value.fact.geometry.circleSegments)
|
||||
|| value.fact.geometry.circleSegments < 16
|
||||
|| value.fact.geometry.circleSegments > 256) {
|
||||
errors.push(`${path}.fact.geometry.circleSegments_invalid`);
|
||||
}
|
||||
if (!sameSet(value.fact.geometry.allowedTypes, ["Polygon", "MultiPolygon"])) {
|
||||
errors.push(`${path}.fact.geometry.allowedTypes_must_be_polygon_or_multipolygon`);
|
||||
}
|
||||
if (value.fact.geometry.omitIfInvalid !== false) errors.push(`${path}.fact.geometry.omitIfInvalid_must_be_false`);
|
||||
if (value.fact.geometry.longitude !== undefined || value.fact.geometry.latitude !== undefined) {
|
||||
if (
|
||||
value.fact.geometry.longitude !== undefined
|
||||
|| value.fact.geometry.latitude !== undefined
|
||||
|| value.fact.geometry.paths !== undefined
|
||||
) {
|
||||
errors.push(`${path}.fact.geometry.geojson_shape_invalid`);
|
||||
}
|
||||
} else {
|
||||
@@ -900,6 +927,9 @@ function validateExpressionAgainstFieldContract(expression, contract, derivation
|
||||
errors.push(`${path}_required_but_mapping_can_omit_invalid`);
|
||||
}
|
||||
if (expression.paths !== undefined) {
|
||||
if (contract.required === false && expression.omitIfMissing !== true) {
|
||||
errors.push(`${path}_optional_source_field_must_omit_missing`);
|
||||
}
|
||||
const expectedCoerce = new Map([["string", "string"], ["number", "number"], ["boolean", "boolean"]]).get(contract.type);
|
||||
if (expectedCoerce && expression.coerce !== expectedCoerce) errors.push(`${path}.coerce_must_match_field_contract`);
|
||||
if (Array.isArray(contract.enum)) errors.push(`${path}.enum_field_must_use_constant_or_derivation`);
|
||||
@@ -1041,8 +1071,11 @@ function mappingSourcePaths(value, found = []) {
|
||||
if (isCanonicalSourcePath(path)) found.push(path);
|
||||
});
|
||||
}
|
||||
for (const key of ["kindPath", "polygonPointsPath", "corridorPointsPath", "radiusPath"]) {
|
||||
if (isCanonicalSourcePath(value[key])) found.push(value[key]);
|
||||
}
|
||||
Object.entries(value).forEach(([key, item]) => {
|
||||
if (key !== "paths") mappingSourcePaths(item, found);
|
||||
if (key !== "paths" && !key.endsWith("Path")) mappingSourcePaths(item, found);
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user