phoxal_model/component/
mod.rs1pub mod capability;
6
7use std::collections::BTreeMap;
8use std::fmt;
9
10use crate::structure::Structure;
11use capability::Capability;
12
13#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
14#[serde(deny_unknown_fields)]
15pub struct Component {
16 capabilities: BTreeMap<String, Capability>,
17 structure: Structure,
18}
19
20impl Component {
21 #[doc(hidden)]
22 pub fn __new(capabilities: BTreeMap<String, Capability>, structure: Structure) -> Self {
23 Self {
24 capabilities,
25 structure,
26 }
27 }
28
29 #[must_use]
30 pub fn capability(&self, capability_id: &str) -> Option<&Capability> {
31 self.capabilities.get(capability_id)
32 }
33
34 pub fn capabilities(&self) -> impl ExactSizeIterator<Item = (&str, &Capability)> {
35 self.capabilities
36 .iter()
37 .map(|(id, capability)| (id.as_str(), capability))
38 }
39
40 #[must_use]
41 pub fn structure(&self) -> &Structure {
42 &self.structure
43 }
44}
45
46#[derive(
47 serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
48)]
49#[serde(try_from = "String", into = "String")]
50pub struct CapabilityRef {
51 pub component_id: String,
52 pub capability_id: String,
53}
54
55impl CapabilityRef {
56 #[must_use]
57 pub fn new(component_id: impl Into<String>, capability_id: impl Into<String>) -> Self {
58 Self {
59 component_id: component_id.into(),
60 capability_id: capability_id.into(),
61 }
62 }
63}
64
65impl fmt::Display for CapabilityRef {
66 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
67 write!(formatter, "{}.{}", self.component_id, self.capability_id)
68 }
69}
70
71impl std::str::FromStr for CapabilityRef {
72 type Err = crate::ModelError;
73
74 fn from_str(value: &str) -> Result<Self, Self::Err> {
75 let (component_id, capability_id) = value.split_once('.').ok_or_else(|| {
76 crate::ModelError::Invalid(format!(
77 "capability reference '{value}' must use component.capability"
78 ))
79 })?;
80 if !is_valid_token(component_id) || !is_valid_token(capability_id) {
81 return Err(crate::ModelError::Invalid(format!(
82 "capability reference '{value}' is not normalized"
83 )));
84 }
85 Ok(Self::new(component_id, capability_id))
86 }
87}
88
89impl TryFrom<String> for CapabilityRef {
90 type Error = crate::ModelError;
91
92 fn try_from(value: String) -> Result<Self, Self::Error> {
93 value.parse()
94 }
95}
96
97impl From<CapabilityRef> for String {
98 fn from(value: CapabilityRef) -> Self {
99 value.to_string()
100 }
101}
102
103#[must_use]
104pub fn is_valid_token(value: &str) -> bool {
105 !value.is_empty()
106 && value.chars().all(|character| {
107 character.is_ascii_lowercase()
108 || character.is_ascii_digit()
109 || character == '_'
110 || character == '-'
111 })
112}