looking_glass/
lib.rs

1//! Looking Glass provides reflection for virtually any Rust type. It does this through a set of traits,
2//! and type-erasing enums.
3//!
4//! We allow the user to store any type, regardless of lifetime, through a trait called [`Instance`].
5//! [`Instance`] has a lifetime attached to it,
6//! that outlives the lifetime of any type it downcasts to.
7//! This ensures that lifetime guarantee are maintained at compile-time.
8//! Much like [`std::any::Any`], we perform runtime type-checks to ensure type-safety.
9//!
10//! Generally it is best to use the derive macros from [`looking_glass_derive`]
11//! to implement the various traits here,
12//! as care must be taken when implementing them to not enable
13//! undefined behaviour. Check out the docs for [`Value`], [`StructInstance`], [`VecInstance`],
14//! [`OptionInstance`], and [`EnumInstance`] for
15//!
16//!
17//! # Examples
18//!
19//! ## Struct reflection
20//!
21//! ```
22//! use looking_glass::Typed;
23//! use looking_glass_derive::Instance;
24//!
25//! #[derive(Instance, Clone, PartialEq)]
26//! struct Foo {
27//!  text: String,
28//!  int: i32,
29//! }
30//!
31//! let test = Foo { text: "Test".to_string(), int: -2 };
32//! let val = test.as_value();
33//! let inst = val.as_reflected_struct().unwrap();
34//! let value  = inst.get_value("text").expect("field not found");
35//! let text = value.as_ref().borrow::<&String>().expect("borrow failed");
36//! assert_eq!(text, &test.text);
37//! ```
38//!
39//! ## Vec reflection
40//!
41//! ```
42//! use looking_glass::{Typed, VecInstance};
43//!
44//! let vec = vec!["test".to_string(), "123".to_string(), "foo".to_string()];
45//! let first = vec.get_value(0).expect("get value failed");
46//! assert_eq!(first, "test".to_string().as_value());
47//! ```
48//!
49//! # Safety Details
50//!
51//! We can't use [`std::any::TypeId`], like [`std::any::Any`] does.
52//! So instead we create an enum called [`ValueTy`] that describes the reflected type.
53//! [`ValueTy`] internally stores the [`std::any::TypeId`] of a type.
54//! For types with generic lifetimes (or non-static lifetimes) we require that the implementer
55//! return the [`std::any::TypeId`] of the static version of that type.
56//! Imagine a struct defined like: `struct Foo<'a>(&'a str)`.
57//! The [`ValueTy`] for this type would be constructed like so:
58//!
59//! ```
60//! # use looking_glass::ValueTy;
61//! # struct Foo<'a>(&'a str);
62//!
63//! let _ = ValueTy::Struct(std::any::TypeId::of::<Foo<'static>>());
64//! ```
65//!
66//! [`looking_glass_derive`]: ../looking_glass_derive/index.html
67pub use bytes::Bytes;
68pub use smol_str::SmolStr;
69use std::any::TypeId;
70use thiserror::Error;
71
72mod field_mask;
73mod instance;
74mod owned;
75mod primatives;
76mod typed;
77mod value;
78
79pub use field_mask::*;
80pub use instance::*;
81pub use owned::*;
82pub use primatives::*;
83pub use typed::*;
84pub use value::*;
85
86#[cfg(test)]
87mod tests;
88
89#[derive(Error, Debug)]
90pub enum Error {
91    #[error("{0} not found")]
92    NotFound(SmolStr),
93    #[error("type error expected {expected} found {found}")]
94    TypeError { expected: SmolStr, found: SmolStr },
95}
96
97/// A trait for types that are stored directly in [`Value`].
98///
99/// This is implemented for various primitives, and generally shouldn't
100/// be implemented for our own types
101pub trait FromValue<'a, 's>: Sized {
102    fn from_value(value: &Value<'a, 's>) -> Option<Self>;
103}
104
105/// A ext trait for [`looking_glass_derive`] meant to consume a [`OwnedValue`] and return `T`
106///
107/// [`looking_glass_derive`]: ../looking_glass_derive
108pub trait IntoInner<T> {
109    fn into_inner(self) -> Result<T, Error>;
110}
111
112impl<'val, 'ty, 'r, T: Typed<'ty> + Sized + 'ty> FromValue<'val, 'ty> for &'r T
113where
114    'val: 'r,
115    'ty: 'val,
116{
117    fn from_value(value: &Value<'val, 'ty>) -> Option<Self> {
118        match value.0 {
119            ValueInner::String(s) => s.as_inst().downcast_ref::<T>(),
120            ValueInner::Bytes(b) => b.as_inst().downcast_ref::<T>(),
121            ValueInner::Vec(v) => v.as_inst().downcast_ref::<T>(),
122            ValueInner::Struct(s) => s.as_inst().downcast_ref::<T>(),
123            ValueInner::Enum(s) => s.as_inst().downcast_ref::<T>(),
124            ValueInner::Option(s) => s.as_inst().downcast_ref::<T>(),
125            _ => None,
126        }
127    }
128}
129
130/// A trait for types that are stored directly in [`Value`].
131///
132/// This is implemented for various primitives, and generally shouldn't
133/// be implemented for our own types
134pub trait IntoValue {
135    fn into_value<'val, 'ty>(self) -> Value<'val, 'ty>;
136}
137
138impl<'ty, T: Instance<'ty> + Typed<'ty> + 'ty> IntoInner<T> for OwnedValue<'ty> {
139    fn into_inner(self) -> Result<T, Error> {
140        let inst = match self {
141            OwnedValue::Vec(s) => s.into_boxed_instance(),
142            OwnedValue::Enum(s) => s.into_boxed_instance(),
143            OwnedValue::Struct(s) => s.into_boxed_instance(),
144            OwnedValue::Option(s) => s.into_boxed_instance(),
145            OwnedValue::String(s) => Box::new(s),
146            OwnedValue::Bytes(s) => Box::new(s),
147            _ => {
148                return Err(Error::TypeError {
149                    expected: "instance".into(),
150                    found: format!("{:?}", self).into(),
151                });
152            }
153        };
154        let inst = inst.downcast::<T>().ok_or_else(|| Error::TypeError {
155            expected: "instance".into(),
156            found: "other".into(),
157        })?;
158        Ok(*inst)
159    }
160}
161
162impl<'val, 'ty> FromValue<'val, 'ty> for &'val (dyn OptionInstance<'ty> + 'ty) {
163    //This lifetime bound could be better
164    fn from_value(value: &Value<'val, 'ty>) -> Option<Self> {
165        if let ValueInner::Option(o) = value.0 {
166            Some(o)
167        } else {
168            None
169        }
170    }
171}
172
173impl<'val, 'ty> FromValue<'val, 'ty> for &'val str {
174    fn from_value(value: &Value<'val, 'ty>) -> Option<&'val str> {
175        if let ValueInner::Str(s) = value.0 {
176            Some(s)
177        } else {
178            None
179        }
180    }
181}
182
183/// A description of a reflected type
184#[derive(Clone, PartialEq, Debug)]
185pub enum ValueTy {
186    U8,
187    U16,
188    U32,
189    U64,
190    I8,
191    I16,
192    I32,
193    I64,
194    F32,
195    F64,
196    Bool,
197    Str,
198    String,
199    Bytes,
200    VecInstance,
201    Vec(Box<ValueTy>),
202    StructInstance,
203    Struct(TypeId),
204    Enum(TypeId),
205    Option(Box<ValueTy>),
206}
207
208impl<'ty, T: Typed<'ty> + Clone + 'ty> Instance<'ty> for Option<T> {
209    fn name(&self) -> SmolStr {
210        format!("Option<{:?}>", T::ty()).into()
211    }
212
213    fn as_inst(&self) -> &(dyn Instance<'ty> + 'ty) {
214        self
215    }
216}
217
218impl<'ty, T: Typed<'ty> + Clone + 'ty> OptionInstance<'ty> for Option<T> {
219    fn value<'val>(&'val self) -> Option<Value<'val, 'ty>>
220    where
221        'ty: 'val,
222    {
223        self.as_ref().map(|val| val.as_value())
224    }
225
226    fn boxed_clone(&self) -> Box<dyn OptionInstance<'ty> + 'ty> {
227        Box::new(self.clone())
228    }
229
230    fn into_boxed_instance(self: Box<Self>) -> Box<dyn Instance<'ty> + 'ty> {
231        self
232    }
233}