///|
/// A validated collection of versioned JSONL mapping profiles.
///
/// Catalog order is registration order. Resolving always requires an exact
/// profile ID and version so a schema change cannot silently select another
/// mapping.
pub struct ProfileCatalog {
profiles : Array[JsonlMappingProfile]
} derive(Debug, Eq)
///|
/// A profile cannot be registered or resolved safely.
pub(all) suberror ProfileCatalogError {
InvalidProfile(field~ : String, reason~ : String)
DuplicateProfile(id~ : String, version~ : String)
MissingProfile(id~ : String, version~ : String)
} derive(Debug, Eq)
///|
fn validate_catalog_profile(
profile : JsonlMappingProfile,
) -> Unit raise ProfileCatalogError {
try validate_jsonl_profile(profile) catch {
ProfileMapError::InvalidProfile(field~, reason~) =>
raise ProfileCatalogError::InvalidProfile(field~, reason~)
ProfileMapError::MissingValue(line_number~, path~) =>
raise ProfileCatalogError::InvalidProfile(
field=path,
reason="profile validation unexpectedly required an evidence line \{line_number}",
)
ProfileMapError::InvalidValue(line_number~, path~, value~) =>
raise ProfileCatalogError::InvalidProfile(
field=path,
reason="profile validation unexpectedly required an evidence value at line \{line_number}: \{value}",
)
} noraise {
_ => ()
}
}
///|
fn has_profile(
profiles : Array[JsonlMappingProfile],
id : String,
version : String,
) -> Bool {
for profile in profiles {
if profile.id == id && profile.version == version {
return true
}
}
false
}
///|
fn copy_profiles(
profiles : Array[JsonlMappingProfile],
) -> Array[JsonlMappingProfile] {
let copied : Array[JsonlMappingProfile] = []
for profile in profiles {
copied.push(profile)
}
copied
}
///|
fn validate_catalog_entries(
profiles : Array[JsonlMappingProfile],
) -> Unit raise ProfileCatalogError {
let validated : Array[JsonlMappingProfile] = []
for profile in profiles {
validate_catalog_profile(profile)
if has_profile(validated, profile.id, profile.version) {
raise ProfileCatalogError::DuplicateProfile(
id=profile.id,
version=profile.version,
)
}
validated.push(profile)
}
}
///|
/// Creates a catalog after validating every profile and its exact identity.
pub fn new_profile_catalog(
profiles : Array[JsonlMappingProfile],
) -> ProfileCatalog raise ProfileCatalogError {
validate_catalog_entries(profiles)
{ profiles, }
}
///|
/// Registers one new profile without replacing an existing ID/version pair.
pub fn register_profile(
catalog : ProfileCatalog,
profile : JsonlMappingProfile,
) -> ProfileCatalog raise ProfileCatalogError {
validate_catalog_profile(profile)
if has_profile(catalog.profiles, profile.id, profile.version) {
raise ProfileCatalogError::DuplicateProfile(
id=profile.id,
version=profile.version,
)
}
let profiles = copy_profiles(catalog.profiles)
profiles.push(profile)
{ profiles, }
}
///|
/// Resolves a profile by exact ID and version.
pub fn resolve_profile(
catalog : ProfileCatalog,
id : String,
version : String,
) -> JsonlMappingProfile raise ProfileCatalogError {
for profile in catalog.profiles {
if profile.id == id && profile.version == version {
return profile
}
}
raise ProfileCatalogError::MissingProfile(id~, version~)
}
///|
/// Returns profiles in their stable registration order.
pub fn list_profiles(catalog : ProfileCatalog) -> Array[JsonlMappingProfile] {
copy_profiles(catalog.profiles)
}