Files
ARCADIA-Studio/harness/fixtures/interface-operation-probe/probe.rs
T

336 lines
11 KiB
Rust

//! Existing-core expressivity experiment, not a proposed production DTO.
use oa_core::validation::{Severity, validate};
use oa_core::*;
use std::{collections::BTreeMap, fs, path::Path};
fn id(n: u128) -> Id {
Id::from_u128(0x52060906000040008000000000000000 + n)
}
fn add(p: &mut Project, n: u128, k: Kind, name: &str, parent: Option<Id>) -> Id {
let mut e = if k == Kind::Port {
Element {
id: id(n),
alias: format!("operation_probe_{n}"),
name: name.into(),
description: String::new(),
parent,
body: ElementBody::shared_in(
Layer::Sa,
SharedBody::Port {
contract: PortContract::Component {
kind: ComponentPortKind::Standard,
orientation: ComponentPortOrientation::Unset,
},
legacy: None,
},
),
}
} else {
p.make_element(k, Layer::Sa, name, parent).unwrap()
};
e.id = id(n);
e.alias = format!("operation_probe_{n}");
p.elements.insert(e.id, e);
id(n)
}
fn body(p: &mut Project, n: u128, b: SharedBody) {
*p.elements.get_mut(&id(n)).unwrap().shared_mut().unwrap() = b;
}
fn rel(p: &mut Project, n: u128, k: RelationKind, a: u128, b: u128) {
p.relations.insert(
id(n),
Relation {
id: id(n),
alias: format!("operation_probe_{n}"),
name: format!("{k:?} {n}"),
description: String::new(),
body: RelationBody::new(k, id(a), id(b)),
},
);
}
fn valid(p: &Project) {
let errors: Vec<_> = validate(p)
.into_iter()
.filter(|i| i.severity == Severity::Error)
.collect();
assert!(errors.is_empty(), "{errors:?}");
}
fn message(p: &Project, n: u128) -> &OccurrenceBody {
let Some(SharedBody::Occurrence { body, .. }) = p.elements[&id(n)].shared() else {
panic!()
};
body
}
fn set_exchange(p: &mut Project, n: u128, target: Id) {
let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { exchange, .. },
..
}) = p.elements.get_mut(&id(n)).unwrap().shared_mut()
else {
panic!()
};
*exchange = Some(target);
}
fn initial() -> Project {
let mut p = Project::empty();
p.id = id(1);
p.name = "Two operations, one interface".into();
add(&mut p, 10, Kind::SystemActor, "Operator console", None);
add(&mut p, 11, Kind::System, "Valve service", None);
for (port, owner) in [(12, 10), (13, 11)] {
add(&mut p, port, Kind::Port, "Service socket", Some(id(owner)));
body(
&mut p,
port,
SharedBody::Port {
contract: PortContract::Component {
kind: ComponentPortKind::Standard,
orientation: ComponentPortOrientation::Unset,
},
legacy: None,
},
);
}
for (operation, name) in [(20, "Reset"), (21, "ReadStatus")] {
add(&mut p, operation, Kind::ExchangeItem, name, None);
body(
&mut p,
operation,
SharedBody::ExchangeItem {
kind: ExchangeItemKind::Information,
mechanism: ExchangeMechanism::Operation,
supertype: None,
},
);
}
add(&mut p, 22, Kind::Interface, "Valve commands", None);
for (allocation, item) in [(30, 20), (31, 21)] {
rel(
&mut p,
allocation,
RelationKind::InterfaceItemAllocation,
22,
item,
);
let RelationBody::InterfaceItemAllocation { transmission, .. } =
&mut p.relations.get_mut(&id(allocation)).unwrap().body
else {
panic!()
};
*transmission = TransmissionProtocol::Synchronous;
}
rel(&mut p, 32, RelationKind::InterfaceRequirement, 12, 22);
rel(&mut p, 33, RelationKind::InterfaceProvision, 13, 22);
rel(&mut p, 34, RelationKind::BoundaryExchange, 12, 13);
p.relations.get_mut(&id(34)).unwrap().body = RelationBody::BoundaryExchange {
source: id(12),
target: id(13),
oriented: false,
items: vec![id(20), id(21)],
};
add(&mut p, 40, Kind::Scenario, "Reset then read status", None);
body(
&mut p,
40,
SharedBody::Scenario {
viewpoint: ScenarioViewpoint::BoundaryInterface,
context: None,
precondition: String::new(),
postcondition: String::new(),
steps: vec![id(43), id(44)],
},
);
for (line, participant) in [(41, 10), (42, 11)] {
add(
&mut p,
line,
Kind::Lifeline,
"Service participant",
Some(id(40)),
);
body(
&mut p,
line,
SharedBody::Lifeline {
represented: Some(id(participant)),
},
);
}
for (call, name) in [(43, "Call Reset"), (44, "Call ReadStatus")] {
add(&mut p, call, Kind::Occurrence, name, Some(id(40)));
body(
&mut p,
call,
SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
exchange: Some(id(34)),
sender: Some(id(41)),
receiver: Some(id(42)),
message_kind: MessageKind::Call,
reply_to: None,
values: vec![],
},
precondition: String::new(),
postcondition: String::new(),
},
);
}
valid(&p);
p
}
fn refined(p: &Project) -> Project {
let mut q = p.clone();
for (work, owner, name) in [
(50, 10, "Issue service request"),
(51, 11, "Execute service request"),
] {
add(&mut q, work, Kind::Function, name, None);
rel(&mut q, work + 10, RelationKind::Allocation, owner, work);
}
for (item, output, input, exchange) in [(20, 70, 71, 80), (21, 72, 73, 81)] {
add(&mut q, output, Kind::Port, "Operation output", Some(id(50)));
add(&mut q, input, Kind::Port, "Operation input", Some(id(51)));
body(
&mut q,
output,
SharedBody::Port {
contract: PortContract::FunctionOutput {
outgoing_items: vec![id(item)],
},
legacy: None,
},
);
body(
&mut q,
input,
SharedBody::Port {
contract: PortContract::FunctionInput {
incoming_items: vec![id(item)],
},
legacy: None,
},
);
rel(&mut q, exchange, RelationKind::FunctionalExchange, 50, 51);
q.relations.get_mut(&id(exchange)).unwrap().body = RelationBody::FunctionalExchange {
source: id(50),
target: id(51),
source_port: id(output),
target_port: id(input),
items: vec![id(item)],
};
rel(
&mut q,
output + 20,
RelationKind::PortAllocation,
12,
output,
);
rel(&mut q, input + 20, RelationKind::PortAllocation, 13, input);
rel(
&mut q,
exchange + 20,
RelationKind::ExchangeAllocation,
34,
exchange,
);
}
set_exchange(&mut q, 43, id(80));
set_exchange(&mut q, 44, id(81));
valid(&q);
q
}
fn write(p: &Project, out: &Path, name: &str) {
let source = language::format(p);
let bundle = source_project::SourceBundle {
entry: "entry.arcadia".into(),
files: BTreeMap::from([("entry.arcadia".into(), source.clone())]),
};
assert_eq!(
&source_project::compile_bundle(&bundle, Default::default())
.unwrap()
.decoded
.project,
p
);
assert_eq!(
&serde_json::from_value::<Project>(serde_json::to_value(p).unwrap()).unwrap(),
p
);
fs::write(out.join(format!("{name}.arcadia")), source).unwrap();
fs::write(
out.join(format!("{name}.json")),
serde_json::to_vec_pretty(p).unwrap(),
)
.unwrap();
}
fn main() {
let directory = std::env::args().nth(1).expect("probe OUTPUT_DIRECTORY");
let out = Path::new(&directory);
fs::create_dir_all(out).unwrap();
let p = initial();
write(&p, out, "boundary-only");
assert_eq!(
message(&p, 43),
message(&p, 44),
"Only prose distinguishes the intended operations"
);
assert!(!references::element_references(&p.elements[&id(43)]).contains(&id(20)));
assert!(!references::element_references(&p.elements[&id(44)]).contains(&id(21)));
for target in [20, 30] {
let mut invalid = p.clone();
set_exchange(&mut invalid, 43, id(target));
assert!(
validate(&invalid)
.iter()
.any(|i| i.code == "occurrence_exchange" && i.element == Some(id(43)))
);
}
let mut proposed = serde_json::to_value(message(&p, 43)).unwrap();
proposed["Exchange"]["operation"] = serde_json::to_value(id(30)).unwrap();
assert!(
serde_json::from_value::<OccurrenceBody>(proposed)
.unwrap_err()
.to_string()
.contains("unknown field")
);
let mut session = Session::new(p.clone());
let mut reset = p.elements[&id(20)].clone();
reset.name = "EmergencyReset".into();
session
.execute(Command::EditElement(id(20), reset))
.unwrap();
assert_eq!(message(session.project(), 43), message(&p, 43));
session.execute(Command::Delete(id(20))).unwrap();
assert_eq!(message(session.project(), 43), message(&p, 43));
assert!(session.project().elements.contains_key(&id(43)));
valid(session.project());
write(session.project(), out, "after-reset-deletion");
let q = refined(&p);
write(&q, out, "explicit-functional-refinement");
assert_eq!(q.relations[&id(34)], p.relations[&id(34)]);
assert_ne!(message(&q, 43), message(&q, 44));
for n in [43, 44] {
assert!(
references::element_references(&q.elements[&id(n)])
.iter()
.any(|v| [id(80), id(81)].contains(v))
);
}
println!(
"PASS validated boundary-only model: {} elements / {} relations, two parameterless Operations",
p.elements.len(),
p.relations.len()
);
println!(
"PASS identical typed Calls; item/allocation exchange substitutions rejected; operation DTO field rejected"
);
println!(
"PASS operation rename/delete leaves intended Call's typed body unchanged; Call remains structurally valid"
);
println!(
"PASS distinct FE Calls require this explicit refinement: +{} elements / +{} relations; same original boundary",
q.elements.len() - p.elements.len(),
q.relations.len() - p.relations.len()
);
println!("PASS all three complete source/JSON round trips; no production mutation");
}