Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
2016 lines
65 KiB
Rust
2016 lines
65 KiB
Rust
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<StateKind> {
|
|
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<Id>,
|
|
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<Id> },
|
|
Shared(SharedBody),
|
|
}
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub enum SaBody {
|
|
System,
|
|
Actor,
|
|
Function,
|
|
Capability,
|
|
Mission,
|
|
FunctionalChain { steps: Vec<Id> },
|
|
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<i64>,
|
|
maximum: Option<i64>,
|
|
},
|
|
Real {
|
|
minimum: Option<f64>,
|
|
maximum: Option<f64>,
|
|
},
|
|
String {
|
|
minimum_length: u32,
|
|
maximum_length: Option<u32>,
|
|
},
|
|
Enumeration {
|
|
literals: Vec<String>,
|
|
},
|
|
Record {
|
|
supertype: Option<Id>,
|
|
},
|
|
}
|
|
#[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<Id>,
|
|
}
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub enum PortContract {
|
|
FunctionInput {
|
|
incoming_items: Vec<Id>,
|
|
},
|
|
FunctionOutput {
|
|
outgoing_items: Vec<Id>,
|
|
},
|
|
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<Id>,
|
|
},
|
|
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<Id>,
|
|
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<Id>,
|
|
}
|
|
/// 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<Id>,
|
|
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<Id>,
|
|
sender: Option<Id>,
|
|
receiver: Option<Id>,
|
|
#[serde(default)]
|
|
message_kind: MessageKind,
|
|
#[serde(default)]
|
|
reply_to: Option<Id>,
|
|
#[serde(default)]
|
|
values: Vec<MessageValue>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
operation: Option<OperationUse>,
|
|
},
|
|
Action {
|
|
action: Option<Id>,
|
|
lifeline: Option<Id>,
|
|
},
|
|
Control {
|
|
kind: ControlKind,
|
|
},
|
|
SequenceReference {
|
|
sequence: Option<Id>,
|
|
},
|
|
ScenarioUse {
|
|
scenario: Option<Id>,
|
|
bindings: Vec<LifelineBinding>,
|
|
},
|
|
State {
|
|
state: Option<Id>,
|
|
lifeline: Option<Id>,
|
|
},
|
|
Fragment {
|
|
operator: FragmentOperator,
|
|
branches: Vec<Id>,
|
|
},
|
|
Branch {
|
|
guard: String,
|
|
occurrences: Vec<Id>,
|
|
},
|
|
}
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields)]
|
|
pub enum SharedBody {
|
|
ExchangeCategory {
|
|
family: crate::view_options::ExchangeFamily,
|
|
exchanges: Vec<Id>,
|
|
},
|
|
ExchangeItem {
|
|
kind: ExchangeItemKind,
|
|
mechanism: ExchangeMechanism,
|
|
supertype: Option<Id>,
|
|
},
|
|
DataType {
|
|
definition: DataTypeDefinition,
|
|
default: Option<DataValue>,
|
|
unit: String,
|
|
},
|
|
DataField {
|
|
type_ref: TypeRef,
|
|
minimum: u32,
|
|
maximum: Option<u32>,
|
|
unit: String,
|
|
direction: ParameterDirection,
|
|
storage: FieldStorage,
|
|
default: Option<Vec<DataValue>>,
|
|
},
|
|
Interface {
|
|
supertype: Option<Id>,
|
|
},
|
|
Port {
|
|
contract: PortContract,
|
|
legacy: Option<LegacyPortContract>,
|
|
},
|
|
Scenario {
|
|
#[serde(default)]
|
|
viewpoint: ScenarioViewpoint,
|
|
context: Option<Id>,
|
|
precondition: String,
|
|
postcondition: String,
|
|
steps: Vec<Id>,
|
|
},
|
|
Lifeline {
|
|
represented: Option<Id>,
|
|
},
|
|
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<Id>,
|
|
},
|
|
}
|
|
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<Id>> {
|
|
self.body.steps()
|
|
}
|
|
pub fn steps_mut(&mut self) -> Option<&mut Vec<Id>> {
|
|
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<Id>> {
|
|
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<Id>> {
|
|
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<Self> {
|
|
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<u32>,
|
|
target_minimum: u32,
|
|
target_maximum: Option<u32>,
|
|
source_navigable: bool,
|
|
target_navigable: bool,
|
|
},
|
|
Allocation {
|
|
source: Id,
|
|
target: Id,
|
|
},
|
|
PortAllocation {
|
|
source: Id,
|
|
target: Id,
|
|
},
|
|
Interaction {
|
|
source: Id,
|
|
target: Id,
|
|
items: Vec<Id>,
|
|
},
|
|
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<Id>,
|
|
},
|
|
BoundaryExchange {
|
|
source: Id,
|
|
target: Id,
|
|
oriented: bool,
|
|
items: Vec<Id>,
|
|
},
|
|
CommunicationMean {
|
|
source: Id,
|
|
target: Id,
|
|
oriented: bool,
|
|
items: Vec<Id>,
|
|
},
|
|
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<Id>,
|
|
#[serde(default)]
|
|
effects: Vec<Id>,
|
|
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<Id> {
|
|
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<Id> {
|
|
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<Id, NodePlacement>,
|
|
#[serde(default)]
|
|
pub routes: Vec<EdgeRoute>,
|
|
}
|
|
/// 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)]
|
|
pub waypoints: Vec<Position>,
|
|
#[serde(default)]
|
|
pub label: Option<EdgeLabel>,
|
|
}
|
|
#[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<PortAnchor>,
|
|
}
|
|
#[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<String>,
|
|
pub fragment: Option<String>,
|
|
}
|
|
#[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<crate::semantic::SemanticBaseline>,
|
|
}
|
|
#[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<TraceBaseline>,
|
|
}
|
|
#[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<Id, Element>,
|
|
#[serde(deserialize_with = "unique_id_map")]
|
|
pub relations: BTreeMap<Id, Relation>,
|
|
#[serde(deserialize_with = "unique_id_map")]
|
|
pub diagrams: BTreeMap<Id, Diagram>,
|
|
#[serde(deserialize_with = "unique_id_map")]
|
|
pub artifact_links: BTreeMap<Id, ArtifactLink>,
|
|
#[serde(deserialize_with = "unique_id_map")]
|
|
pub traces: BTreeMap<Id, Trace>,
|
|
}
|
|
|
|
pub fn unique_id_map<'de, D, T>(deserializer: D) -> Result<BTreeMap<Id, T>, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
T: Deserialize<'de>,
|
|
{
|
|
struct Unique<T>(std::marker::PhantomData<T>);
|
|
impl<'de, T: Deserialize<'de>> serde::de::Visitor<'de> for Unique<T> {
|
|
type Value = BTreeMap<Id, T>;
|
|
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
f.write_str("unique UUID keys")
|
|
}
|
|
fn visit_map<A: serde::de::MapAccess<'de>>(
|
|
self,
|
|
mut access: A,
|
|
) -> Result<Self::Value, A::Error> {
|
|
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<Layer> {
|
|
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<TracedProperties> {
|
|
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<Item = &Element> {
|
|
self.elements
|
|
.values()
|
|
.filter(move |e| e.parent == Some(parent))
|
|
}
|
|
pub fn make_element(
|
|
&self,
|
|
kind: Kind,
|
|
layer: Layer,
|
|
name: &str,
|
|
parent: Option<Id>,
|
|
) -> Option<Element> {
|
|
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<Id>,
|
|
) -> Option<Id> {
|
|
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()
|
|
}
|
|
}
|