ciboulette/body/
resource_type.rs

1use super::*;
2
3/// ## Describe a `json:api` type attribute schema and list its relationships
4#[allow(clippy::derive_hash_xor_eq)]
5#[derive(Clone, Debug, Getters, MutGetters, Hash)]
6#[getset(get = "pub", get_mut = "pub")]
7pub struct CibouletteResourceType {
8    relationships: BTreeMap<ArcStr, petgraph::graph::EdgeIndex<u16>>,
9    relationships_type_to_alias: BTreeMap<ArcStr, ArcStr>,
10    schema: MessyJsonObject,
11    ids: CibouletteIdTypeSelector,
12    name: ArcStr,
13}
14
15/// ## Relationships metadata for [CibouletteResourceType](CibouletteResourceType)
16#[derive(Clone, Debug, Getters, MutGetters, Ord, PartialEq, PartialOrd, Eq, Hash)]
17#[getset(get = "pub", get_mut = "pub")]
18pub struct CibouletteResourceRelationshipDetails {
19    relation_alias: ArcStr,
20    related_type: Arc<CibouletteResourceType>,
21    relation_option: CibouletteRelationshipOption,
22}
23
24impl CibouletteResourceType {
25    /// Create a new type from a schema and a list of relationships
26    pub(crate) fn new(
27        name: ArcStr,
28        ids: CibouletteIdTypeSelector,
29        schema: MessyJsonObject,
30    ) -> Self {
31        CibouletteResourceType {
32            relationships: BTreeMap::new(),
33            relationships_type_to_alias: BTreeMap::new(),
34            schema,
35            ids,
36            name,
37        }
38    }
39
40    /// Get a the alias of a type related to this type
41    pub fn get_alias(&self, name: &str) -> Result<&ArcStr, CibouletteError> {
42        self.relationships_type_to_alias().get(name).ok_or_else(|| {
43            CibouletteError::MissingAliasTranslation(self.name().to_string(), name.to_string())
44        })
45    }
46
47    /// Fetch a relationships alongside its alias
48    pub fn get_relationship_with_alias(
49        &self,
50        store: &CibouletteStore,
51        alias: &str,
52    ) -> Result<(ArcStr, Arc<CibouletteResourceType>), CibouletteError> {
53        let (alias, edge_index) = self.relationships().get_key_value(alias).ok_or_else(|| {
54            CibouletteError::UnknownRelationship(self.name().to_string(), alias.to_string())
55        })?;
56        let self_index = *store
57            .map()
58            .get(self.name().as_str())
59            .ok_or_else(|| CibouletteError::UnknownType(self.name().to_string()))?;
60        let (t1, t2) = store.graph().edge_endpoints(*edge_index).ok_or_else(|| {
61            CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
62        })?;
63        Ok((
64            alias.clone(),
65            match t1 == self_index {
66                true => store.graph().node_weight(t2).cloned().ok_or_else(|| {
67                    CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
68                })?,
69                false => store.graph().node_weight(t1).cloned().ok_or_else(|| {
70                    CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
71                })?,
72            },
73        ))
74    }
75
76    /// Fetch a relationships related type
77    pub fn get_relationship(
78        &self,
79        store: &CibouletteStore,
80        alias: &str,
81    ) -> Result<Arc<CibouletteResourceType>, CibouletteError> {
82        let edge_index = self.relationships().get(alias).ok_or_else(|| {
83            CibouletteError::UnknownRelationship(self.name().to_string(), alias.to_string())
84        })?;
85        let self_index = *store
86            .map()
87            .get(self.name().as_str())
88            .ok_or_else(|| CibouletteError::UnknownType(self.name().to_string()))?;
89        let (t1, t2) = store.graph().edge_endpoints(*edge_index).ok_or_else(|| {
90            CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
91        })?;
92        Ok(match t1 == self_index {
93            true => store.graph().node_weight(t2).cloned().ok_or_else(|| {
94                CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
95            })?,
96            false => store.graph().node_weight(t1).cloned().ok_or_else(|| {
97                CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
98            })?,
99        })
100    }
101
102    /// Get a relationships metadata
103    pub fn get_relationship_details(
104        &self,
105        store: &CibouletteStore,
106        alias: &str,
107    ) -> Result<CibouletteResourceRelationshipDetails, CibouletteError> {
108        let (edge_alias, edge_index) =
109            self.relationships().get_key_value(alias).ok_or_else(|| {
110                CibouletteError::UnknownRelationship(self.name().to_string(), alias.to_string())
111            })?;
112        let self_index = *store
113            .map()
114            .get(self.name().as_str())
115            .ok_or_else(|| CibouletteError::UnknownType(self.name().to_string()))?;
116        let rel_weight = store
117            .graph()
118            .edge_weight(*edge_index)
119            .ok_or_else(|| {
120                CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
121            })?
122            .clone();
123        let (t1, t2) = store.graph().edge_endpoints(*edge_index).ok_or_else(|| {
124            CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
125        })?;
126        let related_type = match t1 == self_index {
127            true => store.graph().node_weight(t2).cloned().ok_or_else(|| {
128                CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
129            })?,
130            false => store.graph().node_weight(t1).cloned().ok_or_else(|| {
131                CibouletteError::RelNotInGraph(self.name().to_string(), alias.to_string())
132            })?,
133        };
134        Ok(CibouletteResourceRelationshipDetails {
135            relation_alias: edge_alias.clone(),
136            related_type,
137            relation_option: rel_weight,
138        })
139    }
140
141    /// Check if a resource type has every the top level fields in the iterator
142    pub fn has_fields<'store, I>(&self, fields: I) -> Result<Option<String>, CibouletteError>
143    where
144        I: Iterator<Item = &'store str>,
145    {
146        Ok(fields
147            .into_iter()
148            .find_map(|k| match self.schema.has_field(k) {
149                true => None,
150                false => Some(k.to_string()),
151            }))
152    }
153}
154
155impl Ord for CibouletteResourceType {
156    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
157        self.name.cmp(&other.name)
158    }
159}
160
161impl PartialOrd for CibouletteResourceType {
162    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
163        Some(self.name.cmp(&other.name))
164    }
165}
166
167impl PartialEq for CibouletteResourceType {
168    fn eq(&self, other: &Self) -> bool {
169        self.name == other.name
170    }
171}
172
173impl Eq for CibouletteResourceType {}