///|
fn record(value : @ion.IonValue, path : String) -> Record raise ModelError {
let fields : Map[String, @ion.IonValue] = Map([])
match value.kind() {
Struct(items) =>
for item in items {
let name = match item.name().text() {
Some(n) => n
None => {
fail("data.symbol_catalog_missing", path)
""
}
}
if fields.contains(name) {
fail("conceptual.duplicate_member", path + "/" + pointer(name))
}
fields[name] = item.value()
}
_ => fail("conceptual.invalid_record", path)
}
{ value, fields, path, }
}
///|
fn required(r : Record, name : String) -> @ion.IonValue raise ModelError {
match r.fields.get(name) {
Some(v) => v
None => {
fail("conceptual.missing_field", r.path + "/" + name)
@ion.IonValue::untyped_null()
}
}
}
///|
fn text(
r : Record,
name : String,
nonempty~ : Bool = false,
) -> String raise ModelError {
let v = required(r, name)
match v.kind() {
String(s) if !v.has_annotations() && (!nonempty || !s.is_empty()) => s
_ => {
fail("conceptual.invalid_record", r.path + "/" + name)
""
}
}
}
///|
fn items(r : Record, name : String) -> Array[@ion.IonValue] raise ModelError {
let v = required(r, name)
match v.kind() {
List(xs) if !v.has_annotations() => xs
_ => {
fail("conceptual.invalid_record", r.path + "/" + name)
[]
}
}
}
///|
fn strings(
r : Record,
name : String,
nonempty~ : Bool = true,
) -> Array[String] raise ModelError {
items(r, name).map(v => {
match v.kind() {
String(s) if !v.has_annotations() && (!nonempty || !s.is_empty()) => s
_ => {
fail("conceptual.invalid_record", r.path + "/" + name)
""
}
}
})
}
///|
fn child(r : Record, name : String) -> Record raise ModelError {
record(required(r, name), r.path + "/" + name)
}
///|
fn id(r : Record, name : String) -> Id raise ModelError {
@intent.Id::parse(text(r, name, nonempty=true)) catch {
_ => {
fail("conceptual.invalid_record", r.path + "/" + name)
abort("unreachable")
}
}
}
///|
fn revision(r : Record, name : String) -> Revision raise ModelError {
@intent.Revision::parse(text(r, name, nonempty=true)) catch {
_ => {
fail("conceptual.invalid_record", r.path + "/" + name)
abort("unreachable")
}
}
}
///|
fn snapshot(r : Record) -> Snapshot raise ModelError {
{ id: id(r, "id"), revision: revision(r, "revision"), }
}
///|
fn version(r : Record) -> Unit raise ModelError {
if text(r, "recordVersion") != RECORD_VERSION {
fail("conceptual.unsupported_profile", r.path + "/recordVersion")
}
}
///|
fn reference(r : Record) -> Reference raise ModelError {
match text(r, "state") {
"resolved" => {
for name in ["locator", "expectedKind", "reason", "proposedId"] {
if r.fields.contains(name) {
fail("conceptual.invalid_variant", r.path + "/" + name)
}
}
Resolved({
model: { id: id(r, "modelId"), revision: revision(r, "modelRevision"), },
entity: {
id: id(r, "entityId"),
revision: revision(r, "entityRevision"),
},
})
}
"unresolved" => {
for name in ["modelId", "modelRevision", "entityId", "entityRevision"] {
if r.fields.contains(name) {
fail("conceptual.invalid_variant", r.path + "/" + name)
}
}
Unresolved(
text(r, "locator", nonempty=true),
text(r, "expectedKind", nonempty=true),
text(r, "reason", nonempty=true),
)
}
_ => {
fail("conceptual.unsupported_variant", r.path + "/state")
abort("unreachable")
}
}
}
///|
fn entity_kind(name : String) -> EntityKind {
match name {
"intent" => Intent
"requirement" => Requirement
"concept" => Concept
"dataElement" => DataElement
"sourceFragment" => SourceFragment
"declaration" => Declaration
"contract" => Contract
"scenario" => Scenario
"implementation" => Implementation
"claim" => Claim
"evidence" => Evidence
other => Unknown(other)
}
}
///|
fn record_list(r : Record, name : String) -> Array[Record] raise ModelError {
items(r, name).mapi((i, v) => {
record(v, r.path + "/" + name + "/" + i.to_string())
})
}
///|
/// Decode bounded structural records while leaving opaque values uninterpreted.
/// Semantic validity is reported separately by inspect, never implied by decode.
pub fn Graph::decode(value : @ion.IonValue) -> Graph raise ModelError {
let pending = [(value, 0)]
let mut count = 0
while pending.pop() is Some((v, depth)) {
count += 1
if depth > 128 || count > 100000 {
fail("data.tree_limit", "")
}
match v.kind() {
List(xs) | Sexp(xs) =>
for x in xs {
pending.push((x, depth + 1))
}
Struct(fs) =>
for f in fs {
pending.push((f.value(), depth + 1))
}
_ => ()
}
}
let root = record(copy_value(value), "")
version(root)
if text(root, "profile") != PROFILE {
fail("conceptual.unsupported_profile", "/profile")
}
let records = [root]
let identity = tracked_snapshot(child(root, "model"), records)
ignore(strings(root, "requiredSemantics"))
for dep in record_list(root, "dependencies") {
ignore(tracked_snapshot(child(dep, "model"), records))
ignore(id(dep, "inputId"))
records.push(dep)
}
for gap in record_list(root, "projectionGaps") {
ignore(id(gap, "entityId"))
ignore(text(gap, "reason"))
ignore(id(gap, "requiredCapability"))
records.push(gap)
}
let sources : Array[Source] = []
let entities : Array[Entity] = []
let relationships : Array[Relationship] = []
let capabilities : Array[Record] = []
let definitions : Array[TraitDefinition] = []
let applications : Array[TraitApplication] = []
let references : Array[(Reference, String)] = []
let source_refs : Array[(SourceRef, String)] = []
let seen : Map[String, String] = Map([])
fn register(r : Record) -> Unit raise ModelError {
version(r)
let name = text(r, "id", nonempty=true)
let path = r.path + "/id"
if seen.get(name) is Some(earlier) {
fail("conceptual.duplicate_identity", path, related_path=Some(earlier))
}
seen[name] = path
records.push(r)
}
fn refs(r : Record) -> Unit raise ModelError {
for sr in record_list(r, "sourceRefs") {
source_refs.push(
({ source_id: id(sr, "sourceId"), anchor: id(sr, "anchor"), }, sr.path),
)
records.push(sr)
}
}
fn ref_field(r : Record, name : String) -> Reference raise ModelError {
let rr = child(r, name)
let result = reference(rr)
references.push((result, rr.path))
records.push(rr)
result
}
fn resolved_ref_field(r : Record, name : String) -> Unit raise ModelError {
let rr = child(r, name)
references.push((Resolved(resolved_reference(rr)), rr.path))
records.push(rr)
}
for r in record_list(root, "sources") {
register(r)
let document = child(r, "document")
ignore(id(document, "id"))
if document.fields.contains("revision") {
ignore(revision(document, "revision"))
}
if document.fields.contains("digest") {
let digest = child(document, "digest")
validate_digest(digest)
records.push(digest)
}
records.push(document)
ignore(text(r, "locator"))
let fragments : Array[Fragment] = []
let anchors : Map[String, Bool] = Map([])
for f in record_list(r, "fragments") {
let anchor = id(f, "anchor")
if anchors.contains(anchor.value()) {
fail("conceptual.duplicate_identity", f.path + "/anchor")
}
anchors[anchor.value()] = true
fragments.push({ anchor, text: text(f, "text"), })
records.push(f)
}
match text(r, "resolution") {
"retained" =>
if fragments.is_empty() {
fail("conceptual.invalid_record", r.path + "/fragments")
}
"unresolved" => ignore(text(r, "reason", nonempty=true))
_ => fail("conceptual.unsupported_variant", r.path + "/resolution")
}
sources.push({ record: r, id: id(r, "id"), fragments, })
}
for r in record_list(root, "entities") {
register(r)
refs(r)
let identity = snapshot(r)
let kind = entity_kind(text(r, "kind", nonempty=true))
ignore(text(r, "name"))
let content = match kind {
Unknown(_) => {
ignore(required(r, "content"))
None
}
_ => Some(child(r, "content"))
}
if content is Some(c) {
records.push(c)
content_records(kind, c, records)
match kind {
Intent => ignore(text(c, "text"))
Requirement => {
ignore(id(c, "category"))
ignore(text(c, "text"))
}
Concept => ignore(strings(c, "aliases", nonempty=false))
SourceFragment =>
source_refs.push(
({ source_id: id(c, "sourceId"), anchor: id(c, "anchor"), }, c.path),
)
Declaration => {
ignore(text(c, "role", nonempty=true))
if c.fields.contains("signature") {
let signature = child(c, "signature")
records.push(signature)
records.push(child(signature, "inputs"))
ignore(required(signature, "output"))
}
if c.fields.contains("parent") {
resolved_ref_field(c, "parent")
}
if c.fields.contains("representation") {
records.push(child(c, "representation"))
}
}
Contract => {
ignore(text(c, "clauseKind", nonempty=true))
resolved_ref_field(c, "subject")
let notation = child(c, "notation")
ignore(id(notation, "profile"))
ignore(text(notation, "text"))
records.push(notation)
let expression = child(c, "expression")
ignore(text(expression, "state", nonempty=true))
records.push(expression)
for b in record_list(c, "binders") {
ignore(id(b, "id"))
ignore(text(b, "role"))
ignore(text(b, "name"))
ignore(required(b, "type"))
records.push(b)
}
}
Scenario => {
ignore(id(c, "valueProfile"))
ignore(required(c, "inputs"))
ignore(required(c, "expected"))
records.push(child(c, "origin"))
}
Implementation => {
ignore(snapshot(child(c, "artifact")))
ignore(text(c, "fqName"))
ignore(text(c, "availability"))
}
Claim => {
resolved_ref_field(c, "subject")
ignore(text(c, "proposition"))
ignore(id(c, "author"))
ignore(required(c, "scope"))
records.push(child(c, "context"))
}
Evidence => {
ignore(text(c, "evidenceKind"))
ignore(text(c, "origin"))
records.push(child(c, "context"))
}
_ => ()
}
}
for
concern in [
"elaboration", "definition", "recovery", "review", "implementation",
] {
if r.fields.get(concern) is Some(v) {
let c = record(v, r.path + "/" + concern)
validate_concern(c, concern)
ignore(text(c, "state", nonempty=true))
records.push(c)
if c.fields.contains("sourceRefs") {
refs(c)
}
if concern == "implementation" && c.fields.contains("binding") {
ignore(ref_field(c, "binding"))
}
if concern == "elaboration" && c.fields.contains("aspects") {
for aspect in record_list(c, "aspects") {
ignore(id(aspect, "id"))
ignore(text(aspect, "question"))
ignore(text(aspect, "rationale"))
refs(aspect)
records.push(aspect)
}
}
if concern == "recovery" && c.fields.contains("diagnostics") {
for d in record_list(c, "diagnostics") {
ignore(text(d, "code"))
ignore(text(d, "message"))
records.push(d)
if d.fields.contains("span") {
let span = child(d, "span")
records.push(span)
exact_nat(span, "start")
exact_nat(span, "end")
ignore(text(span, "unit"))
}
}
let sr = child(c, "sourceRef")
source_refs.push(
(
{ source_id: id(sr, "sourceId"), anchor: id(sr, "anchor"), },
sr.path,
),
)
records.push(sr)
}
}
}
entities.push({ record: r, identity, kind, content, })
}
for r in record_list(root, "relationships") {
register(r)
refs(r)
let subject = ref_field(r, "subject")
if subject is Unresolved(_, _, _) {
fail("conceptual.invalid_variant", r.path + "/subject")
}
relationships.push({
record: r,
id: id(r, "id"),
predicate: text(r, "predicate", nonempty=true),
subject,
target: ref_field(r, "target"),
})
}
for r in record_list(root, "capabilities") {
register(r)
ignore(tracked_snapshot(child(r, "provider"), records))
ignore(text(r, "profile", nonempty=true))
ignore(strings(r, "operations"))
ignore(strings(r, "semanticProfiles"))
let limits = child(r, "limits")
records.push(limits)
for name in ["bytes", "depth", "values"] {
exact_nat(limits, name, positive=true)
}
capabilities.push(r)
}
if root.fields.get("traits") is Some(v) {
let traits = record(v, "/traits")
version(traits)
if text(traits, "profile") != TRAITS_PROFILE {
fail("conceptual.unsupported_profile", "/traits/profile")
}
records.push(traits)
for r in record_list(traits, "definitions") {
register(r)
refs(r)
ignore(text(r, "name"))
ignore(strings(r, "targets"))
ignore(text(r, "multiplicity"))
let schema = child(r, "payloadSchema")
ignore(tracked_snapshot(child(schema, "artifact"), records))
ignore(text(schema, "fqName"))
ignore(items(schema, "typeArguments"))
ignore(text(schema, "dataProfile"))
records.push(schema)
records.push(child(r, "interpretation"))
if r.fields.contains("referenceBindings") {
for b in record_list(r, "referenceBindings") {
records.push(b)
}
}
let definition = TraitDefinition::{ record: r, identity: snapshot(r), }
ignore(definition.interpretation())
ignore(definition.multiplicity())
definitions.push(definition)
}
for r in record_list(traits, "applications") {
register(r)
refs(r)
let definition = ref_field(r, "definition")
let target = child(r, "target")
ignore(text(target, "kind", nonempty=true))
records.push(target)
for name in ["model", "artifact"] {
if target.fields.contains(name) {
ignore(tracked_snapshot(child(target, name), records))
}
}
if target.fields.contains("ref") {
ignore(ref_field(target, "ref"))
}
ignore(required(r, "payload"))
records.push(child(r, "interpretation"))
let provenance = child(r, "provenance")
ignore(text(provenance, "origin"))
ignore(text(provenance, "agent"))
refs(provenance)
records.push(provenance)
if provenance.fields.contains("derivedFrom") {
for rr in record_list(provenance, "derivedFrom") {
references.push((Resolved(resolved_reference(rr)), rr.path))
records.push(rr)
}
}
let application = TraitApplication::{
record: r,
identity: snapshot(r),
definition,
provenance,
}
ignore(application.interpretation())
ignore(application.target())
if application.origin() is Derived &&
(
!provenance.fields.contains("derivedFrom") ||
items(provenance, "derivedFrom").is_empty()
) {
fail("conceptual.invalid_record", provenance.path + "/derivedFrom")
}
applications.push(application)
}
}
for r in records {
if r.fields.contains("critical") {
let names = strings(r, "critical")
let seen : Map[String, Bool] = Map([])
for name in names {
if seen.contains(name) || !r.fields.contains(name) {
fail("conceptual.invalid_record", r.path + "/critical")
}
seen[name] = true
}
}
}
{
record: root,
identity,
sources,
entities,
relationships,
capabilities,
definitions,
applications,
references,
source_refs,
records,
}
}