Migrate frozen data contracts and authored source to schema 4
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
// Frozen authored schema3 compatibility input. Do not rewrite as current syntax.
|
||||
arcadia 3;
|
||||
project legacy_data "Legacy data contract" id "00000000-0000-0000-0000-000000000001" {
|
||||
sa_function produce "Produce" id "00000000-0000-0000-0000-000000000002" {}
|
||||
sa_function consume "Consume" id "00000000-0000-0000-0000-000000000003" {}
|
||||
sa_exchange_item packet "Position event" id "00000000-0000-0000-0000-000000000004" {
|
||||
kind Event;
|
||||
// Reused record parameters retain their own identities.
|
||||
sa_data_field origin "Origin" id "00000000-0000-0000-0000-000000000007" { type_ref { Named @coordinate; }; }
|
||||
sa_data_field destination "Destination" id "00000000-0000-0000-0000-000000000008" { type_ref { Named @coordinate; }; }
|
||||
}
|
||||
sa_data_type coordinate "Coordinate" id "00000000-0000-0000-0000-000000000005" {
|
||||
definition Record;
|
||||
sa_data_field latitude "Latitude" id "00000000-0000-0000-0000-000000000006" { type_ref Real; unit "deg"; }
|
||||
}
|
||||
sa_interface sender_contract "Sender contract" id "00000000-0000-0000-0000-000000000009" { items [@packet]; }
|
||||
sa_interface receiver_contract "Receiver contract" id "00000000-0000-0000-0000-00000000000a" { items [@packet]; }
|
||||
sa_port output "Output" id "00000000-0000-0000-0000-00000000000b" {
|
||||
parent @produce; contract { FunctionOutput { outgoing_items [@packet]; }; }; legacy none;
|
||||
}
|
||||
sa_port input "Input" id "00000000-0000-0000-0000-00000000000c" {
|
||||
parent @consume; contract { FunctionInput { incoming_items [@packet]; }; }; legacy none;
|
||||
}
|
||||
functional_exchange position_exchange "Position exchange" id "00000000-0000-0000-0000-00000000000d" {
|
||||
source @produce; target @consume; source_port @output; target_port @input; items [@packet];
|
||||
}
|
||||
sa_scenario scenario "Position scenario" id "00000000-0000-0000-0000-00000000000e" {
|
||||
viewpoint Functional; steps [@message];
|
||||
sa_lifeline sender "Sender" id "00000000-0000-0000-0000-00000000000f" { represented @produce; }
|
||||
sa_lifeline receiver "Receiver" id "00000000-0000-0000-0000-000000000010" { represented @consume; }
|
||||
sa_occurrence message "Position" id "00000000-0000-0000-0000-000000000011" {
|
||||
body { Exchange {
|
||||
exchange @position_exchange; sender @sender; receiver @receiver;
|
||||
values [{ field @latitude; expression "navigation.latitude"; }];
|
||||
}; };
|
||||
}
|
||||
}
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
{
|
||||
"schema_version": 3,
|
||||
"id": "00000000-0000-0000-0000-000000000001",
|
||||
"alias": "legacy_data",
|
||||
"name": "Legacy data contract",
|
||||
"description": "",
|
||||
"elements": {
|
||||
"00000000-0000-0000-0000-000000000002": {
|
||||
"id": "00000000-0000-0000-0000-000000000002",
|
||||
"alias": "produce",
|
||||
"name": "Produce",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": "Function"
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000003": {
|
||||
"id": "00000000-0000-0000-0000-000000000003",
|
||||
"alias": "consume",
|
||||
"name": "Consume",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": "Function"
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000004": {
|
||||
"id": "00000000-0000-0000-0000-000000000004",
|
||||
"alias": "packet",
|
||||
"name": "Position event",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"ExchangeItem": {
|
||||
"kind": "Event"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000005": {
|
||||
"id": "00000000-0000-0000-0000-000000000005",
|
||||
"alias": "coordinate",
|
||||
"name": "Coordinate",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"DataType": {
|
||||
"definition": "Record"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000006": {
|
||||
"id": "00000000-0000-0000-0000-000000000006",
|
||||
"alias": "latitude",
|
||||
"name": "Latitude",
|
||||
"description": "",
|
||||
"parent": "00000000-0000-0000-0000-000000000005",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"DataField": {
|
||||
"type_ref": "Real",
|
||||
"minimum": 1,
|
||||
"maximum": 1,
|
||||
"unit": "deg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000007": {
|
||||
"id": "00000000-0000-0000-0000-000000000007",
|
||||
"alias": "origin",
|
||||
"name": "Origin",
|
||||
"description": "",
|
||||
"parent": "00000000-0000-0000-0000-000000000004",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"DataField": {
|
||||
"type_ref": {
|
||||
"Named": "00000000-0000-0000-0000-000000000005"
|
||||
},
|
||||
"minimum": 1,
|
||||
"maximum": 1,
|
||||
"unit": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000008": {
|
||||
"id": "00000000-0000-0000-0000-000000000008",
|
||||
"alias": "destination",
|
||||
"name": "Destination",
|
||||
"description": "",
|
||||
"parent": "00000000-0000-0000-0000-000000000004",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"DataField": {
|
||||
"type_ref": {
|
||||
"Named": "00000000-0000-0000-0000-000000000005"
|
||||
},
|
||||
"minimum": 1,
|
||||
"maximum": 1,
|
||||
"unit": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000009": {
|
||||
"id": "00000000-0000-0000-0000-000000000009",
|
||||
"alias": "sender_contract",
|
||||
"name": "Sender contract",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Interface": {
|
||||
"items": [
|
||||
"00000000-0000-0000-0000-000000000004"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-00000000000a": {
|
||||
"id": "00000000-0000-0000-0000-00000000000a",
|
||||
"alias": "receiver_contract",
|
||||
"name": "Receiver contract",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Interface": {
|
||||
"items": [
|
||||
"00000000-0000-0000-0000-000000000004"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-00000000000b": {
|
||||
"id": "00000000-0000-0000-0000-00000000000b",
|
||||
"alias": "output",
|
||||
"name": "Output",
|
||||
"description": "",
|
||||
"parent": "00000000-0000-0000-0000-000000000002",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Port": {
|
||||
"contract": {
|
||||
"FunctionOutput": {
|
||||
"outgoing_items": [
|
||||
"00000000-0000-0000-0000-000000000004"
|
||||
]
|
||||
}
|
||||
},
|
||||
"legacy": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-00000000000c": {
|
||||
"id": "00000000-0000-0000-0000-00000000000c",
|
||||
"alias": "input",
|
||||
"name": "Input",
|
||||
"description": "",
|
||||
"parent": "00000000-0000-0000-0000-000000000003",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Port": {
|
||||
"contract": {
|
||||
"FunctionInput": {
|
||||
"incoming_items": [
|
||||
"00000000-0000-0000-0000-000000000004"
|
||||
]
|
||||
}
|
||||
},
|
||||
"legacy": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-00000000000e": {
|
||||
"id": "00000000-0000-0000-0000-00000000000e",
|
||||
"alias": "scenario",
|
||||
"name": "Position scenario",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Scenario": {
|
||||
"viewpoint": "Functional",
|
||||
"context": null,
|
||||
"precondition": "",
|
||||
"postcondition": "",
|
||||
"steps": [
|
||||
"00000000-0000-0000-0000-000000000011"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-00000000000f": {
|
||||
"id": "00000000-0000-0000-0000-00000000000f",
|
||||
"alias": "sender",
|
||||
"name": "Sender",
|
||||
"description": "",
|
||||
"parent": "00000000-0000-0000-0000-00000000000e",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Lifeline": {
|
||||
"represented": "00000000-0000-0000-0000-000000000002"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000010": {
|
||||
"id": "00000000-0000-0000-0000-000000000010",
|
||||
"alias": "receiver",
|
||||
"name": "Receiver",
|
||||
"description": "",
|
||||
"parent": "00000000-0000-0000-0000-00000000000e",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Lifeline": {
|
||||
"represented": "00000000-0000-0000-0000-000000000003"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"00000000-0000-0000-0000-000000000011": {
|
||||
"id": "00000000-0000-0000-0000-000000000011",
|
||||
"alias": "message",
|
||||
"name": "Position",
|
||||
"description": "",
|
||||
"parent": "00000000-0000-0000-0000-00000000000e",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Occurrence": {
|
||||
"body": {
|
||||
"Exchange": {
|
||||
"exchange": "00000000-0000-0000-0000-00000000000d",
|
||||
"sender": "00000000-0000-0000-0000-00000000000f",
|
||||
"receiver": "00000000-0000-0000-0000-000000000010",
|
||||
"message_kind": "Asynchronous",
|
||||
"reply_to": null,
|
||||
"values": [
|
||||
{
|
||||
"field": "00000000-0000-0000-0000-000000000006",
|
||||
"expression": "navigation.latitude"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"precondition": "",
|
||||
"postcondition": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"00000000-0000-0000-0000-00000000000d": {
|
||||
"id": "00000000-0000-0000-0000-00000000000d",
|
||||
"alias": "position_exchange",
|
||||
"name": "Position exchange",
|
||||
"description": "",
|
||||
"body": {
|
||||
"FunctionalExchange": {
|
||||
"source": "00000000-0000-0000-0000-000000000002",
|
||||
"target": "00000000-0000-0000-0000-000000000003",
|
||||
"source_port": "00000000-0000-0000-0000-00000000000b",
|
||||
"target_port": "00000000-0000-0000-0000-00000000000c",
|
||||
"items": [
|
||||
"00000000-0000-0000-0000-000000000004"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"diagrams": {},
|
||||
"artifact_links": {},
|
||||
"traces": {}
|
||||
}
|
||||
@@ -168,6 +168,20 @@ project mission "Harbor safety — 港 🌊" {
|
||||
.find(|e| e.alias == "event_record")
|
||||
.unwrap()
|
||||
.id;
|
||||
let associated = p
|
||||
.add_element(Kind::DataType, Layer::Sa, "Associated record", None)
|
||||
.unwrap();
|
||||
if let Some(SharedBody::DataType { definition, .. }) =
|
||||
p.elements.get_mut(&associated).unwrap().shared_mut()
|
||||
{
|
||||
*definition = DataTypeDefinition::Record { supertype: None };
|
||||
}
|
||||
p.relate(
|
||||
RelationKind::DataAssociation,
|
||||
"Related record",
|
||||
record,
|
||||
associated,
|
||||
);
|
||||
for (index, type_ref) in [
|
||||
TypeRef::Boolean,
|
||||
TypeRef::Integer,
|
||||
@@ -190,18 +204,30 @@ project mission "Harbor safety — 港 🌊" {
|
||||
minimum: 1,
|
||||
maximum: Some(5),
|
||||
unit: "unit".into(),
|
||||
direction: ParameterDirection::Unspecified,
|
||||
storage: FieldStorage::Value,
|
||||
default: None,
|
||||
};
|
||||
}
|
||||
for definition in [
|
||||
DataTypeDefinition::Boolean,
|
||||
DataTypeDefinition::Integer,
|
||||
DataTypeDefinition::String,
|
||||
DataTypeDefinition::Integer {
|
||||
minimum: None,
|
||||
maximum: None,
|
||||
},
|
||||
DataTypeDefinition::String {
|
||||
minimum_length: 0,
|
||||
maximum_length: None,
|
||||
},
|
||||
] {
|
||||
let id = p
|
||||
.add_element(Kind::DataType, Layer::Sa, "Primitive type", None)
|
||||
.unwrap();
|
||||
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() =
|
||||
SharedBody::DataType { definition };
|
||||
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::DataType {
|
||||
definition,
|
||||
default: None,
|
||||
unit: String::new(),
|
||||
};
|
||||
}
|
||||
for kind in [
|
||||
ExchangeItemKind::Material,
|
||||
@@ -216,7 +242,11 @@ project mission "Harbor safety — 港 🌊" {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::ExchangeItem { kind };
|
||||
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::ExchangeItem {
|
||||
kind,
|
||||
mechanism: ExchangeMechanism::Unspecified,
|
||||
supertype: None,
|
||||
};
|
||||
}
|
||||
let scenario = p
|
||||
.elements
|
||||
@@ -461,7 +491,7 @@ fn authoring_defaults_and_forward_references_remain_strict() {
|
||||
#[test]
|
||||
fn typed_unknown_fields_enums_duplicates_and_reference_spelling_are_rejected() {
|
||||
for (source, expected) in [
|
||||
("arcadia 4; project mission \"x\" {}", "Unsupported schema"),
|
||||
("arcadia 5; project mission \"x\" {}", "Unsupported schema"),
|
||||
(
|
||||
"arcadia 2; project mission \"x\" { oa_activity a \"A\" { nonsense 1; } }",
|
||||
"Unknown field",
|
||||
@@ -765,9 +795,27 @@ fn original_v2_text_and_json_preserve_identity_and_mixed_scenarios_after_extensi
|
||||
assert_eq!(from_source, from_json);
|
||||
let original: serde_json::Value = serde_json::from_slice(old_json).unwrap();
|
||||
let mut retained = serde_json::to_value(&from_json).unwrap();
|
||||
assert_eq!(retained["schema_version"], 3);
|
||||
assert_eq!(retained["schema_version"], 4);
|
||||
retained["schema_version"] = 2.into();
|
||||
let allocations: Vec<_> = from_json
|
||||
.relations
|
||||
.values()
|
||||
.filter_map(|relation| {
|
||||
if let RelationBody::InterfaceItemAllocation {
|
||||
source,
|
||||
target,
|
||||
order,
|
||||
..
|
||||
} = relation.body
|
||||
{
|
||||
Some((source, target, order))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
for element in retained["elements"].as_object_mut().unwrap().values_mut() {
|
||||
let element_id: Id = element["id"].as_str().unwrap().parse().unwrap();
|
||||
for layer in ["Oa", "Sa"] {
|
||||
let Some(shared) = element["body"]
|
||||
.get_mut(layer)
|
||||
@@ -779,6 +827,46 @@ fn original_v2_text_and_json_preserve_identity_and_mixed_scenarios_after_extensi
|
||||
assert_eq!(port["contract"], "Legacy");
|
||||
*port = port["legacy"].clone();
|
||||
}
|
||||
if let Some(item) = shared
|
||||
.get_mut("ExchangeItem")
|
||||
.and_then(serde_json::Value::as_object_mut)
|
||||
{
|
||||
if item["mechanism"] == "Event" {
|
||||
item.insert("kind".into(), "Event".into());
|
||||
}
|
||||
item.remove("mechanism");
|
||||
item.remove("supertype");
|
||||
}
|
||||
if let Some(kind) = shared
|
||||
.get_mut("DataType")
|
||||
.and_then(serde_json::Value::as_object_mut)
|
||||
{
|
||||
kind.remove("default");
|
||||
kind.remove("unit");
|
||||
let definition = &kind["definition"];
|
||||
if let Some(name) = ["Integer", "Real", "String", "Record"]
|
||||
.into_iter()
|
||||
.find(|name| definition.get(name).is_some())
|
||||
{
|
||||
kind.insert("definition".into(), name.into());
|
||||
}
|
||||
}
|
||||
if let Some(field) = shared
|
||||
.get_mut("DataField")
|
||||
.and_then(serde_json::Value::as_object_mut)
|
||||
{
|
||||
for key in ["direction", "storage", "default"] {
|
||||
field.remove(key);
|
||||
}
|
||||
}
|
||||
if let Some(interface) = shared.get_mut("Interface") {
|
||||
let mut entries: Vec<_> = allocations
|
||||
.iter()
|
||||
.filter(|(source, _, _)| *source == element_id)
|
||||
.collect();
|
||||
entries.sort_by_key(|(_, _, order)| *order);
|
||||
*interface = serde_json::json!({"items":entries.into_iter().map(|(_,target,_)|target).collect::<Vec<_>>()});
|
||||
}
|
||||
if let Some(scenario) = shared
|
||||
.get_mut("Scenario")
|
||||
.and_then(serde_json::Value::as_object_mut)
|
||||
@@ -797,6 +885,10 @@ fn original_v2_text_and_json_preserve_identity_and_mixed_scenarios_after_extensi
|
||||
}
|
||||
}
|
||||
}
|
||||
retained["relations"]
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.retain(|_, relation| relation["body"].get("InterfaceItemAllocation").is_none());
|
||||
for relation in retained["relations"].as_object_mut().unwrap().values_mut() {
|
||||
if let Some(boundary) = relation["body"].get_mut("BoundaryExchange") {
|
||||
assert_eq!(boundary["oriented"], true);
|
||||
@@ -826,7 +918,7 @@ fn original_v2_text_and_json_preserve_identity_and_mixed_scenarios_after_extensi
|
||||
);
|
||||
assert_eq!(
|
||||
from_json.relations.len(),
|
||||
original["relations"].as_object().unwrap().len()
|
||||
original["relations"].as_object().unwrap().len() + allocations.len()
|
||||
);
|
||||
assert_eq!(
|
||||
from_json.diagrams.len(),
|
||||
@@ -899,7 +991,7 @@ fn v1_text_and_json_migrate_deterministically_without_losing_legacy_semantics()
|
||||
assert_eq!(from_text, from_json);
|
||||
assert_eq!(read(text), from_text);
|
||||
assert_eq!(persistence::decode(json).unwrap(), from_json);
|
||||
assert_eq!(from_text.schema_version, 3);
|
||||
assert_eq!(from_text.schema_version, 4);
|
||||
assert!(!from_text.elements.values().any(|e| e.layer() == Layer::Sa));
|
||||
let raw: serde_json::Value = serde_json::from_slice(json).unwrap();
|
||||
for (id, record) in raw["elements"].as_object().unwrap() {
|
||||
@@ -1100,6 +1192,7 @@ fn complete_normative_examples_are_accepted_by_the_real_parser() {
|
||||
}
|
||||
if code.trim_start().starts_with("arcadia 2;")
|
||||
|| code.trim_start().starts_with("arcadia 3;")
|
||||
|| code.trim_start().starts_with("arcadia 4;")
|
||||
{
|
||||
read(&code);
|
||||
examples += 1;
|
||||
|
||||
@@ -39,7 +39,7 @@ fn frozen_original_v2_reader_preserves_every_original_record() {
|
||||
assert_eq!(decoded.project, from_source.project);
|
||||
assert_eq!(decoded.migration, from_source.migration);
|
||||
let report = decoded.migration.unwrap();
|
||||
assert_eq!((report.from_schema, report.to_schema), (2, 3));
|
||||
assert_eq!((report.from_schema, report.to_schema), (2, 4));
|
||||
assert_eq!(report.preserved_ports.len(), 4);
|
||||
assert!(report.generated_endpoints.is_empty());
|
||||
assert_eq!(report.orientation_defaults.len(), 1);
|
||||
@@ -56,6 +56,7 @@ fn frozen_original_v2_reader_preserves_every_original_record() {
|
||||
+ old["traces"].as_object().unwrap().len()
|
||||
+ old["artifact_links"].as_object().unwrap().len()
|
||||
+ 1
|
||||
+ report.generated_interface_allocations.len()
|
||||
);
|
||||
let mut current = serde_json::to_value(&decoded.project).unwrap();
|
||||
for diagram in current["diagrams"].as_object_mut().unwrap().values_mut() {
|
||||
@@ -143,7 +144,7 @@ fn missing_v2_endpoints_are_deterministic_reported_and_independently_typed() {
|
||||
}
|
||||
}
|
||||
let canonical = language::format(&first.project);
|
||||
assert!(canonical.starts_with("arcadia 3;"));
|
||||
assert!(canonical.starts_with("arcadia 4;"));
|
||||
let reopened = persistence::decode_with_report(canonical.as_bytes()).unwrap();
|
||||
assert!(reopened.migration.is_none());
|
||||
assert_eq!(reopened.project, first.project);
|
||||
@@ -223,7 +224,13 @@ fn migration_admission_counts_generated_objects_records_and_references() {
|
||||
_ => 0,
|
||||
})
|
||||
})
|
||||
.sum::<usize>();
|
||||
.sum::<usize>()
|
||||
+ decode(&old)
|
||||
.migration
|
||||
.unwrap()
|
||||
.generated_interface_allocations
|
||||
.len()
|
||||
* 2;
|
||||
for limits in [
|
||||
ParseLimits {
|
||||
max_graph_objects: graph + 1,
|
||||
@@ -244,7 +251,7 @@ fn migration_admission_counts_generated_objects_records_and_references() {
|
||||
));
|
||||
}
|
||||
let accepted = ParseLimits {
|
||||
max_graph_objects: graph + 2,
|
||||
max_graph_objects: migrated.elements.len() + migrated.relations.len(),
|
||||
max_records: records,
|
||||
max_step_references: reference_count,
|
||||
..ParseLimits::UNLIMITED
|
||||
@@ -267,7 +274,7 @@ fn old_contract_is_validated_before_new_shapes_and_v1_flows_through_v2() {
|
||||
assert!(error.contains("V2") && error.contains(&fe), "{error}");
|
||||
let v1 = include_bytes!("../../../examples/legacy-v1-coastal-rescue.oa.json");
|
||||
let result = persistence::decode_with_report(v1).unwrap();
|
||||
assert_eq!(result.project.schema_version, 3);
|
||||
assert_eq!(result.project.schema_version, 4);
|
||||
assert_eq!(result.migration.unwrap().from_schema, 1);
|
||||
assert_eq!(persistence::decode(v1).unwrap(), result.project);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use oa_core::{language, persistence, *};
|
||||
|
||||
const SOURCE: &str = r#"arcadia 3;
|
||||
const SOURCE: &str = r#"arcadia 4;
|
||||
project typed_ports "Typed ports" {
|
||||
sa_system producer "Producer" {}
|
||||
sa_actor consumer "Consumer" {}
|
||||
@@ -9,7 +9,8 @@ project typed_ports "Typed ports" {
|
||||
allocation sends "Produces" { source @producer; target @send; }
|
||||
allocation receives "Consumes" { source @consumer; target @receive; }
|
||||
sa_exchange_item data "Data" {}
|
||||
sa_interface service "Service" { items [@data]; }
|
||||
sa_interface service "Service" {}
|
||||
interface_item_allocation service_data "Service data" { source @service; target @data; }
|
||||
sa_port output "Output" { parent @send; contract { FunctionOutput { outgoing_items [@data]; }; }; }
|
||||
sa_port input "Input" { parent @receive; contract { FunctionInput { incoming_items [@data]; }; }; }
|
||||
sa_port provider "Provider port" { parent @producer; contract { Component { kind Standard; orientation Unset; }; }; }
|
||||
@@ -26,7 +27,7 @@ project typed_ports "Typed ports" {
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn source3_covers_typed_contracts_roles_allocations_and_nested_legacy_references() {
|
||||
fn source4_covers_typed_contracts_roles_allocations_and_nested_legacy_references() {
|
||||
let decoded = language::parse_with_report(SOURCE).unwrap();
|
||||
assert!(decoded.migration.is_none());
|
||||
let canonical = language::format(&decoded.project);
|
||||
@@ -69,7 +70,7 @@ fn source3_covers_typed_contracts_roles_allocations_and_nested_legacy_references
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source3_requires_explicit_port_contract_and_fe_endpoints_without_generation() {
|
||||
fn source4_requires_explicit_port_contract_and_fe_endpoints_without_generation() {
|
||||
for (remove, expected) in [
|
||||
("source_port @output; ", "source_port"),
|
||||
("target_port @input; ", "target_port"),
|
||||
@@ -90,7 +91,7 @@ fn source3_requires_explicit_port_contract_and_fe_endpoints_without_generation()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source3_rejects_mixed_component_kinds_and_nested_reference_typos() {
|
||||
fn source4_rejects_mixed_component_kinds_and_nested_reference_typos() {
|
||||
let mixed = SOURCE.replace(
|
||||
"parent @consumer; contract { Component { kind Standard; orientation Unset;",
|
||||
"parent @consumer; contract { Component { kind Flow; orientation Unset;",
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
use oa_core::{language, persistence, *};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
const SOURCE3: &str = include_str!("fixtures/v3-data-contract.arcadia");
|
||||
fn id(n: u128) -> Id {
|
||||
Id::from_u128(n)
|
||||
}
|
||||
|
||||
// Frozen JSON uses the same authored schema3 contract as the source fixture;
|
||||
// this helper never serializes current defaults under version3.
|
||||
fn original3() -> Value {
|
||||
serde_json::from_str(include_str!("fixtures/v3-data-contract.json")).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn genuine_v3_preserves_typed_ports_and_allocates_deterministic_relationships() {
|
||||
let old = original3();
|
||||
let decoded = persistence::decode_with_report(&serde_json::to_vec(&old).unwrap()).unwrap();
|
||||
let source = language::parse_with_report(SOURCE3).unwrap();
|
||||
assert_eq!(decoded.project, source.project);
|
||||
assert_eq!(decoded.migration, source.migration);
|
||||
let project = &decoded.project;
|
||||
let report = decoded.migration.as_ref().unwrap();
|
||||
assert_eq!((report.from_schema, report.to_schema), (3, 4));
|
||||
assert_eq!(project.identities().len(), 19);
|
||||
assert!(report.preserved_ports.is_empty());
|
||||
assert!(report.generated_endpoints.is_empty());
|
||||
assert_eq!(report.generated_interface_allocations.len(), 2);
|
||||
for allocation in &report.generated_interface_allocations {
|
||||
assert_eq!(
|
||||
allocation.id,
|
||||
Id::new_v5(
|
||||
&id(1),
|
||||
format!(
|
||||
"v2-interface-item:{}:{}",
|
||||
allocation.source, allocation.target
|
||||
)
|
||||
.as_bytes()
|
||||
)
|
||||
);
|
||||
assert_eq!(allocation.target, id(4));
|
||||
assert_eq!(allocation.order, 0);
|
||||
assert!(matches!(
|
||||
project.relations[&allocation.id].body,
|
||||
RelationBody::InterfaceItemAllocation {
|
||||
transmission: TransmissionProtocol::Unspecified,
|
||||
acquisition: AcquisitionProtocol::Unspecified,
|
||||
..
|
||||
}
|
||||
));
|
||||
}
|
||||
let now = serde_json::to_value(project).unwrap();
|
||||
for port in [id(11), id(12)] {
|
||||
assert_eq!(
|
||||
now["elements"][port.to_string()],
|
||||
old["elements"][port.to_string()]
|
||||
);
|
||||
}
|
||||
assert!(matches!(
|
||||
project.elements[&id(4)].shared(),
|
||||
Some(SharedBody::ExchangeItem {
|
||||
kind: ExchangeItemKind::Information,
|
||||
mechanism: ExchangeMechanism::Event,
|
||||
supertype: None
|
||||
})
|
||||
));
|
||||
assert_eq!(report.unresolved_message_values.len(), 1);
|
||||
assert!(
|
||||
validation::validate(project)
|
||||
.iter()
|
||||
.any(|issue| issue.code == "message_value_ambiguous_path")
|
||||
);
|
||||
let canonical = language::format(project);
|
||||
assert!(canonical.starts_with("arcadia 4;"));
|
||||
let reopened = language::parse_with_report(&canonical).unwrap();
|
||||
assert_eq!(&reopened.project, project);
|
||||
assert!(reopened.migration.is_none());
|
||||
assert_eq!(canonical, language::format(&reopened.project));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v3_changed_payloads_reject_schema4_fields_and_new_constructor_names() {
|
||||
let mut old = original3();
|
||||
old["elements"][id(4).to_string()]["body"]["Sa"]["Shared"]["ExchangeItem"]["mechanism"] =
|
||||
json!("Flow");
|
||||
assert!(persistence::decode(&serde_json::to_vec(&old).unwrap()).is_err());
|
||||
let mut old = original3();
|
||||
old["elements"][id(5).to_string()]["body"]["Sa"]["Shared"]["DataType"]["definition"] =
|
||||
json!({"Record":{"supertype":null}});
|
||||
assert!(persistence::decode(&serde_json::to_vec(&old).unwrap()).is_err());
|
||||
let mut old = original3();
|
||||
old["elements"][id(17).to_string()]["body"]["Sa"]["Shared"]["Occurrence"]["body"]["Exchange"]
|
||||
["values"][0]["path"] = json!([]);
|
||||
assert!(persistence::decode(&serde_json::to_vec(&old).unwrap()).is_err());
|
||||
let source = SOURCE3.replace("kind Event;", "kind Information; mechanism Flow;");
|
||||
assert!(language::parse(&source).is_err());
|
||||
let source = SOURCE3.replace(
|
||||
"functional_exchange position_exchange",
|
||||
"interface_item_allocation position_exchange",
|
||||
);
|
||||
assert!(language::parse(&source).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deterministic_allocation_id_collision_is_located_without_overwriting_any_object() {
|
||||
let mut old = original3();
|
||||
let collision = Id::new_v5(
|
||||
&id(1),
|
||||
format!("v2-interface-item:{}:{}", id(9), id(4)).as_bytes(),
|
||||
);
|
||||
old["artifact_links"][collision.to_string()] = json!({"id":collision,"alias":"retained_artifact","name":"Retain","description":"","owner":id(1),"relation":"References","uri":"docs/contract.md","revision":null,"fragment":null});
|
||||
let error = persistence::decode(&serde_json::to_vec(&old).unwrap()).unwrap_err();
|
||||
match error {
|
||||
persistence::FileError::Model { issues, .. } => {
|
||||
assert_eq!(issues[0].code, "identity_collision");
|
||||
assert_eq!(issues[0].element, Some(id(9)));
|
||||
assert_eq!(issues[0].field.as_deref(), Some("items"));
|
||||
}
|
||||
other => panic!("unexpected error {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unique_legacy_nested_value_is_qualified_and_physical_nature_is_not_guessed() {
|
||||
let source = SOURCE3.replace("kind Event;", "kind Material;").replace(
|
||||
"type_ref { Named @coordinate; }; }\n }",
|
||||
"type_ref String; }\n }",
|
||||
);
|
||||
let decoded = language::parse_with_report(&source).unwrap();
|
||||
assert!(matches!(
|
||||
decoded.project.elements[&id(4)].shared(),
|
||||
Some(SharedBody::ExchangeItem {
|
||||
kind: ExchangeItemKind::Material,
|
||||
mechanism: ExchangeMechanism::Unspecified,
|
||||
..
|
||||
})
|
||||
));
|
||||
let Some(SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange { values, .. },
|
||||
..
|
||||
}) = decoded.project.elements[&id(17)].shared()
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
assert_eq!(values[0].path, vec![id(7)]);
|
||||
assert_eq!(values[0].expression, "navigation.latitude");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v1_and_frozen_v2_inputs_reach_four_through_the_shared_data_upgrade() {
|
||||
let v2 =
|
||||
persistence::decode_with_report(include_bytes!("fixtures/v2-coastal-rescue.json")).unwrap();
|
||||
let text =
|
||||
language::parse_with_report(include_str!("fixtures/v2-coastal-rescue.arcadia")).unwrap();
|
||||
assert_eq!(v2.project, text.project);
|
||||
assert_eq!(v2.migration, text.migration);
|
||||
assert_eq!(v2.migration.unwrap().to_schema, 4);
|
||||
let v1 = language::parse_with_report(
|
||||
"arcadia 1; project \"Old\" id \"00000000-0000-0000-0000-000000000001\" {}",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(v1.project.schema_version, 4);
|
||||
assert_eq!(v1.migration.unwrap().from_schema, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allocation_alias_collisions_are_stable_and_duplicate_legacy_payload_fields_reject() {
|
||||
let mut old = original3();
|
||||
let first = persistence::decode_with_report(&serde_json::to_vec(&old).unwrap()).unwrap();
|
||||
let alias = first.migration.unwrap().generated_interface_allocations[0]
|
||||
.alias
|
||||
.clone();
|
||||
old["artifact_links"][id(100).to_string()] = json!({"id":id(100),"alias":alias,"name":"Existing alias","description":"","owner":id(1),"relation":"References","uri":"docs/contract.md","revision":null,"fragment":null});
|
||||
let bytes = serde_json::to_vec(&old).unwrap();
|
||||
let migrated = persistence::decode_with_report(&bytes).unwrap();
|
||||
let generated = &migrated
|
||||
.migration
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.generated_interface_allocations[0];
|
||||
assert_ne!(generated.alias, alias);
|
||||
assert!(generated.alias.starts_with(&alias));
|
||||
assert_eq!(
|
||||
migrated.project,
|
||||
persistence::decode_with_report(&bytes).unwrap().project
|
||||
);
|
||||
let duplicate = include_str!("fixtures/v3-data-contract.json").replace(
|
||||
"\"kind\": \"Event\"",
|
||||
"\"kind\": \"Event\", \"kind\": \"Material\"",
|
||||
);
|
||||
assert!(
|
||||
persistence::decode(duplicate.as_bytes())
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Duplicate field 'kind'")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_legacy_record_paths_remain_ambiguous_instead_of_becoming_one_argument() {
|
||||
let mut old = original3();
|
||||
old["elements"][id(8).to_string()]["body"]["Sa"]["Shared"]["DataField"]["type_ref"] =
|
||||
json!("String");
|
||||
old["elements"][id(100).to_string()] = json!({"id":id(100),"alias":"next_coordinate","name":"Next coordinate","description":"","parent":id(5),"body":{"Sa":{"Shared":{"DataField":{"type_ref":{"Named":id(5)},"minimum":0,"maximum":1,"unit":""}}}}});
|
||||
let decoded = persistence::decode_with_report(&serde_json::to_vec(&old).unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
decoded
|
||||
.migration
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.unresolved_message_values
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
let Some(SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange { values, .. },
|
||||
..
|
||||
}) = decoded.project.elements[&id(17)].shared()
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
assert!(values[0].path.is_empty());
|
||||
assert!(
|
||||
validation::validate(&decoded.project)
|
||||
.iter()
|
||||
.any(|issue| issue.code == "message_value_ambiguous_path")
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
use oa_core::{language, source_project::*, *};
|
||||
use std::collections::BTreeMap;
|
||||
fn id(n: u128) -> Id {
|
||||
Id::from_u128(n)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authored_v3_bundle_upgrade_preserves_includes_nested_ownership_comments_and_identity() {
|
||||
let original = include_str!("fixtures/v3-data-contract.arcadia");
|
||||
let header = original.find(" sa_function").unwrap();
|
||||
let close = original.rfind('}').unwrap();
|
||||
let entry = format!(
|
||||
"{} // Contracts stay in their original module.\n include \"contracts.arcadia\";\n}}\n",
|
||||
&original[..header]
|
||||
);
|
||||
let fragment = original[header..close].replace(
|
||||
"items [@packet];",
|
||||
"items [@ // interface contract decision\npacket];",
|
||||
);
|
||||
let bundle = SourceBundle {
|
||||
entry: "project.arcadia".into(),
|
||||
files: BTreeMap::from([
|
||||
("project.arcadia".into(), entry),
|
||||
("contracts.arcadia".into(), fragment),
|
||||
]),
|
||||
};
|
||||
let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
|
||||
assert_eq!(compiled.bundle.files.len(), 2);
|
||||
assert!(compiled.bundle.files["project.arcadia"].contains("arcadia 4;"));
|
||||
assert!(compiled.bundle.files["project.arcadia"].contains("include \"contracts.arcadia\";"));
|
||||
for text in [
|
||||
"// Contracts stay in their original module.",
|
||||
"// Reused record parameters retain their own identities.",
|
||||
"// interface contract decision",
|
||||
] {
|
||||
assert!(
|
||||
compiled
|
||||
.bundle
|
||||
.files
|
||||
.values()
|
||||
.any(|source| source.contains(text)),
|
||||
"Lost {text}"
|
||||
);
|
||||
}
|
||||
assert_eq!(compiled.index.lexical_owners[&id(6)], id(5));
|
||||
assert_eq!(compiled.index.lexical_owners[&id(7)], id(4));
|
||||
assert_eq!(compiled.index.lexical_owners[&id(8)], id(4));
|
||||
assert_eq!(compiled.index.lexical_owners[&id(17)], id(14));
|
||||
let report = compiled.decoded.migration.as_ref().unwrap();
|
||||
assert_eq!(report.from_schema, 3);
|
||||
for allocation in &report.generated_interface_allocations {
|
||||
assert_eq!(
|
||||
compiled.index.declarations[&allocation.id].path,
|
||||
"contracts.arcadia"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
compiled
|
||||
.diagnostics
|
||||
.iter()
|
||||
.any(|d| d.diagnostic.code == "message_value_ambiguous_path")
|
||||
);
|
||||
let second = compile_bundle(&compiled.bundle, ProjectLimits::default()).unwrap();
|
||||
assert_eq!(second.bundle, compiled.bundle);
|
||||
assert_eq!(second.decoded.project, compiled.decoded.project);
|
||||
assert!(second.decoded.migration.is_none());
|
||||
let mut renamed = compiled.decoded.project.clone();
|
||||
renamed.elements.get_mut(&id(6)).unwrap().name = "Latitude degrees".into();
|
||||
let updated = update_bundle(&compiled, &renamed, ProjectLimits::default()).unwrap();
|
||||
assert_eq!(updated.index.lexical_owners, compiled.index.lexical_owners);
|
||||
assert!(updated.bundle.files["contracts.arcadia"].contains("Latitude degrees"));
|
||||
assert!(
|
||||
bundle.files["project.arcadia"].contains("arcadia 3;"),
|
||||
"Migration does not mutate the caller's draft"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema4_typed_values_supertype_paths_and_protocols_roundtrip() {
|
||||
let source = r#"arcadia 4; project data_contract "Data contract" {
|
||||
sa_data_type bounded "Bounded" { definition { Integer { minimum -9223372036854775808; maximum 9223372036854775807; }; }; default { Integer -9223372036854775808; }; unit "m"; }
|
||||
sa_data_type label_type "Label" { definition { String { minimum_length 1; maximum_length 4; }; }; default { String "航海"; }; }
|
||||
sa_data_type base "Base" { definition { Record { supertype none; }; }; }
|
||||
sa_data_type derived "Derived" { definition { Record { supertype @base; }; }; }
|
||||
sa_exchange_item operation "Operation" { mechanism Operation;
|
||||
sa_data_field value "Value" { type_ref { Named @bounded; }; direction In; storage Value; default [{ Integer 42; }]; }
|
||||
}
|
||||
sa_interface contract "Contract" { supertype none; }
|
||||
interface_item_allocation member "Operation member" { source @contract; target @operation; transmission Synchronous; acquisition Accept; order 3; }
|
||||
data_association association "Related records" { source @base; target @derived; source_role "parent"; target_role "children"; source_minimum 0; source_maximum 1; target_minimum 0; target_maximum none; source_navigable true; target_navigable true; }
|
||||
}"#;
|
||||
let parsed = language::parse(source).unwrap();
|
||||
let formatted = language::format(&parsed);
|
||||
assert!(formatted.contains("supertype @base"));
|
||||
assert!(formatted.contains("transmission Synchronous"));
|
||||
assert_eq!(language::parse(&formatted).unwrap(), parsed);
|
||||
let compiled = compile_bundle(&format_bundle(&parsed), ProjectLimits::default()).unwrap();
|
||||
assert_eq!(compiled.decoded.project, parsed);
|
||||
assert!(compiled.decoded.migration.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omitted_ids_are_stabilized_before_allocation_identity_generation() {
|
||||
let bundle=SourceBundle{entry:"project.arcadia".into(),files:BTreeMap::from([("project.arcadia".into(),"arcadia 3; project old_model \"Old\" { sa_exchange_item event_item \"Event\" { kind Event; } sa_interface contract \"Contract\" { items [@event_item]; } }".into())])};
|
||||
let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
|
||||
let report = compiled.decoded.migration.as_ref().unwrap();
|
||||
let allocation = &report.generated_interface_allocations[0];
|
||||
assert_eq!(
|
||||
allocation.id,
|
||||
Id::new_v5(
|
||||
&compiled.decoded.project.id,
|
||||
format!(
|
||||
"v2-interface-item:{}:{}",
|
||||
allocation.source, allocation.target
|
||||
)
|
||||
.as_bytes()
|
||||
)
|
||||
);
|
||||
let reopened = compile_bundle(&compiled.bundle, ProjectLimits::default()).unwrap();
|
||||
assert_eq!(reopened.decoded.project, compiled.decoded.project);
|
||||
assert_eq!(reopened.bundle, compiled.bundle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inferring_a_legacy_path_keeps_rationale_inside_its_argument() {
|
||||
let source = include_str!("fixtures/v3-data-contract.arcadia")
|
||||
.replace(
|
||||
"type_ref { Named @coordinate; }; }\n }",
|
||||
"type_ref String; }\n }",
|
||||
)
|
||||
.replace(
|
||||
"field @latitude; expression",
|
||||
"field @latitude; // original argument rationale\nexpression",
|
||||
);
|
||||
let bundle = SourceBundle {
|
||||
entry: "project.arcadia".into(),
|
||||
files: BTreeMap::from([("project.arcadia".into(), source)]),
|
||||
};
|
||||
let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
|
||||
let source = &compiled.bundle.files["project.arcadia"];
|
||||
let argument = &source[source.find("values [{").unwrap()..];
|
||||
let argument = &argument[..argument.find('}').unwrap()];
|
||||
assert!(
|
||||
argument.contains("// original argument rationale"),
|
||||
"{source}"
|
||||
);
|
||||
assert!(argument.contains("path [@origin]"), "{source}");
|
||||
assert_eq!(compiled.index.lexical_owners[&id(17)], id(14));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified_value_reordering_and_path_reidentification_keep_each_arguments_comments() {
|
||||
let mut project = language::parse(include_str!("fixtures/v3-data-contract.arcadia")).unwrap();
|
||||
let Some(SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange { values, .. },
|
||||
..
|
||||
}) = project.elements.get_mut(&id(17)).unwrap().shared_mut()
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
*values = vec![
|
||||
MessageValue {
|
||||
path: vec![id(7)],
|
||||
field: id(6),
|
||||
expression: "origin value".into(),
|
||||
},
|
||||
MessageValue {
|
||||
path: vec![id(8)],
|
||||
field: id(6),
|
||||
expression: "destination value".into(),
|
||||
},
|
||||
];
|
||||
let mut bundle = format_bundle(&project);
|
||||
for source in bundle.files.values_mut() {
|
||||
*source = source
|
||||
.replace(
|
||||
"expression \"origin value\";",
|
||||
"expression \"origin value\"; // origin argument rationale",
|
||||
)
|
||||
.replace(
|
||||
"expression \"destination value\";",
|
||||
"expression \"destination value\"; // destination argument rationale",
|
||||
);
|
||||
}
|
||||
let current = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
|
||||
let Some(SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange { values, .. },
|
||||
..
|
||||
}) = project.elements.get_mut(&id(17)).unwrap().shared_mut()
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
values.reverse();
|
||||
let renamed = Id::from_u128(100);
|
||||
let mut identities: BTreeMap<_, _> = project
|
||||
.identities()
|
||||
.into_iter()
|
||||
.map(|(id, _)| (id, id))
|
||||
.collect();
|
||||
identities.insert(id(7), renamed);
|
||||
reidentify(&mut project, id(7), renamed).unwrap();
|
||||
project.elements.get_mut(&renamed).unwrap().alias = "revised_origin".into();
|
||||
let result =
|
||||
update_bundle_with_identity_map(¤t, &project, &identities, ProjectLimits::default())
|
||||
.unwrap();
|
||||
let source = &result.bundle.files[&result.index.declarations[&id(17)].path];
|
||||
let destination = source.find("// destination argument rationale").unwrap();
|
||||
let origin = source.find("// origin argument rationale").unwrap();
|
||||
assert!(
|
||||
destination < origin,
|
||||
"Distinct uses of the same terminal field must reorder as complete argument annotations: {source}"
|
||||
);
|
||||
assert!(source[origin..].contains("path [@revised_origin]"));
|
||||
assert_eq!(result.decoded.project, project);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ fn setup() -> tempfile::TempDir {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
fs::write(
|
||||
directory.path().join("project.arcadia"),
|
||||
"arcadia 3; project p \"P\" { include \"parts/a.arcadia\"; }\n",
|
||||
"arcadia 4; project p \"P\" { include \"parts/a.arcadia\"; }\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::create_dir(directory.path().join("parts")).unwrap();
|
||||
@@ -59,7 +59,7 @@ fn loading_keeps_original_no_uuid_bytes_and_saving_stabilizes_exactly_once() {
|
||||
fn invalid_and_missing_includes_keep_editable_text_and_located_diagnostics() {
|
||||
let directory = setup();
|
||||
let entry = directory.path().join("project.arcadia");
|
||||
fs::write(&entry, "arcadia 3; project p \"P\" {\n include \"missing.arcadia\";\n include \"parts/a.arcadia\";\n}\n").unwrap();
|
||||
fs::write(&entry, "arcadia 4; project p \"P\" {\n include \"missing.arcadia\";\n include \"parts/a.arcadia\";\n}\n").unwrap();
|
||||
fs::write(
|
||||
directory.path().join("parts/a.arcadia"),
|
||||
"oa_activity a \"A\" { invalid",
|
||||
@@ -102,7 +102,7 @@ fn discovery_never_refreshes_drafts_and_preserves_detached_buffers() {
|
||||
let overlays = BTreeMap::from([
|
||||
(
|
||||
"project.arcadia".into(),
|
||||
"arcadia 3; project p \"P\" { include \"new.arcadia\"; }".into(),
|
||||
"arcadia 4; project p \"P\" { include \"new.arcadia\"; }".into(),
|
||||
),
|
||||
(
|
||||
"parts/a.arcadia".into(),
|
||||
@@ -176,7 +176,7 @@ fn new_destinations_require_explicit_missing_expectations_and_reject_collisions(
|
||||
files: BTreeMap::from([
|
||||
(
|
||||
"project.arcadia".into(),
|
||||
"arcadia 3; project p \"P\" { include \"child.arcadia\"; }".into(),
|
||||
"arcadia 4; project p \"P\" { include \"child.arcadia\"; }".into(),
|
||||
),
|
||||
("child.arcadia".into(), "oa_activity a \"A\" {}".into()),
|
||||
]),
|
||||
@@ -256,7 +256,7 @@ fn physical_hard_link_aliases_are_rejected() {
|
||||
.unwrap();
|
||||
fs::write(
|
||||
directory.path().join("project.arcadia"),
|
||||
"arcadia 3; project p \"P\" { include \"parts/a.arcadia\"; include \"alias.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"parts/a.arcadia\"; include \"alias.arcadia\"; }",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
@@ -286,7 +286,7 @@ fn symlinks_to_outside_or_inside_project_are_rejected() {
|
||||
std::os::unix::fs::symlink(&destination, &link).unwrap();
|
||||
fs::write(
|
||||
directory.path().join("project.arcadia"),
|
||||
"arcadia 3; project p \"P\" { include \"alias.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"alias.arcadia\"; }",
|
||||
)
|
||||
.unwrap();
|
||||
let loaded = load_project(
|
||||
|
||||
@@ -6,10 +6,10 @@ fn source() -> CompiledSourceProject {
|
||||
&SourceBundle {
|
||||
entry: "project.arcadia".into(),
|
||||
files: BTreeMap::from([
|
||||
("project.arcadia".into(), "arcadia 3; project p \"Identity review\" { include \"team/first.arcadia\"; include \"team/second.arcadia\"; include \"contracts.arcadia\"; }".into()),
|
||||
("project.arcadia".into(), "arcadia 4; project p \"Identity review\" { include \"team/first.arcadia\"; include \"team/second.arcadia\"; include \"contracts.arcadia\"; }".into()),
|
||||
("team/first.arcadia".into(), "// Rationale for first\nsa_exchange_item first \"First\" { description \"First rationale\"; }\n".into()),
|
||||
("team/second.arcadia".into(), "// Rationale for second\nsa_exchange_item second \"Second\" {}\n".into()),
|
||||
("contracts.arcadia".into(), "sa_interface contract \"Contract\" { items [\n @first // first item rationale\n ,\n @second // second item rationale\n ]; }\n".into()),
|
||||
("contracts.arcadia".into(), "sa_event contract \"Contract\" { body { Signal { items [\n @first // first item rationale\n ,\n @second // second item rationale\n ]; }; }; }\n".into()),
|
||||
]),
|
||||
},
|
||||
ProjectLimits::default(),
|
||||
@@ -91,8 +91,9 @@ fn identity_swap_and_reorder_keep_both_declarations_and_item_comments_with_origi
|
||||
reidentify(&mut candidate, second, first).unwrap();
|
||||
reidentify(&mut candidate, temporary, second).unwrap();
|
||||
let contract = find(&candidate, "contract");
|
||||
let Some(SharedBody::Interface { items }) =
|
||||
candidate.elements.get_mut(&contract).unwrap().shared_mut()
|
||||
let Some(SharedBody::Event {
|
||||
body: EventBody::Signal { items },
|
||||
}) = candidate.elements.get_mut(&contract).unwrap().shared_mut()
|
||||
else {
|
||||
panic!("interface")
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ fn compile(a: &str, b: &str) -> CompiledSourceProject {
|
||||
compile_bundle(&SourceBundle {
|
||||
entry: "project.arcadia".into(),
|
||||
files: BTreeMap::from([
|
||||
("project.arcadia".into(), "arcadia 3; project p \"Nested writeback\" { include \"a.arcadia\"; include \"b.arcadia\"; }\n".into()),
|
||||
("project.arcadia".into(), "arcadia 4; project p \"Nested writeback\" { include \"a.arcadia\"; include \"b.arcadia\"; }\n".into()),
|
||||
("a.arcadia".into(), a.into()), ("b.arcadia".into(), b.into()),
|
||||
]),
|
||||
}, ProjectLimits::default()).unwrap()
|
||||
|
||||
@@ -30,7 +30,7 @@ fn id(project: &Project, alias: &str) -> Id {
|
||||
#[test]
|
||||
fn route_edits_keep_diagram_file_ownership_and_comments_with_edge_identity() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"Routing\" { include \"views.arcadia\"; include \"work.arcadia\"; }",
|
||||
"arcadia 4; project p \"Routing\" { include \"views.arcadia\"; include \"work.arcadia\"; }",
|
||||
&[
|
||||
(
|
||||
"work.arcadia",
|
||||
@@ -95,7 +95,7 @@ diagram review "Review" {
|
||||
#[test]
|
||||
fn invalid_cross_file_route_references_and_geometry_locate_the_authored_view() {
|
||||
let mut input = bundle(
|
||||
"arcadia 3; project p \"Routing\" { include \"views.arcadia\"; include \"work.arcadia\"; }",
|
||||
"arcadia 4; project p \"Routing\" { include \"views.arcadia\"; include \"work.arcadia\"; }",
|
||||
&[
|
||||
(
|
||||
"work.arcadia",
|
||||
@@ -127,7 +127,7 @@ fn invalid_cross_file_route_references_and_geometry_locate_the_authored_view() {
|
||||
|
||||
fn typed() -> SourceBundle {
|
||||
bundle(
|
||||
"arcadia 3; project test_model \"航海\" {\n include \"exchanges.arcadia\";\n include \"work/send.arcadia\";\n include \"work/receive.arcadia\";\n include \"types.arcadia\";\n}",
|
||||
"arcadia 4; project test_model \"航海\" {\n include \"exchanges.arcadia\";\n include \"work/send.arcadia\";\n include \"work/receive.arcadia\";\n include \"types.arcadia\";\n}",
|
||||
&[
|
||||
(
|
||||
"exchanges.arcadia",
|
||||
@@ -194,7 +194,7 @@ fn cross_file_typed_ports_global_forward_references_and_stable_ids() {
|
||||
#[test]
|
||||
fn advisory_lint_locations_refer_to_raw_drafts_before_identity_stabilization() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"work.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"work.arcadia\"; }",
|
||||
&[(
|
||||
"work.arcadia",
|
||||
"sa_function first \"First\" {}\nsa_function second \"Second\" {}\n",
|
||||
@@ -228,7 +228,7 @@ fn advisory_lint_locations_refer_to_raw_drafts_before_identity_stabilization() {
|
||||
#[test]
|
||||
fn source_errors_keep_exact_fragment_unicode_positions() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"child.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"child.arcadia\"; }",
|
||||
&[(
|
||||
"child.arcadia",
|
||||
"// Пример\nsa_function child \"航海\" { parent @missing; }",
|
||||
@@ -274,7 +274,7 @@ fn duplicate_alias_and_identity_report_both_files_including_project_identity() {
|
||||
),
|
||||
] {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"a.arcadia\"; include \"b.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"a.arcadia\"; include \"b.arcadia\"; }",
|
||||
&[("a.arcadia", left), ("b.arcadia", right)],
|
||||
);
|
||||
let error = compile_bundle(&input, ProjectLimits::default())
|
||||
@@ -285,7 +285,7 @@ fn duplicate_alias_and_identity_report_both_files_including_project_identity() {
|
||||
assert_eq!(error.related[0].path, "a.arcadia");
|
||||
}
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"child.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"child.arcadia\"; }",
|
||||
&[("child.arcadia", "oa_activity p \"Collision\" {}")],
|
||||
);
|
||||
let error = compile_bundle(&input, ProjectLimits::default())
|
||||
@@ -297,7 +297,7 @@ fn duplicate_alias_and_identity_report_both_files_including_project_identity() {
|
||||
#[test]
|
||||
fn includes_are_once_only_and_cycles_missing_and_unreachable_files_are_located() {
|
||||
let mut input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"sub/a.arcadia\"; include \"sub/b.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"sub/a.arcadia\"; include \"sub/b.arcadia\"; }",
|
||||
&[
|
||||
("sub/a.arcadia", "include \"../shared.arcadia\";"),
|
||||
("sub/b.arcadia", "include \"../shared.arcadia\";"),
|
||||
@@ -356,7 +356,7 @@ fn portable_paths_reject_escapes_devices_urls_and_case_collisions() {
|
||||
);
|
||||
assert!(resolve_include("../bad.arcadia", "a.arcadia").is_err());
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" {}",
|
||||
"arcadia 4; project p \"P\" {}",
|
||||
&[("A.arcadia", ""), ("a.arcadia", "")],
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -370,7 +370,7 @@ fn portable_paths_reject_escapes_devices_urls_and_case_collisions() {
|
||||
#[test]
|
||||
fn project_admission_is_aggregate_and_precedes_global_resolution() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"a.arcadia\"; include \"b.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"a.arcadia\"; include \"b.arcadia\"; }",
|
||||
&[
|
||||
("a.arcadia", "oa_activity one \"One\" { parent @missing; }"),
|
||||
("b.arcadia", "oa_activity two \"Two\" {}"),
|
||||
@@ -591,7 +591,7 @@ fn failed_update_preserves_candidate_and_rejects_stale_index() {
|
||||
|
||||
#[test]
|
||||
fn single_string_and_legacy_apis_remain_explicit_and_filesystem_free() {
|
||||
let source = "arcadia 3; project p \"P\" { include \"missing.arcadia\"; }";
|
||||
let source = "arcadia 4; project p \"P\" { include \"missing.arcadia\"; }";
|
||||
assert!(
|
||||
language::parse(source).unwrap_err()[0]
|
||||
.message
|
||||
@@ -681,7 +681,7 @@ fn json_bundle_rejects_duplicate_file_keys_and_unknown_fields() {
|
||||
#[test]
|
||||
fn generated_id_insertion_handles_adjacent_header_tokens_and_remains_exactly_indexed() {
|
||||
let input = bundle(
|
||||
"arcadia 3;project p\"P\"{include \"child.arcadia\";}",
|
||||
"arcadia 4;project p\"P\"{include \"child.arcadia\";}",
|
||||
&[(
|
||||
"child.arcadia",
|
||||
"oa_activity a\"A\"{}diagram d\"D\"{}node n{diagram @d;element @a;}",
|
||||
@@ -703,7 +703,7 @@ fn generated_id_insertion_handles_adjacent_header_tokens_and_remains_exactly_ind
|
||||
#[test]
|
||||
fn duplicate_include_edges_count_toward_admission_and_ordered_references_are_aggregate() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"a.arcadia\"; include \"a.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"a.arcadia\"; include \"a.arcadia\"; }",
|
||||
&[("a.arcadia", "")],
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -720,7 +720,7 @@ fn duplicate_include_edges_count_toward_admission_and_ordered_references_are_agg
|
||||
"project_limit"
|
||||
);
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"a.arcadia\"; include \"b.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"a.arcadia\"; include \"b.arcadia\"; }",
|
||||
&[
|
||||
(
|
||||
"a.arcadia",
|
||||
@@ -763,7 +763,7 @@ fn include_inspection_reuses_strict_entry_fragment_syntax_and_real_spans() {
|
||||
&source[span.start..span.end],
|
||||
"include \"../shared.arcadia\";"
|
||||
);
|
||||
let entry = "arcadia 3; project p \"P\" { include \"sub/work.arcadia\"; }";
|
||||
let entry = "arcadia 4; project p \"P\" { include \"sub/work.arcadia\"; }";
|
||||
assert_eq!(
|
||||
inspect_includes("project.arcadia", entry, true, ProjectLimits::default()).unwrap()[0]
|
||||
.target,
|
||||
@@ -829,7 +829,7 @@ fn comments(source: &str) -> Vec<String> {
|
||||
|
||||
#[test]
|
||||
fn scalar_header_guard_and_project_edits_preserve_exact_crlf_trivia_and_escape_spelling() {
|
||||
let entry = "arcadia 3;\r\nproject p // project title\r\n \"Original project\" {\r\n description // project reason\r\n \"Before\";\r\n include \"behavior.arcadia\";\r\n}\r\n";
|
||||
let entry = "arcadia 4;\r\nproject p // project title\r\n \"Original project\" {\r\n description // project reason\r\n \"Before\";\r\n include \"behavior.arcadia\";\r\n}\r\n";
|
||||
let source = "oa_state_machine machine \"Machine\" {}\r\noa_region region_one \"Region\" { parent @machine; }\r\noa_state first \"First\" { parent @region_one; }\r\noa_state second \"Second\" { parent @region_one; }\r\nstate_transition route // route title\r\n \"Original transition\" {\r\n\tguard // feasibility\r\n\t \"old guard\"; // retain explanation\r\n\tdescription \"Keep\\u0020spelling\";\r\n\tsource @ // endpoint proof\r\n first; target @second;\r\n}\r\n";
|
||||
let current = compile(&bundle(entry, &[("behavior.arcadia", source)]));
|
||||
let mut project = current.decoded.project.clone();
|
||||
@@ -864,7 +864,7 @@ fn scalar_header_guard_and_project_edits_preserve_exact_crlf_trivia_and_escape_s
|
||||
#[test]
|
||||
fn alias_updates_touch_only_header_and_reference_tokens_even_with_comments_after_at() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"work.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"work.arcadia\"; }",
|
||||
&[(
|
||||
"work.arcadia",
|
||||
"oa_activity root_work // root rationale\n \"Root\\u0020name\" { description \"root_work is prose\"; }\noa_activity child \"Child\" { parent @ // parent proof\n root_work; }\n",
|
||||
@@ -887,7 +887,7 @@ fn alias_updates_touch_only_header_and_reference_tokens_even_with_comments_after
|
||||
#[test]
|
||||
fn omitted_defaults_stay_omitted_and_new_fields_do_not_reformat_surviving_fields() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"work.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"work.arcadia\"; }",
|
||||
&[(
|
||||
"work.arcadia",
|
||||
"oa_activity work \"Work\" {\r\n\t// owner rationale\r\n\tparent none ; // preserve spacing\r\n}\r\n",
|
||||
@@ -913,7 +913,7 @@ fn omitted_defaults_stay_omitted_and_new_fields_do_not_reformat_surviving_fields
|
||||
#[test]
|
||||
fn kind_change_removes_field_syntax_but_keeps_its_comments_and_other_fields() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"item.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"item.arcadia\"; }",
|
||||
&[(
|
||||
"item.arcadia",
|
||||
"oa_exchange_item item \"Item\" {\n description \"Keep\\u0020this\" ; // description note\n kind // classification rationale\n Information ;\n parent none;\n}\n",
|
||||
@@ -953,7 +953,7 @@ fn significant_words(source: &str) -> Vec<String> {
|
||||
#[test]
|
||||
fn constructor_tag_changes_retain_shared_nested_fields_and_removed_field_comments() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"event.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"event.arcadia\"; }",
|
||||
&[(
|
||||
"event.arcadia",
|
||||
"oa_event timer \"Timer\" { body { Time // time rationale\n { expression // expression rationale\n \"before\" ; kind After; // clock rationale\n }; }; }\n",
|
||||
@@ -986,7 +986,7 @@ fn constructor_tag_changes_retain_shared_nested_fields_and_removed_field_comment
|
||||
#[test]
|
||||
fn scalar_record_constructor_changes_keep_comments_inside_removed_values() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"ports.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"ports.arcadia\"; }",
|
||||
&[(
|
||||
"ports.arcadia",
|
||||
"sa_function work \"Work\" {}\nsa_port input \"Input\" { parent @work; contract Legacy; // contract rationale\n legacy { direction In; // direction rationale\n interface none; // interface rationale\n }; }\n",
|
||||
@@ -1017,10 +1017,10 @@ fn scalar_record_constructor_changes_keep_comments_inside_removed_values() {
|
||||
#[test]
|
||||
fn reference_list_reordering_carries_item_comments_and_alias_spelling_with_identity() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"types.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"types.arcadia\"; }",
|
||||
&[(
|
||||
"types.arcadia",
|
||||
"sa_exchange_item a \"A\" {} sa_exchange_item b \"B\" {}\nsa_interface service \"Service\" { items [\n // before A\n @ // A reference rationale\n a // after A\n ,\n // before B\n @b // after B\n ]; }\n",
|
||||
"sa_exchange_item a \"A\" {} sa_exchange_item b \"B\" {}\nsa_event service \"Service\" { body { Signal { items [\n // before A\n @ // A reference rationale\n a // after A\n ,\n // before B\n @b // after B\n ]; }; }; }\n",
|
||||
)],
|
||||
);
|
||||
let current = compile(&input);
|
||||
@@ -1029,8 +1029,9 @@ fn reference_list_reordering_carries_item_comments_and_alias_spelling_with_ident
|
||||
let b = id(&project, "b");
|
||||
let service = id(&project, "service");
|
||||
project.elements.get_mut(&a).unwrap().alias = "renamed_a".into();
|
||||
if let Some(SharedBody::Interface { items }) =
|
||||
project.elements.get_mut(&service).unwrap().shared_mut()
|
||||
if let Some(SharedBody::Event {
|
||||
body: EventBody::Signal { items },
|
||||
}) = project.elements.get_mut(&service).unwrap().shared_mut()
|
||||
{
|
||||
*items = vec![b, a];
|
||||
} else {
|
||||
@@ -1051,10 +1052,10 @@ fn reference_list_reordering_carries_item_comments_and_alias_spelling_with_ident
|
||||
#[test]
|
||||
fn removed_list_items_and_declarations_keep_comments_as_enclosing_annotations() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"types.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"types.arcadia\"; }",
|
||||
&[(
|
||||
"types.arcadia",
|
||||
"sa_exchange_item a \"A\" { // declaration explanation\n } sa_exchange_item b \"B\" {}\nsa_interface service \"Service\" { items [\n // explanation for removed A\n @a,\n // explanation for retained B\n @b ]; }\n",
|
||||
"sa_exchange_item a \"A\" { // declaration explanation\n } sa_exchange_item b \"B\" {}\nsa_event service \"Service\" { body { Signal { items [\n // explanation for removed A\n @a,\n // explanation for retained B\n @b ]; }; }; }\n",
|
||||
)],
|
||||
);
|
||||
let current = compile(&input);
|
||||
@@ -1063,8 +1064,9 @@ fn removed_list_items_and_declarations_keep_comments_as_enclosing_annotations()
|
||||
let b = id(&project, "b");
|
||||
let service = id(&project, "service");
|
||||
project.elements.remove(&a);
|
||||
if let Some(SharedBody::Interface { items }) =
|
||||
project.elements.get_mut(&service).unwrap().shared_mut()
|
||||
if let Some(SharedBody::Event {
|
||||
body: EventBody::Signal { items },
|
||||
}) = project.elements.get_mut(&service).unwrap().shared_mut()
|
||||
{
|
||||
*items = vec![b];
|
||||
} else {
|
||||
@@ -1102,7 +1104,7 @@ oa_occurrence message "Message" { parent @conversation; body { Exchange {
|
||||
}; }; }
|
||||
"#;
|
||||
let current = compile(&bundle(
|
||||
"arcadia 3; project p \"P\" { include \"conversation.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"conversation.arcadia\"; }",
|
||||
&[("conversation.arcadia", source)],
|
||||
));
|
||||
let mut project = current.decoded.project.clone();
|
||||
@@ -1151,7 +1153,7 @@ oa_occurrence message "Message" { parent @conversation; body { Exchange {
|
||||
#[test]
|
||||
fn added_list_items_empty_lists_and_literal_changes_preserve_existing_annotations() {
|
||||
let input = bundle(
|
||||
"arcadia 3; project p \"P\" { include \"types.arcadia\"; }",
|
||||
"arcadia 4; project p \"P\" { include \"types.arcadia\"; }",
|
||||
&[(
|
||||
"types.arcadia",
|
||||
"oa_data_type values \"Values\" { definition { Enumeration { literals [ // enumeration rationale\n ]; }; }; }\n",
|
||||
@@ -1160,7 +1162,7 @@ fn added_list_items_empty_lists_and_literal_changes_preserve_existing_annotation
|
||||
let empty = compile(&input);
|
||||
let mut project = empty.decoded.project.clone();
|
||||
let values = id(&project, "values");
|
||||
if let Some(SharedBody::DataType { definition }) =
|
||||
if let Some(SharedBody::DataType { definition, .. }) =
|
||||
project.elements.get_mut(&values).unwrap().shared_mut()
|
||||
{
|
||||
*definition = DataTypeDefinition::Enumeration {
|
||||
@@ -1185,7 +1187,7 @@ fn added_list_items_empty_lists_and_literal_changes_preserve_existing_annotation
|
||||
.replace("\"Beta\"", "\"Beta\" // beta explanation\n"),
|
||||
);
|
||||
let annotated = compile(&annotated);
|
||||
if let Some(SharedBody::DataType { definition }) =
|
||||
if let Some(SharedBody::DataType { definition, .. }) =
|
||||
project.elements.get_mut(&values).unwrap().shared_mut()
|
||||
{
|
||||
*definition = DataTypeDefinition::Enumeration {
|
||||
|
||||
Reference in New Issue
Block a user