ankurah_core/property/value/
entity_ref.rs1use crate::model::View;
19use ankurah_proto::EntityId;
20use serde::{Deserialize, Serialize};
21use std::borrow::Borrow;
22use std::fmt;
23use std::marker::PhantomData;
24use std::ops::Deref;
25
26use crate::context::Context;
27use crate::error::RetrievalError;
28use crate::model::Model;
29use crate::property::{Property, PropertyError};
30use crate::value::Value;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37#[serde(transparent)]
38pub struct Ref<T> {
39 id: EntityId,
40 #[serde(skip)]
41 _phantom: PhantomData<T>,
42}
43
44impl<T> Deref for Ref<T> {
45 type Target = EntityId;
46 fn deref(&self) -> &EntityId { &self.id }
47}
48
49impl<T> AsRef<EntityId> for Ref<T> {
50 fn as_ref(&self) -> &EntityId { &self.id }
51}
52
53impl<T> Borrow<EntityId> for Ref<T> {
54 fn borrow(&self) -> &EntityId { &self.id }
55}
56
57impl<T> Ref<T> {
58 pub fn new(id: EntityId) -> Self { Ref { id, _phantom: PhantomData } }
60
61 pub fn from_base64(s: &str) -> Result<Self, ankurah_proto::DecodeError> { Ok(Ref::new(EntityId::from_base64(s)?)) }
63
64 pub fn id(&self) -> EntityId { self.id.clone() }
66
67 pub fn id_ref(&self) -> &EntityId { &self.id }
69}
70
71impl<T: Model> Ref<T> {
72 pub async fn get(&self, ctx: &Context) -> Result<T::View, RetrievalError> { ctx.get::<T::View>(self.id.clone()).await }
80}
81
82impl<T> From<EntityId> for Ref<T> {
83 fn from(id: EntityId) -> Self { Ref::new(id) }
84}
85
86impl<T> From<&EntityId> for Ref<T> {
87 fn from(id: &EntityId) -> Self { Ref::new(id.clone()) }
88}
89
90impl<T> TryFrom<&str> for Ref<T> {
91 type Error = ankurah_proto::DecodeError;
92 fn try_from(s: &str) -> Result<Self, Self::Error> { Ref::from_base64(s) }
93}
94
95impl<T> TryFrom<String> for Ref<T> {
96 type Error = ankurah_proto::DecodeError;
97 fn try_from(s: String) -> Result<Self, Self::Error> { Ref::from_base64(&s) }
98}
99
100impl<T> From<Ref<T>> for EntityId {
101 fn from(r: Ref<T>) -> Self { r.id }
102}
103
104impl<T> From<&Ref<T>> for EntityId {
105 fn from(r: &Ref<T>) -> Self { r.id.clone() }
106}
107
108impl<T> fmt::Display for Ref<T> {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.id.to_base64()) }
110}
111
112impl<T> From<Ref<T>> for ::ankql::ast::Expr {
114 fn from(r: Ref<T>) -> ::ankql::ast::Expr { r.id.into() }
115}
116
117impl<T> From<&Ref<T>> for ::ankql::ast::Expr {
118 fn from(r: &Ref<T>) -> ::ankql::ast::Expr { (&r.id).into() }
119}
120
121impl<V: View> From<&V> for Ref<V::Model> {
123 fn from(view: &V) -> Ref<V::Model> { Ref::new(view.id()) }
124}
125
126impl<T> Property for Ref<T> {
127 fn into_value(&self) -> Result<Option<Value>, PropertyError> { Ok(Some(Value::EntityId(self.id.clone()))) }
128
129 fn from_value(value: Option<Value>) -> Result<Self, PropertyError> {
130 match value {
131 Some(Value::EntityId(id)) => Ok(Ref::new(id)),
132 Some(Value::String(s)) => {
134 EntityId::from_base64(&s).map(Ref::new).map_err(|e| PropertyError::InvalidValue { value: s, ty: format!("Ref ({})", e) })
135 }
136 Some(other) => Err(PropertyError::InvalidVariant { given: other, ty: "Ref".to_string() }),
137 None => Err(PropertyError::Missing),
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 struct TestModel;
148
149 #[test]
150 fn test_ref_roundtrip() {
151 let id = EntityId::new();
152 let r: Ref<TestModel> = Ref::new(id.clone());
153
154 let value = r.into_value().unwrap().unwrap();
155 assert!(matches!(value, Value::EntityId(_)));
156
157 let recovered: Ref<TestModel> = Ref::from_value(Some(value)).unwrap();
158 assert_eq!(recovered.id(), id);
159 }
160
161 #[test]
162 fn test_ref_from_entity_id() {
163 let id = EntityId::new();
164 let r: Ref<TestModel> = id.clone().into();
165 assert_eq!(r.id(), id);
166 }
167
168 #[test]
169 fn test_ref_into_entity_id() {
170 let id = EntityId::new();
171 let r: Ref<TestModel> = Ref::new(id.clone());
172 let recovered: EntityId = r.into();
173 assert_eq!(recovered, id);
174 }
175
176 #[test]
177 fn test_ref_missing() {
178 let result: Result<Ref<TestModel>, _> = Ref::from_value(None);
179 assert!(matches!(result, Err(PropertyError::Missing)));
180 }
181
182 #[test]
183 fn test_ref_invalid_string() {
184 let result: Result<Ref<TestModel>, _> = Ref::from_value(Some(Value::String("not an id".to_string())));
186 assert!(matches!(result, Err(PropertyError::InvalidValue { .. })));
187 }
188
189 #[test]
190 fn test_ref_invalid_variant() {
191 let result: Result<Ref<TestModel>, _> = Ref::from_value(Some(Value::I64(42)));
193 assert!(matches!(result, Err(PropertyError::InvalidVariant { .. })));
194 }
195}