///|
fn expect_keys(
fields : Map[String, Json],
allowed : Array[String],
location : String,
) -> Unit raise TraceError {
for key in sorted_keys(fields) {
if !allowed.contains(key) {
reject(
"unknown_manifest_field",
location,
"Unknown manifest field " +
key +
"; remove it explicitly or use a supported manifest version.",
)
}
}
}
///|
/// Read the strict v1 manifest: {"files":[paths],"sidecars":[{"path":path,"metadata":object}]}.
/// Duplicate keys are checked throughout, before any data is consumed.
pub fn DatasetIndex::from_json(text : String) -> DatasetIndex raise TraceError {
// The envelope adds three containers. Each extracted sidecar is reparsed
// at its own 64-container metadata limit during index construction.
guard parse_source("", text, container_limit=67) is Object(fields) else {
reject(
"invalid_manifest", "", "Expected an object containing files and sidecars arrays.",
)
return DatasetIndex::from_manifest([], [])
}
expect_keys(fields, ["files", "sidecars"], "")
guard fields.get("files") is Some(Array(files)) &&
fields.get("sidecars") is Some(Array(sidecars)) else {
reject(
"invalid_manifest", "", "Both files and sidecars must be arrays.",
)
return DatasetIndex::from_manifest([], [])
}
let paths = files.map(value => {
guard value is String(path) else {
reject(
"invalid_manifest", "/files", "Each data path must be a string.",
)
return ""
}
path
})
let inputs = sidecars.map(value => {
guard value is Object(entry) else {
reject(
"invalid_manifest", "/sidecars", "Each sidecar must be a path/metadata object.",
)
return SidecarInput::new("", "")
}
expect_keys(entry, ["path", "metadata"], "/sidecars")
guard entry.get("path") is Some(String(path)) &&
entry.get("metadata") is Some(Object(metadata)) else {
reject(
"invalid_manifest", "/sidecars", "Expected a string path and an object metadata field.",
)
return SidecarInput::new("", "")
}
SidecarInput::new(path, canonical_json(Json::object(metadata)))
})
DatasetIndex::from_manifest(paths, inputs)
}
///|
/// A complete deterministic v1 manifest snapshot. Unknown metadata is retained.
fn DatasetIndex::manifest_value(self : DatasetIndex) -> Json {
Json::object({
"files": Json::array(self.data_paths().map(Json::string)),
"sidecars": Json::array(
self.sidecars.map(sidecar => {
Json::object({
"path": Json::string(sidecar.selector.path),
"metadata": Json::object(sidecar.metadata),
})
}),
),
})
}
///|
pub fn DatasetIndex::to_manifest_json(self : DatasetIndex) -> String {
canonical_json(self.manifest_value())
}