use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, BTreeSet}, fmt, }; use uuid::Uuid; pub type Id = Uuid; pub const SCHEMA_VERSION: u32 = 7; pub const MAX_COORDINATE: f32 = 1_000_000.; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum Layer { Oa, Sa, } impl fmt::Display for Layer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Self::Oa => "OA", Self::Sa => "SA", }) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum Kind { Entity, Actor, Role, Activity, Capability, OperationalMission, Process, System, SystemActor, Function, SystemCapability, Mission, FunctionalChain, ExchangeItem, ExchangeCategory, DataType, DataField, Interface, Port, Scenario, Lifeline, Occurrence, StateMachine, Region, State, Mode, Initial, Final, Choice, Junction, Fork, Join, ShallowHistory, DeepHistory, EntryPoint, ExitPoint, Terminate, Event, Constraint, } impl Kind { pub const ALL: [Self; 39] = [ Self::Entity, Self::Actor, Self::Role, Self::Activity, Self::Capability, Self::OperationalMission, Self::Process, Self::System, Self::SystemActor, Self::Function, Self::SystemCapability, Self::Mission, Self::FunctionalChain, Self::ExchangeItem, Self::ExchangeCategory, Self::DataType, Self::DataField, Self::Interface, Self::Port, Self::Scenario, Self::Lifeline, Self::Occurrence, Self::StateMachine, Self::Region, Self::State, Self::Mode, Self::Initial, Self::Final, Self::Choice, Self::Junction, Self::Fork, Self::Join, Self::ShallowHistory, Self::DeepHistory, Self::EntryPoint, Self::ExitPoint, Self::Terminate, Self::Event, Self::Constraint, ]; pub fn label(self) -> &'static str { match self { Self::Entity => "Entity", Self::Actor => "Actor", Self::Role => "Role", Self::Activity => "Activity", Self::Capability => "Capability", Self::OperationalMission => "Operational mission", Self::Process => "Process", Self::System => "System", Self::SystemActor => "System actor", Self::Function => "Function", Self::SystemCapability => "System capability", Self::Mission => "Mission", Self::FunctionalChain => "Functional chain", Self::ExchangeItem => "Exchange item", Self::ExchangeCategory => "Exchange category", Self::DataType => "Data type", Self::DataField => "Data field", Self::Interface => "Interface", Self::Port => "Port", Self::Scenario => "Scenario", Self::Lifeline => "Lifeline", Self::Occurrence => "Occurrence", Self::StateMachine => "State machine", Self::Region => "Region", Self::State => "State", Self::Mode => "Mode", Self::Initial => "Initial", Self::Final => "Final", Self::Choice => "Choice", Self::Junction => "Junction", Self::Fork => "Fork", Self::Join => "Join", Self::ShallowHistory => "Shallow history", Self::DeepHistory => "Deep history", Self::EntryPoint => "Entry point", Self::ExitPoint => "Exit point", Self::Terminate => "Terminate", Self::Event => "Event", Self::Constraint => "Constraint", } } pub fn is_performer(self) -> bool { matches!( self, Self::Entity | Self::Actor | Self::System | Self::SystemActor ) } pub fn is_work(self) -> bool { matches!(self, Self::Activity | Self::Function) } pub fn is_sequence(self) -> bool { matches!(self, Self::Process | Self::FunctionalChain | Self::Scenario) } pub fn is_state(self) -> bool { self.state_kind().is_some() } pub fn state_kind(self) -> Option { Some(match self { Self::State => StateKind::State, Self::Mode => StateKind::Mode, Self::Initial => StateKind::Initial, Self::Final => StateKind::Final, Self::Choice => StateKind::Choice, Self::Junction => StateKind::Junction, Self::Fork => StateKind::Fork, Self::Join => StateKind::Join, Self::ShallowHistory => StateKind::ShallowHistory, Self::DeepHistory => StateKind::DeepHistory, Self::EntryPoint => StateKind::EntryPoint, Self::ExitPoint => StateKind::ExitPoint, Self::Terminate => StateKind::Terminate, _ => return None, }) } pub fn is_stable_state(self) -> bool { matches!(self, Self::State | Self::Mode) } pub fn supports(self, layer: Layer) -> bool { match self { Self::Entity | Self::Actor | Self::Role | Self::Activity | Self::Capability | Self::OperationalMission | Self::Process => layer == Layer::Oa, Self::System | Self::SystemActor | Self::Function | Self::SystemCapability | Self::Mission | Self::FunctionalChain => layer == Layer::Sa, _ => true, } } } impl fmt::Display for Kind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.label()) } } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Position { pub x: f32, pub y: f32, } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Size { pub width: f32, pub height: f32, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Element { pub id: Id, pub alias: String, pub name: String, pub description: String, pub parent: Option, pub body: ElementBody, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum ElementBody { Oa(OaBody), Sa(SaBody), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum OaBody { Entity, Actor, Role, Activity, Capability, Mission, Process { steps: Vec }, Shared(SharedBody), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum SaBody { System, Actor, Function, Capability, Mission, FunctionalChain { steps: Vec }, Shared(SharedBody), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ExchangeItemKind { Information, Material, Energy, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum ExchangeMechanism { #[default] Unspecified, Flow, Event, Operation, SharedData, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum ParameterDirection { #[default] Unspecified, In, Out, InOut, Return, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum FieldStorage { #[default] Value, Reference, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum DataValue { Boolean(bool), Integer(i64), Real(f64), String(String), Enumeration(String), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum TransmissionProtocol { #[default] Unspecified, Synchronous, Asynchronous, Unicast, Multicast, Broadcast, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum AcquisitionProtocol { #[default] Unspecified, Read, Accept, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum DataTypeDefinition { Boolean, Integer { minimum: Option, maximum: Option, }, Real { minimum: Option, maximum: Option, }, String { minimum_length: u32, maximum_length: Option, }, Enumeration { literals: Vec, }, Record { supertype: Option, }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum TypeRef { Boolean, Integer, Real, String, Named(Id), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum PortDirection { In, Out, InOut, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ComponentPortKind { Standard, Flow, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ComponentPortOrientation { Unset, In, Out, InOut, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct LegacyPortContract { pub direction: PortDirection, pub interface: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum PortContract { FunctionInput { incoming_items: Vec, }, FunctionOutput { outgoing_items: Vec, }, Component { kind: ComponentPortKind, orientation: ComponentPortOrientation, }, Legacy, } impl PortContract { pub fn items(&self) -> &[Id] { match self { Self::FunctionInput { incoming_items } => incoming_items, Self::FunctionOutput { outgoing_items } => outgoing_items, _ => &[], } } pub fn is_function(&self) -> bool { matches!( self, Self::FunctionInput { .. } | Self::FunctionOutput { .. } ) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum StateKind { State, Mode, Initial, Final, Choice, Junction, Fork, Join, ShallowHistory, DeepHistory, EntryPoint, ExitPoint, Terminate, } impl StateKind { pub fn kind(self) -> Kind { match self { Self::State => Kind::State, Self::Mode => Kind::Mode, Self::Initial => Kind::Initial, Self::Final => Kind::Final, Self::Choice => Kind::Choice, Self::Junction => Kind::Junction, Self::Fork => Kind::Fork, Self::Join => Kind::Join, Self::ShallowHistory => Kind::ShallowHistory, Self::DeepHistory => Kind::DeepHistory, Self::EntryPoint => Kind::EntryPoint, Self::ExitPoint => Kind::ExitPoint, Self::Terminate => Kind::Terminate, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum TimeEventKind { After, At, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum EventBody { Signal { items: Vec, }, Time { kind: TimeEventKind, expression: String, }, Change { expression: String, }, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum TransitionKind { #[default] External, Local, Internal, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum StateActionKind { Entry, Do, Exit, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum FragmentOperator { Alternative, Parallel, Loop, Optional, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ControlKind { Choice, Merge, Fork, Join, Loop, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum ScenarioViewpoint { OperationalActivity, ParticipantInteraction, Functional, BoundaryInterface, #[default] MixedLegacy, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde(deny_unknown_fields)] pub enum MessageKind { #[default] Asynchronous, Call, Reply, Create, Delete, Timer { expression: String, }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct LifelineBinding { pub referenced: Id, pub local: Id, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MessageValue { #[serde(default)] pub path: Vec, pub field: Id, pub expression: String, } /// Explicit operation intent. A missing allocation remains an unfinished operation, /// whereas an absent OperationUse retains ordinary channel-message semantics. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct OperationUse { pub allocation: Option, } /// A contribution in a caller instance. Paths contain reference occurrence IDs, /// never sequence definitions; repeated references therefore remain distinct. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct OccurrenceEndpoint { pub references: Vec, pub occurrence: Id, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum ContextualLinkPurpose { Precedence { guard: String }, Exchange { exchange: Id }, CommonWork { work: Id }, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum OccurrenceBody { Exchange { exchange: Option, sender: Option, receiver: Option, #[serde(default)] message_kind: MessageKind, #[serde(default)] reply_to: Option, #[serde(default)] values: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] operation: Option, }, Action { action: Option, lifeline: Option, }, Control { kind: ControlKind, }, SequenceReference { sequence: Option, }, ScenarioUse { scenario: Option, bindings: Vec, }, State { state: Option, lifeline: Option, }, Fragment { operator: FragmentOperator, branches: Vec, }, Branch { guard: String, occurrences: Vec, }, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum SharedBody { ExchangeCategory { family: crate::view_options::ExchangeFamily, exchanges: Vec, }, ExchangeItem { kind: ExchangeItemKind, mechanism: ExchangeMechanism, supertype: Option, }, DataType { definition: DataTypeDefinition, default: Option, unit: String, }, DataField { type_ref: TypeRef, minimum: u32, maximum: Option, unit: String, direction: ParameterDirection, storage: FieldStorage, default: Option>, }, Interface { supertype: Option, }, Port { contract: PortContract, legacy: Option, }, Scenario { #[serde(default)] viewpoint: ScenarioViewpoint, context: Option, precondition: String, postcondition: String, steps: Vec, }, Lifeline { represented: Option, }, Occurrence { body: OccurrenceBody, precondition: String, postcondition: String, }, StateMachine, Event { body: EventBody, }, Region, State { kind: StateKind, entry: String, exit: String, do_activity: String, }, Constraint { expression: String, constrained: Vec, }, } impl Element { pub fn layer(&self) -> Layer { self.body.layer() } pub fn kind(&self) -> Kind { self.body.kind() } pub fn shared(&self) -> Option<&SharedBody> { self.body.shared() } pub fn shared_mut(&mut self) -> Option<&mut SharedBody> { self.body.shared_mut() } pub fn steps(&self) -> Option<&Vec> { self.body.steps() } pub fn steps_mut(&mut self) -> Option<&mut Vec> { self.body.steps_mut() } } impl ElementBody { pub fn layer(&self) -> Layer { match self { Self::Oa(_) => Layer::Oa, Self::Sa(_) => Layer::Sa, } } pub fn shared(&self) -> Option<&SharedBody> { match self { Self::Oa(OaBody::Shared(v)) | Self::Sa(SaBody::Shared(v)) => Some(v), _ => None, } } pub fn shared_mut(&mut self) -> Option<&mut SharedBody> { match self { Self::Oa(OaBody::Shared(v)) | Self::Sa(SaBody::Shared(v)) => Some(v), _ => None, } } pub fn steps(&self) -> Option<&Vec> { match self { Self::Oa(OaBody::Process { steps }) | Self::Sa(SaBody::FunctionalChain { steps }) => { Some(steps) } _ => match self.shared() { Some(SharedBody::Scenario { steps, .. }) => Some(steps), Some(SharedBody::Occurrence { body: OccurrenceBody::Branch { occurrences, .. }, .. }) => Some(occurrences), _ => None, }, } } pub fn steps_mut(&mut self) -> Option<&mut Vec> { match self { Self::Oa(OaBody::Process { steps }) | Self::Sa(SaBody::FunctionalChain { steps }) => { Some(steps) } _ => match self.shared_mut() { Some(SharedBody::Scenario { steps, .. }) => Some(steps), Some(SharedBody::Occurrence { body: OccurrenceBody::Branch { occurrences, .. }, .. }) => Some(occurrences), _ => None, }, } } pub fn kind(&self) -> Kind { match self { Self::Oa(v) => match v { OaBody::Entity => Kind::Entity, OaBody::Actor => Kind::Actor, OaBody::Role => Kind::Role, OaBody::Activity => Kind::Activity, OaBody::Capability => Kind::Capability, OaBody::Mission => Kind::OperationalMission, OaBody::Process { .. } => Kind::Process, OaBody::Shared(s) => s.kind(), }, Self::Sa(v) => match v { SaBody::System => Kind::System, SaBody::Actor => Kind::SystemActor, SaBody::Function => Kind::Function, SaBody::Capability => Kind::SystemCapability, SaBody::Mission => Kind::Mission, SaBody::FunctionalChain { .. } => Kind::FunctionalChain, SaBody::Shared(s) => s.kind(), }, } } pub fn shared_in(layer: Layer, body: SharedBody) -> Self { match layer { Layer::Oa => Self::Oa(OaBody::Shared(body)), Layer::Sa => Self::Sa(SaBody::Shared(body)), } } pub fn new(kind: Kind, layer: Layer) -> Option { if !kind.supports(layer) { return None; } Some(match kind { Kind::Entity => Self::Oa(OaBody::Entity), Kind::Actor => Self::Oa(OaBody::Actor), Kind::Role => Self::Oa(OaBody::Role), Kind::Activity => Self::Oa(OaBody::Activity), Kind::Capability => Self::Oa(OaBody::Capability), Kind::OperationalMission => Self::Oa(OaBody::Mission), Kind::Process => Self::Oa(OaBody::Process { steps: vec![] }), Kind::System => Self::Sa(SaBody::System), Kind::SystemActor => Self::Sa(SaBody::Actor), Kind::Function => Self::Sa(SaBody::Function), Kind::SystemCapability => Self::Sa(SaBody::Capability), Kind::Mission => Self::Sa(SaBody::Mission), Kind::FunctionalChain => Self::Sa(SaBody::FunctionalChain { steps: vec![] }), k => Self::shared_in( layer, match k { Kind::ExchangeItem => SharedBody::ExchangeItem { kind: ExchangeItemKind::Information, mechanism: ExchangeMechanism::Unspecified, supertype: None, }, Kind::DataType => SharedBody::DataType { definition: DataTypeDefinition::String { minimum_length: 0, maximum_length: None, }, default: None, unit: String::new(), }, Kind::ExchangeCategory => SharedBody::ExchangeCategory { family: crate::view_options::ExchangeFamily::Work, exchanges: vec![], }, Kind::DataField => SharedBody::DataField { type_ref: TypeRef::String, minimum: 1, maximum: Some(1), unit: String::new(), direction: ParameterDirection::Unspecified, storage: FieldStorage::Value, default: None, }, Kind::Interface => SharedBody::Interface { supertype: None }, // Port creation requires an explicit contract and owner. Kind::Port => return None, Kind::Scenario => SharedBody::Scenario { viewpoint: if layer == Layer::Oa { ScenarioViewpoint::OperationalActivity } else { ScenarioViewpoint::Functional }, context: None, precondition: String::new(), postcondition: String::new(), steps: vec![], }, Kind::Lifeline => SharedBody::Lifeline { represented: None }, Kind::Occurrence => SharedBody::Occurrence { body: OccurrenceBody::Exchange { exchange: None, sender: None, receiver: None, message_kind: MessageKind::Asynchronous, reply_to: None, values: vec![], operation: None, }, precondition: String::new(), postcondition: String::new(), }, Kind::StateMachine => SharedBody::StateMachine, Kind::Event => SharedBody::Event { body: EventBody::Signal { items: vec![] }, }, Kind::Region => SharedBody::Region, k if k.is_state() => SharedBody::State { kind: k.state_kind().expect("state kind"), entry: String::new(), exit: String::new(), do_activity: String::new(), }, Kind::Constraint => SharedBody::Constraint { expression: String::new(), constrained: vec![], }, _ => unreachable!(), }, ), }) } } impl SharedBody { pub fn kind(&self) -> Kind { match self { Self::ExchangeItem { .. } => Kind::ExchangeItem, Self::ExchangeCategory { .. } => Kind::ExchangeCategory, Self::DataType { .. } => Kind::DataType, Self::DataField { .. } => Kind::DataField, Self::Interface { .. } => Kind::Interface, Self::Port { .. } => Kind::Port, Self::Scenario { .. } => Kind::Scenario, Self::Lifeline { .. } => Kind::Lifeline, Self::Occurrence { .. } => Kind::Occurrence, Self::StateMachine => Kind::StateMachine, Self::Event { .. } => Kind::Event, Self::Region => Kind::Region, Self::State { kind, .. } => kind.kind(), Self::Constraint { .. } => Kind::Constraint, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum RelationKind { InterfaceItemAllocation, DataAssociation, Allocation, PortAllocation, Interaction, CapabilityInvolvement, ChainInvolvement, RoleRealization, RoleAssignment, FunctionalExchange, BoundaryExchange, CommunicationMean, MissionCapability, CapabilityInclude, CapabilityExtend, CapabilityGeneralization, MissionInvolvement, SequenceLink, ContextualLink, StateTransition, Availability, ExchangeAllocation, InterfaceProvision, InterfaceRequirement, StateAction, } impl RelationKind { pub const ALL: [Self; 25] = [ Self::InterfaceItemAllocation, Self::DataAssociation, Self::Allocation, Self::PortAllocation, Self::Interaction, Self::CapabilityInvolvement, Self::ChainInvolvement, Self::RoleRealization, Self::RoleAssignment, Self::FunctionalExchange, Self::BoundaryExchange, Self::CommunicationMean, Self::MissionCapability, Self::CapabilityInclude, Self::CapabilityExtend, Self::CapabilityGeneralization, Self::MissionInvolvement, Self::SequenceLink, Self::ContextualLink, Self::StateTransition, Self::Availability, Self::ExchangeAllocation, Self::InterfaceProvision, Self::InterfaceRequirement, Self::StateAction, ]; pub fn permits(self, from: Kind, to: Kind) -> bool { match self { Self::InterfaceItemAllocation => from == Kind::Interface && to == Kind::ExchangeItem, Self::DataAssociation => from == Kind::DataType && to == Kind::DataType, Self::Allocation => from.is_performer() && to.is_work(), Self::PortAllocation => from == Kind::Port && to == Kind::Port, Self::Interaction => from == Kind::Activity && to == Kind::Activity, Self::CapabilityInvolvement => { (from.is_performer() || from.is_work() || from == Kind::Role) && matches!(to, Kind::Capability | Kind::SystemCapability) } Self::ChainInvolvement => { matches!(from, Kind::Process | Kind::FunctionalChain) && matches!(to, Kind::Capability | Kind::SystemCapability) } Self::RoleRealization => matches!(from, Kind::Entity | Kind::Actor) && to == Kind::Role, Self::RoleAssignment => from == Kind::Role && to == Kind::Activity, Self::FunctionalExchange => from == Kind::Function && to == Kind::Function, Self::BoundaryExchange => from == Kind::Port && to == Kind::Port, Self::CommunicationMean => { matches!(from, Kind::Entity | Kind::Actor) && matches!(to, Kind::Entity | Kind::Actor) } Self::MissionCapability => matches!( (from, to), (Kind::OperationalMission, Kind::Capability) | (Kind::Mission, Kind::SystemCapability) ), Self::CapabilityInclude | Self::CapabilityExtend | Self::CapabilityGeneralization => { matches!( (from, to), (Kind::Capability, Kind::Capability) | (Kind::SystemCapability, Kind::SystemCapability) ) } Self::MissionInvolvement => matches!( (from, to), (Kind::Entity | Kind::Actor, Kind::OperationalMission) | (Kind::System | Kind::SystemActor, Kind::Mission) ), Self::SequenceLink | Self::ContextualLink => { from == Kind::Occurrence && to == Kind::Occurrence } Self::StateTransition => from.is_state() && to.is_state(), Self::ExchangeAllocation => false, Self::InterfaceProvision | Self::InterfaceRequirement => { (from.is_performer() || from == Kind::Port) && to == Kind::Interface } Self::StateAction => from.is_stable_state() && to.is_work(), Self::Availability => { (from.is_work() || from.is_performer() || matches!( from, Kind::Capability | Kind::SystemCapability | Kind::Process | Kind::FunctionalChain )) && to.is_stable_state() } } } pub fn hint(self) -> &'static str { match self { Self::InterfaceItemAllocation => "Interface → allocated exchange item", Self::DataAssociation => "Record type → record type", Self::Allocation => "Performer → leaf activity/function", Self::PortAllocation => "Component port → allocated function port", Self::Interaction => "OA activity → OA activity", Self::CapabilityInvolvement => "Participant/activity/function → capability", Self::ChainInvolvement => "Process/chain → capability", Self::RoleRealization => "OA entity/actor → role", Self::RoleAssignment => "OA role → activity", Self::FunctionalExchange => "SA function → SA function", Self::BoundaryExchange => "Output port → input port", Self::CommunicationMean => "OA entity/actor → OA entity/actor", Self::MissionCapability => "Mission → capability in the same perspective", Self::CapabilityInclude => "Including capability → included capability", Self::CapabilityExtend => { "Extending capability → extended capability, under a condition" } Self::CapabilityGeneralization => "Specific capability → general capability", Self::MissionInvolvement => "Participant → mission in the same perspective", Self::SequenceLink => "Preceding → following contribution in the same process or chain", Self::ContextualLink => { "Contributions addressed in an explicit process or chain caller" } Self::StateTransition => "State → state in one machine", Self::ExchangeAllocation => { "Communication means / boundary exchange → operational interaction / functional exchange" } Self::InterfaceProvision => "Participant/port → provided interface", Self::InterfaceRequirement => "Participant/port → required interface", Self::StateAction => "State/mode → activity/function", Self::Availability => "Behavior/participant → state/mode", } } } impl fmt::Display for RelationKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Self::InterfaceItemAllocation => "Interface item allocation", Self::DataAssociation => "Data association", Self::Allocation => "Allocation", Self::PortAllocation => "Port allocation", Self::Interaction => "Interaction", Self::CapabilityInvolvement => "Capability involvement", Self::ChainInvolvement => "Chain involvement", Self::RoleRealization => "Role realization", Self::RoleAssignment => "Role assignment", Self::FunctionalExchange => "Functional exchange", Self::BoundaryExchange => "Boundary exchange", Self::CommunicationMean => "Communication means", Self::MissionCapability => "Mission capability", Self::CapabilityInclude => "Capability include", Self::CapabilityExtend => "Capability extend", Self::CapabilityGeneralization => "Capability generalization", Self::MissionInvolvement => "Mission involvement", Self::SequenceLink => "Sequence link", Self::ContextualLink => "Contextual link", Self::StateTransition => "State transition", Self::ExchangeAllocation => "Exchange allocation", Self::InterfaceProvision => "Interface provision", Self::InterfaceRequirement => "Interface requirement", Self::StateAction => "State action", Self::Availability => "Availability", }) } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Relation { pub id: Id, pub alias: String, pub name: String, pub description: String, pub body: RelationBody, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum RelationBody { InterfaceItemAllocation { source: Id, target: Id, order: u32, transmission: TransmissionProtocol, acquisition: AcquisitionProtocol, }, DataAssociation { source: Id, target: Id, source_role: String, target_role: String, source_minimum: u32, source_maximum: Option, target_minimum: u32, target_maximum: Option, source_navigable: bool, target_navigable: bool, }, Allocation { source: Id, target: Id, }, PortAllocation { source: Id, target: Id, }, Interaction { source: Id, target: Id, items: Vec, }, CapabilityInvolvement { source: Id, target: Id, }, ChainInvolvement { source: Id, target: Id, }, RoleRealization { source: Id, target: Id, }, RoleAssignment { source: Id, target: Id, }, FunctionalExchange { source: Id, target: Id, source_port: Id, target_port: Id, items: Vec, }, BoundaryExchange { source: Id, target: Id, oriented: bool, items: Vec, }, CommunicationMean { source: Id, target: Id, oriented: bool, items: Vec, }, MissionCapability { source: Id, target: Id, }, CapabilityInclude { source: Id, target: Id, }, CapabilityExtend { source: Id, target: Id, condition: String, }, CapabilityGeneralization { source: Id, target: Id, }, MissionInvolvement { source: Id, target: Id, }, SequenceLink { source: Id, target: Id, guard: String, }, ContextualLink { owner: Id, source: OccurrenceEndpoint, target: OccurrenceEndpoint, purpose: ContextualLinkPurpose, }, StateTransition { source: Id, target: Id, #[serde(default)] transition_kind: TransitionKind, #[serde(default)] triggers: Vec, #[serde(default)] effects: Vec, trigger: String, guard: String, effect: String, }, Availability { source: Id, target: Id, }, ExchangeAllocation { source: Id, target: Id, }, InterfaceProvision { source: Id, target: Id, }, InterfaceRequirement { source: Id, target: Id, }, StateAction { source: Id, target: Id, phase: StateActionKind, }, } impl Relation { /// Caller ownership cannot be inferred from a contextual link's leaf IDs. pub fn contextual_owner(&self) -> Option { match self.body { RelationBody::ContextualLink { owner, .. } => Some(owner), _ => None, } } pub fn kind(&self) -> RelationKind { self.body.kind() } pub fn source(&self) -> Id { self.body.endpoints().0 } pub fn target(&self) -> Id { self.body.endpoints().1 } } impl RelationBody { pub fn kind(&self) -> RelationKind { match self { Self::InterfaceItemAllocation { .. } => RelationKind::InterfaceItemAllocation, Self::DataAssociation { .. } => RelationKind::DataAssociation, Self::Allocation { .. } => RelationKind::Allocation, Self::PortAllocation { .. } => RelationKind::PortAllocation, Self::Interaction { .. } => RelationKind::Interaction, Self::CapabilityInvolvement { .. } => RelationKind::CapabilityInvolvement, Self::ChainInvolvement { .. } => RelationKind::ChainInvolvement, Self::RoleRealization { .. } => RelationKind::RoleRealization, Self::RoleAssignment { .. } => RelationKind::RoleAssignment, Self::FunctionalExchange { .. } => RelationKind::FunctionalExchange, Self::BoundaryExchange { .. } => RelationKind::BoundaryExchange, Self::CommunicationMean { .. } => RelationKind::CommunicationMean, Self::MissionCapability { .. } => RelationKind::MissionCapability, Self::CapabilityInclude { .. } => RelationKind::CapabilityInclude, Self::CapabilityExtend { .. } => RelationKind::CapabilityExtend, Self::CapabilityGeneralization { .. } => RelationKind::CapabilityGeneralization, Self::MissionInvolvement { .. } => RelationKind::MissionInvolvement, Self::SequenceLink { .. } => RelationKind::SequenceLink, Self::ContextualLink { .. } => RelationKind::ContextualLink, Self::StateTransition { .. } => RelationKind::StateTransition, Self::ExchangeAllocation { .. } => RelationKind::ExchangeAllocation, Self::InterfaceProvision { .. } => RelationKind::InterfaceProvision, Self::InterfaceRequirement { .. } => RelationKind::InterfaceRequirement, Self::StateAction { .. } => RelationKind::StateAction, Self::Availability { .. } => RelationKind::Availability, } } pub fn endpoints(&self) -> (Id, Id) { match self { Self::ContextualLink { source, target, .. } => (source.occurrence, target.occurrence), Self::InterfaceItemAllocation { source, target, .. } | Self::DataAssociation { source, target, .. } | Self::Allocation { source, target } | Self::PortAllocation { source, target } | Self::Interaction { source, target, .. } | Self::CapabilityInvolvement { source, target } | Self::ChainInvolvement { source, target } | Self::RoleRealization { source, target } | Self::RoleAssignment { source, target } | Self::FunctionalExchange { source, target, .. } | Self::BoundaryExchange { source, target, .. } | Self::CommunicationMean { source, target, .. } | Self::MissionCapability { source, target } | Self::CapabilityInclude { source, target } | Self::CapabilityExtend { source, target, .. } | Self::CapabilityGeneralization { source, target } | Self::MissionInvolvement { source, target } | Self::SequenceLink { source, target, .. } | Self::StateTransition { source, target, .. } | Self::ExchangeAllocation { source, target } | Self::InterfaceProvision { source, target } | Self::InterfaceRequirement { source, target } | Self::StateAction { source, target, .. } | Self::Availability { source, target } => (*source, *target), } } pub fn set_endpoints(&mut self, a: Id, b: Id) { match self { Self::ContextualLink { source, target, .. } => { source.occurrence = a; target.occurrence = b; } Self::InterfaceItemAllocation { source, target, .. } | Self::DataAssociation { source, target, .. } | Self::Allocation { source, target } | Self::PortAllocation { source, target } | Self::Interaction { source, target, .. } | Self::CapabilityInvolvement { source, target } | Self::ChainInvolvement { source, target } | Self::RoleRealization { source, target } | Self::RoleAssignment { source, target } | Self::FunctionalExchange { source, target, .. } | Self::BoundaryExchange { source, target, .. } | Self::CommunicationMean { source, target, .. } | Self::MissionCapability { source, target } | Self::CapabilityInclude { source, target } | Self::CapabilityExtend { source, target, .. } | Self::CapabilityGeneralization { source, target } | Self::MissionInvolvement { source, target } | Self::SequenceLink { source, target, .. } | Self::StateTransition { source, target, .. } | Self::ExchangeAllocation { source, target } | Self::InterfaceProvision { source, target } | Self::InterfaceRequirement { source, target } | Self::StateAction { source, target, .. } | Self::Availability { source, target } => { *source = a; *target = b; } } } pub fn items(&self) -> &[Id] { match self { Self::Interaction { items, .. } | Self::FunctionalExchange { items, .. } | Self::BoundaryExchange { items, .. } | Self::CommunicationMean { items, .. } => items, _ => &[], } } pub fn new(kind: RelationKind, source: Id, target: Id) -> Self { match kind { RelationKind::InterfaceItemAllocation => Self::InterfaceItemAllocation { source, target, order: 0, transmission: TransmissionProtocol::Unspecified, acquisition: AcquisitionProtocol::Unspecified, }, RelationKind::DataAssociation => Self::DataAssociation { source, target, source_role: String::new(), target_role: String::new(), source_minimum: 0, source_maximum: None, target_minimum: 0, target_maximum: None, source_navigable: false, target_navigable: false, }, RelationKind::Allocation => Self::Allocation { source, target }, RelationKind::PortAllocation => Self::PortAllocation { source, target }, RelationKind::Interaction => Self::Interaction { source, target, items: vec![], }, RelationKind::CapabilityInvolvement => Self::CapabilityInvolvement { source, target }, RelationKind::ChainInvolvement => Self::ChainInvolvement { source, target }, RelationKind::RoleRealization => Self::RoleRealization { source, target }, RelationKind::RoleAssignment => Self::RoleAssignment { source, target }, RelationKind::FunctionalExchange => Self::FunctionalExchange { source, target, // Unbound drafts must be completed before candidate admission. source_port: Id::nil(), target_port: Id::nil(), items: vec![], }, RelationKind::BoundaryExchange => Self::BoundaryExchange { source, target, oriented: true, items: vec![], }, RelationKind::CommunicationMean => Self::CommunicationMean { source, target, oriented: false, items: vec![], }, RelationKind::MissionCapability => Self::MissionCapability { source, target }, RelationKind::CapabilityInclude => Self::CapabilityInclude { source, target }, RelationKind::CapabilityExtend => Self::CapabilityExtend { source, target, condition: String::new(), }, RelationKind::CapabilityGeneralization => { Self::CapabilityGeneralization { source, target } } RelationKind::MissionInvolvement => Self::MissionInvolvement { source, target }, RelationKind::SequenceLink => Self::SequenceLink { source, target, guard: String::new(), }, RelationKind::ContextualLink => Self::ContextualLink { owner: Id::nil(), source: OccurrenceEndpoint { references: vec![], occurrence: source, }, target: OccurrenceEndpoint { references: vec![], occurrence: target, }, purpose: ContextualLinkPurpose::Precedence { guard: String::new(), }, }, RelationKind::StateTransition => Self::StateTransition { source, target, transition_kind: TransitionKind::External, triggers: vec![], effects: vec![], trigger: String::new(), guard: String::new(), effect: String::new(), }, RelationKind::ExchangeAllocation => Self::ExchangeAllocation { source, target }, RelationKind::InterfaceProvision => Self::InterfaceProvision { source, target }, RelationKind::InterfaceRequirement => Self::InterfaceRequirement { source, target }, RelationKind::StateAction => Self::StateAction { source, target, phase: StateActionKind::Do, }, RelationKind::Availability => Self::Availability { source, target }, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum ProjectionKind { Overview, Decomposition, Allocation, Traceability, FunctionalDataflow, Capabilities, StateMachine { machine: Id }, } impl ProjectionKind { pub fn state_machine(self) -> Option { match self { Self::StateMachine { machine } => Some(machine), _ => None, } } pub fn includes(self, kind: Kind) -> bool { match self { Self::StateMachine { .. } => { matches!(kind, Kind::StateMachine | Kind::Region) || kind.is_state() } Self::Allocation => kind.is_performer() || kind.is_work(), Self::FunctionalDataflow => kind.is_work(), Self::Capabilities => { kind.is_performer() || matches!( kind, Kind::Role | Kind::Capability | Kind::OperationalMission | Kind::Mission | Kind::SystemCapability ) } _ => true, } } } impl std::fmt::Display for ProjectionKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Self::StateMachine { .. } => "State machine", Self::Allocation => "Architecture · allocated work", Self::FunctionalDataflow => "Functional dataflow", Self::Capabilities => "Missions & capabilities", Self::Decomposition => "Containment", Self::Overview => "All relationships", Self::Traceability => "OA ↔ SA traceability", }) } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Diagram { pub id: Id, pub alias: String, pub name: String, pub description: String, pub layer: Layer, pub projection: ProjectionKind, #[serde(default)] pub view_options: crate::view_options::ViewOptions, #[serde(deserialize_with = "unique_id_map")] pub nodes: BTreeMap, #[serde(default)] pub routes: Vec, } /// Authored presentation for one existing relation or trace in one diagram. /// Computed endpoints and automatic routing elbows are never persisted here. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct EdgeRoute { pub edge: Id, #[serde(default, skip_serializing_if = "Option::is_none")] pub source_attachment: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub target_attachment: Option, #[serde(default)] pub waypoints: Vec, #[serde(default)] pub label: Option, } /// Presentation preference on one end of a connection. Real ports take precedence. #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct EdgeAttachment { pub side: PortAnchorSide, pub offset: f32, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EdgeEnd { Source, Target, } impl EdgeRoute { pub fn attachment(&self, end: EdgeEnd) -> Option { match end { EdgeEnd::Source => self.source_attachment, EdgeEnd::Target => self.target_attachment, } } pub fn set_attachment(&mut self, end: EdgeEnd, attachment: Option) { match end { EdgeEnd::Source => self.source_attachment = attachment, EdgeEnd::Target => self.target_attachment = attachment, } } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct EdgeLabel { /// Fraction of the computed polyline's total arc length, from source to target. pub fraction: f32, /// Diagram-space displacement relative to the point at `fraction`. pub offset: Position, } pub const MAX_ROUTE_POINTS: usize = 128; /// Presentation side; independent of the semantic port direction. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub enum PortAnchorSide { Left, Right, Top, Bottom, } impl PortAnchorSide { pub fn normal(self) -> Position { match self { Self::Left => Position { x: -1., y: 0. }, Self::Right => Position { x: 1., y: 0. }, Self::Top => Position { x: 0., y: -1. }, Self::Bottom => Position { x: 0., y: 1. }, } } } /// Manual position of a semantic child port on one owner placement. /// Absence from the placement's list selects automatic positioning. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct PortAnchor { pub port: Id, pub side: PortAnchorSide, /// Fraction of the complete edge, from top (vertical) or left (horizontal). pub offset: f32, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct NodePlacement { pub id: Id, pub alias: String, pub element: Id, pub position: Position, pub size: Size, pub collapsed: bool, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub port_anchors: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ArtifactRelation { References, Satisfies, Verifies, Implements, DerivedFrom, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ArtifactLink { pub id: Id, pub alias: String, pub name: String, pub description: String, pub owner: Id, pub relation: ArtifactRelation, pub uri: String, pub revision: Option, pub fragment: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum TraceKind { Refinement, Participant, Capability, Mission, Exchange, Sequence, Item, Scenario, State, Manual, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct TracedProperties { pub name: String, pub description: String, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct TraceBaseline { pub oa: TracedProperties, pub sa: TracedProperties, #[serde(default, skip_serializing_if = "Option::is_none")] pub semantic: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Trace { pub id: Id, pub alias: String, pub name: String, pub description: String, pub oa: Id, pub sa: Id, pub kind: TraceKind, pub baseline: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Project { pub schema_version: u32, pub id: Id, pub alias: String, pub name: String, pub description: String, #[serde(deserialize_with = "unique_id_map")] pub elements: BTreeMap, #[serde(deserialize_with = "unique_id_map")] pub relations: BTreeMap, #[serde(deserialize_with = "unique_id_map")] pub diagrams: BTreeMap, #[serde(deserialize_with = "unique_id_map")] pub artifact_links: BTreeMap, #[serde(deserialize_with = "unique_id_map")] pub traces: BTreeMap, } pub fn unique_id_map<'de, D, T>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, T: Deserialize<'de>, { struct Unique(std::marker::PhantomData); impl<'de, T: Deserialize<'de>> serde::de::Visitor<'de> for Unique { type Value = BTreeMap; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("unique UUID keys") } fn visit_map>( self, mut access: A, ) -> Result { let mut map = BTreeMap::new(); while let Some((id, value)) = access.next_entry()? { if map.insert(id, value).is_some() { return Err(serde::de::Error::custom(format!( "Duplicate object ID {id}" ))); } } Ok(map) } } deserializer.deserialize_map(Unique(std::marker::PhantomData)) } pub fn reserved_alias(value: &str) -> bool { matches!( value, "arcadia" | "project" | "id" | "oa" | "sa" | "element" | "relation" | "diagram" | "node" | "artifact" | "trace" | "body" | "alias" | "name" | "description" | "parent" | "from" | "to" | "entity" | "actor" | "activity" | "capability" | "process" | "allocation" | "interaction" | "contribution" | "realization" | "position" | "steps" | "true" | "false" | "null" ) } pub fn valid_alias(value: &str) -> bool { let mut chars = value.chars(); chars .next() .is_some_and(|c| c.is_ascii_alphabetic() || c == '_') && chars.all(|c| c.is_ascii_alphanumeric() || c == '_') && !reserved_alias(value) } impl Project { pub fn empty() -> Self { Self { schema_version: SCHEMA_VERSION, id: Id::new_v4(), alias: "mission".into(), name: "Untitled mission".into(), description: String::new(), elements: BTreeMap::new(), relations: BTreeMap::new(), diagrams: BTreeMap::new(), artifact_links: BTreeMap::new(), traces: BTreeMap::new(), } } pub fn identities(&self) -> Vec<(Id, &str)> { let mut out = vec![(self.id, self.alias.as_str())]; out.extend(self.elements.values().map(|v| (v.id, v.alias.as_str()))); out.extend(self.relations.values().map(|v| (v.id, v.alias.as_str()))); out.extend(self.diagrams.values().map(|v| (v.id, v.alias.as_str()))); out.extend( self.diagrams .values() .flat_map(|v| v.nodes.values()) .map(|v| (v.id, v.alias.as_str())), ); out.extend( self.artifact_links .values() .map(|v| (v.id, v.alias.as_str())), ); out.extend(self.traces.values().map(|v| (v.id, v.alias.as_str()))); out } pub fn contains(&self, id: Id) -> bool { self.identities().iter().any(|(i, _)| *i == id) } pub fn fresh_alias(&self, name: &str, fallback: &str, id: Id) -> String { let used: BTreeSet<_> = self.identities().into_iter().map(|(_, a)| a).collect(); let mut stem = String::new(); for c in name.chars() { if c.is_ascii_alphanumeric() { stem.push(c.to_ascii_lowercase()) } else if !stem.is_empty() && !stem.ends_with('_') { stem.push('_') } } let mut stem = stem.trim_end_matches('_').to_owned(); if !valid_alias(&stem) { stem = format!("{fallback}_{}", stem.trim_matches('_')); } if !valid_alias(&stem) { stem = "item".into(); } if !used.contains(stem.as_str()) { return stem; } let candidate = format!("{stem}_{}", &id.simple().to_string()[..8]); if !used.contains(candidate.as_str()) { return candidate; } let mut n = 2; loop { let value = format!("{candidate}_{n}"); if !used.contains(value.as_str()) { return value; } n += 1; } } pub fn named(&self, id: Id) -> &str { if id == self.id { return &self.name; } if let Some(v) = self.elements.get(&id) { return &v.name; } if let Some(v) = self.relations.get(&id) { return &v.name; } if let Some(v) = self.traces.get(&id) { return &v.name; } if let Some(v) = self.artifact_links.get(&id) { return &v.name; } if let Some(v) = self.diagrams.get(&id) { return &v.name; } "Missing object" } pub fn layer_of(&self, id: Id) -> Option { let mut current = id; let mut seen = BTreeSet::new(); loop { if !seen.insert(current) { return None; } if let Some(e) = self.elements.get(¤t) { return Some(e.layer()); } current = self.relations.get(¤t)?.source(); } } pub fn properties(&self, id: Id) -> Option { if let Some(e) = self.elements.get(&id) { return Some(TracedProperties { name: e.name.clone(), description: e.description.clone(), }); } self.relations.get(&id).map(|r| TracedProperties { name: r.name.clone(), description: r.description.clone(), }) } pub fn children(&self, parent: Id) -> impl Iterator { self.elements .values() .filter(move |e| e.parent == Some(parent)) } pub fn make_element( &self, kind: Kind, layer: Layer, name: &str, parent: Option, ) -> Option { let id = Id::new_v4(); let mut body = ElementBody::new(kind, layer)?; if kind == Kind::Occurrence && parent .and_then(|id| self.elements.get(&id)) .is_some_and(|owner| { matches!( owner.shared(), Some(SharedBody::Occurrence { body: OccurrenceBody::Fragment { .. }, .. }) ) }) { body = ElementBody::shared_in( layer, SharedBody::Occurrence { body: OccurrenceBody::Branch { guard: String::new(), occurrences: vec![], }, precondition: String::new(), postcondition: String::new(), }, ); } Some(Element { id, alias: self.fresh_alias(name, "element", id), name: name.into(), description: String::new(), parent, body, }) } pub fn add_element( &mut self, kind: Kind, layer: Layer, name: &str, parent: Option, ) -> Option { let e = self.make_element(kind, layer, name, parent)?; let id = e.id; self.elements.insert(id, e); Some(id) } pub fn add(&mut self, kind: Kind, name: &str, x: f32, y: f32) -> Id { let layer = if kind.supports(Layer::Oa) { Layer::Oa } else { Layer::Sa }; let id = self .add_element(kind, layer, name, None) .expect("kind belongs to a layer"); let diagram = self.ensure_diagram(layer); self.place(diagram, id, Position { x, y }); id } pub fn ensure_diagram(&mut self, layer: Layer) -> Id { if let Some(d) = self .diagrams .values() .find(|d| d.layer == layer && d.projection == ProjectionKind::Overview) { return d.id; } let id = Id::new_v4(); let name = format!("{layer} overview"); let alias = self.fresh_alias(&name, "diagram", id); self.diagrams.insert( id, Diagram { id, alias, name, description: String::new(), layer, projection: ProjectionKind::Overview, view_options: crate::view_options::ViewOptions::default(), nodes: BTreeMap::new(), routes: vec![], }, ); id } pub fn place(&mut self, diagram: Id, element: Id, position: Position) -> Id { if let Some(node) = self .diagrams .get(&diagram) .and_then(|d| d.nodes.values().find(|n| n.element == element)) { return node.id; } let id = Id::new_v4(); let alias = self.fresh_alias(&format!("view_{}", self.named(element)), "node", id); let node = NodePlacement { id, alias, element, position, size: Size { width: 224., height: 94., }, collapsed: false, port_anchors: Vec::new(), }; if let Some(d) = self.diagrams.get_mut(&diagram) { d.nodes.insert(id, node); } id } pub fn relate(&mut self, kind: RelationKind, name: &str, source: Id, target: Id) -> Id { let id = Id::new_v4(); let alias = self.fresh_alias(name, "relation", id); if kind == RelationKind::FunctionalExchange && self.elements.contains_key(&source) && self.elements.contains_key(&target) { let request = crate::ports::FunctionalExchangeRequest { id, alias, name: name.into(), description: String::new(), source, target, source_port: crate::ports::PortChoice::Auto, target_port: crate::ports::PortChoice::Auto, items: vec![], }; // Project is the unchecked builder; Session validates the complete batch. for command in crate::ports::functional_exchange_commands(self, &request) .expect("fresh exchange identities") { match command { crate::Command::CreateElement(e) => { self.elements.insert(e.id, e); } crate::Command::CreateRelation(r) => { self.relations.insert(r.id, r); } _ => unreachable!("functional creation only creates ports and an exchange"), } } return id; } let mut body = RelationBody::new(kind, source, target); if self .elements .get(&source) .is_some_and(|e| e.kind() == Kind::EntryPoint) && let RelationBody::StateTransition { transition_kind, .. } = &mut body { *transition_kind = TransitionKind::Local; } self.relations.insert( id, Relation { id, alias, name: name.into(), description: String::new(), body, }, ); id } pub fn demo() -> Self { crate::demo::coastal_rescue() } }