1extern crate alloc;
4use alloc::string::String;
5use core::fmt;
6use core::str::FromStr;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
15#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
17pub enum EdgeCategory {
18 Structure,
21 Derivation,
23 Provenance,
25 Temporal,
27 Dependency,
29 Implementation,
31 Lateral,
33 Annotation,
35 Epistemic,
37 Ownership,
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
48pub enum EdgeRelation {
49 Contains,
51 PartOf,
52 InstanceOf,
53 LinksTo,
54 LocatedIn,
55 Extends,
57 VariantOf,
58 IntroducedBy,
59 Supersedes,
60 DerivedFrom,
62 Precedes,
64 DependsOn,
66 Enables,
67 Implements,
69 CompetesWith,
71 ComposedWith,
72 Annotates,
74 Supports,
76 Refutes,
77 Owns,
79}
80
81impl EdgeRelation {
82 pub const ALL: [Self; 20] = [
84 Self::Contains,
85 Self::PartOf,
86 Self::InstanceOf,
87 Self::LinksTo,
88 Self::LocatedIn,
89 Self::Extends,
90 Self::VariantOf,
91 Self::IntroducedBy,
92 Self::Supersedes,
93 Self::DerivedFrom,
94 Self::Precedes,
95 Self::DependsOn,
96 Self::Enables,
97 Self::Implements,
98 Self::CompetesWith,
99 Self::ComposedWith,
100 Self::Annotates,
101 Self::Supports,
102 Self::Refutes,
103 Self::Owns,
104 ];
105
106 pub const VALID_NAMES: &'static [&'static str] = &[
108 "contains",
109 "part_of",
110 "instance_of",
111 "links_to",
112 "located_in",
113 "extends",
114 "variant_of",
115 "introduced_by",
116 "supersedes",
117 "derived_from",
118 "precedes",
119 "depends_on",
120 "enables",
121 "implements",
122 "competes_with",
123 "composed_with",
124 "annotates",
125 "supports",
126 "refutes",
127 "owns",
128 ];
129
130 pub const fn is_symmetric(&self) -> bool {
132 matches!(self, Self::CompetesWith | Self::ComposedWith)
133 }
134
135 pub fn canonical_endpoints<T: Ord>(&self, source: T, target: T) -> (T, T) {
145 if self.is_symmetric() && target < source {
146 (target, source)
147 } else {
148 (source, target)
149 }
150 }
151
152 pub const fn category(&self) -> EdgeCategory {
154 match self {
155 Self::Contains | Self::PartOf | Self::InstanceOf | Self::LinksTo | Self::LocatedIn => {
156 EdgeCategory::Structure
157 }
158 Self::Extends | Self::VariantOf | Self::IntroducedBy | Self::Supersedes => {
159 EdgeCategory::Derivation
160 }
161 Self::DerivedFrom => EdgeCategory::Provenance,
162 Self::Precedes => EdgeCategory::Temporal,
163 Self::DependsOn | Self::Enables => EdgeCategory::Dependency,
164 Self::Implements => EdgeCategory::Implementation,
165 Self::CompetesWith | Self::ComposedWith => EdgeCategory::Lateral,
166 Self::Annotates => EdgeCategory::Annotation,
167 Self::Supports | Self::Refutes => EdgeCategory::Epistemic,
168 Self::Owns => EdgeCategory::Ownership,
169 }
170 }
171
172 pub const fn as_str(&self) -> &'static str {
174 match self {
175 Self::Contains => "contains",
176 Self::PartOf => "part_of",
177 Self::InstanceOf => "instance_of",
178 Self::LinksTo => "links_to",
179 Self::LocatedIn => "located_in",
180 Self::Extends => "extends",
181 Self::VariantOf => "variant_of",
182 Self::IntroducedBy => "introduced_by",
183 Self::Supersedes => "supersedes",
184 Self::DerivedFrom => "derived_from",
185 Self::Precedes => "precedes",
186 Self::DependsOn => "depends_on",
187 Self::Enables => "enables",
188 Self::Implements => "implements",
189 Self::CompetesWith => "competes_with",
190 Self::ComposedWith => "composed_with",
191 Self::Annotates => "annotates",
192 Self::Supports => "supports",
193 Self::Refutes => "refutes",
194 Self::Owns => "owns",
195 }
196 }
197}
198
199impl fmt::Display for EdgeRelation {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.write_str(self.as_str())
202 }
203}
204
205impl FromStr for EdgeRelation {
206 type Err = crate::error::UnknownVariant;
207
208 fn from_str(s: &str) -> Result<Self, Self::Err> {
216 let mut normalised = String::with_capacity(s.len());
217 for c in s.chars() {
218 match c {
219 '-' | '_' => normalised.push('_'),
220 c if c.is_ascii_alphanumeric() => normalised.push(c.to_ascii_lowercase()),
221 _ => {
222 return Err(crate::error::UnknownVariant::new(
223 "edge_relation",
224 s,
225 Self::VALID_NAMES,
226 ));
227 }
228 }
229 }
230
231 match normalised.as_str() {
232 "contains" => Ok(Self::Contains),
233 "part_of" | "partof" => Ok(Self::PartOf),
234 "instance_of" | "instanceof" => Ok(Self::InstanceOf),
235 "links_to" | "linksto" => Ok(Self::LinksTo),
236 "located_in" | "locatedin" => Ok(Self::LocatedIn),
237 "extends" => Ok(Self::Extends),
238 "variant_of" | "variantof" => Ok(Self::VariantOf),
239 "introduced_by" | "introducedby" => Ok(Self::IntroducedBy),
240 "supersedes" => Ok(Self::Supersedes),
241 "derived_from" | "derivedfrom" => Ok(Self::DerivedFrom),
242 "precedes" => Ok(Self::Precedes),
243 "depends_on" | "dependson" => Ok(Self::DependsOn),
244 "enables" => Ok(Self::Enables),
245 "implements" => Ok(Self::Implements),
246 "competes_with" | "competeswith" => Ok(Self::CompetesWith),
247 "composed_with" | "composedwith" => Ok(Self::ComposedWith),
248 "annotates" => Ok(Self::Annotates),
249 "supports" => Ok(Self::Supports),
250 "refutes" => Ok(Self::Refutes),
251 "owns" => Ok(Self::Owns),
252 _ => Err(crate::error::UnknownVariant::new(
253 "edge_relation",
254 s,
255 Self::VALID_NAMES,
256 )),
257 }
258 }
259}
260
261#[cfg(test)]
262#[path = "edge_tests.rs"]
263mod tests;