///|
pub fn value_uri(file : @ir.IRFile, name : @ir.FQName) -> String raise {
ignore(@ir.FQName::parse(name.to_canonical()))
"morphir://ir/pkg/" +
name.package_name.to_canonical() +
"?format=" +
file.format_version.to_release_string() +
"#/module/" +
name.module_name
.to_canonical()
.split("/")
.map(StringView::to_owned)
.collect()
.join("%2F") +
"/value/" +
name.local_name.to_canonical()
}
///|
priv struct Address {
package_name : @ir.Path
format : String
revision : String?
fragment : String
}
///|
/// A bounded named-node URI reader. Ordered children, workspace aliases and
/// positional guards are not supported by the value-definition consumer.
fn address(uri : String) -> Address raise {
let prefix = "morphir://ir/pkg/"
guard uri.has_prefix(prefix) else {
raise Invalid(
"metadata.invalid_node_uri", uri, "Use a canonical package Node URI",
)
}
let pieces = uri[prefix.length():].split("#").collect()
if pieces.length() != 2 {
raise Invalid(
"metadata.invalid_node_uri", uri, "Supply one semantic fragment",
)
}
let query = pieces[0].split("?").collect()
if query.length() != 2 {
raise Invalid(
"metadata.invalid_node_uri", uri, "Supply an exact format release",
)
}
let package_name = @ir.Path::parse(query[0].to_owned()) catch {
_ =>
raise Invalid(
"metadata.invalid_node_uri", uri, "Package path must use canonical Morphir names",
)
}
if package_name.to_canonical() != query[0].to_owned() {
raise Invalid("metadata.invalid_node_uri", uri, "Noncanonical package path")
}
let parameters = Map([])
for p in query[1].to_owned().split("&") {
let pair = p.split("=").collect()
if pair.length() != 2 || parameters.contains(pair[0].to_owned()) {
raise Invalid(
"metadata.invalid_node_uri", uri, "Duplicate or malformed query parameter",
)
}
parameters[pair[0].to_owned()] = pair[1].to_owned()
}
guard parameters.get("format") is Some(format) else {
raise Invalid("metadata.invalid_node_uri", uri, "Missing format")
}
ignore(@version.FormatVersion::parse(format)) catch {
_ =>
raise Invalid(
"metadata.invalid_node_uri", uri, "Use an exact three-component format release",
)
}
if parameters.length() > 2 ||
parameters.keys().any(k => k != "format" && k != "rev") {
raise Invalid(
"metadata.invalid_node_uri", uri, "Unsupported query parameter for a named declaration",
)
}
let revision = parameters.get("rev")
if revision is Some(r) &&
(!r.has_prefix("sha256:") || !digest(r[7:].to_owned())) {
raise Invalid(
"metadata.invalid_node_uri", uri, "Use rev=sha256:<64 lowercase hex digits>",
)
}
let canonical = prefix +
package_name.to_canonical() +
"?format=" +
format +
revision.map(r => "&rev=" + r).unwrap_or("") +
"#" +
pieces[1].to_owned()
if canonical != uri {
raise Invalid(
"metadata.invalid_node_uri", uri, "Use canonical query order: format, then rev",
)
}
{ package_name, format, revision, fragment: pieces[1].to_owned(), }
}
///|
fn declaration(uri : String) -> (@ir.FQName, Address) raise {
let parsed = address(uri)
let parts = parsed.fragment.split("/").collect()
if parts.length() != 5 ||
parts[0].to_owned() != "" ||
parts[1].to_owned() != "module" ||
parts[3].to_owned() != "value" {
raise Invalid(
"metadata.unsupported_subject_role", uri, "This consumer requires a ValueDefinition URI, not its body or attributes",
)
}
let module_path = parts[2]
.to_owned()
.split("%2F")
.map(StringView::to_owned)
.collect()
.join("/")
let module_name = @ir.Path::parse(module_path) catch {
_ =>
raise Invalid(
"metadata.invalid_node_uri", uri, "Invalid canonical module path",
)
}
let local_name = @ir.Name::parse(parts[4].to_owned()) catch {
_ =>
raise Invalid(
"metadata.invalid_node_uri", uri, "Invalid canonical value name",
)
}
let encoded = module_name
.to_canonical()
.split("/")
.map(StringView::to_owned)
.collect()
.join("%2F")
if encoded != parts[2].to_owned() ||
local_name.to_canonical() != parts[4].to_owned() {
raise Invalid(
"metadata.invalid_node_uri", uri, "Noncanonical fragment encoding",
)
}
({ package_name: parsed.package_name, module_name, local_name, }, parsed)
}
///|
/// Recognize the 4.1 target-names declaration, including optional native revision
/// pins. Recognition grants no authority and does not check revision equality;
/// admission checks the pin against the freshly restored native Library.
pub fn is_target_names_predicate(uri : String) -> Bool raise {
if !uri.has_prefix("morphir://ir/pkg/morphir/naming?") {
return false
}
let (name, parsed) = declaration(uri)
name.to_canonical() == "morphir/naming:naming#target-names" &&
parsed.format == "4.1.0"
}