Skip to main content

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//! [`HashMapInstance`], [`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::HashMap(h) => h.as_inst().downcast_ref::<T>(),
123            ValueInner::Struct(s) => s.as_inst().downcast_ref::<T>(),
124            ValueInner::Enum(s) => s.as_inst().downcast_ref::<T>(),
125            ValueInner::Option(s) => s.as_inst().downcast_ref::<T>(),
126            _ => None,
127        }
128    }
129}
130
131/// A trait for types that are stored directly in [`Value`].
132///
133/// This is implemented for various primitives, and generally shouldn't
134/// be implemented for our own types
135pub trait IntoValue {
136    fn into_value<'val, 'ty>(self) -> Value<'val, 'ty>;
137}
138
139impl<'ty, T: Instance<'ty> + Typed<'ty> + 'ty> IntoInner<T> for OwnedValue<'ty> {
140    fn into_inner(self) -> Result<T, Error> {
141        let inst = match self {
142            OwnedValue::Vec(s) => s.into_boxed_instance(),
143            OwnedValue::HashMap(s) => s.into_boxed_instance(),
144            OwnedValue::Enum(s) => s.into_boxed_instance(),
145            OwnedValue::Struct(s) => s.into_boxed_instance(),
146            OwnedValue::Option(s) => s.into_boxed_instance(),
147            OwnedValue::String(s) => Box::new(s),
148            OwnedValue::Bytes(s) => Box::new(s),
149            _ => {
150                return Err(Error::TypeError {
151                    expected: "instance".into(),
152                    found: format!("{:?}", self).into(),
153                });
154            }
155        };
156        let inst = inst.downcast::<T>().ok_or_else(|| Error::TypeError {
157            expected: "instance".into(),
158            found: "other".into(),
159        })?;
160        Ok(*inst)
161    }
162}
163
164impl<'val, 'ty> FromValue<'val, 'ty> for &'val (dyn OptionInstance<'ty> + 'ty) {
165    //This lifetime bound could be better
166    fn from_value(value: &Value<'val, 'ty>) -> Option<Self> {
167        if let ValueInner::Option(o) = value.0 {
168            Some(o)
169        } else {
170            None
171        }
172    }
173}
174
175impl<'val, 'ty> FromValue<'val, 'ty> for &'val str {
176    fn from_value(value: &Value<'val, 'ty>) -> Option<&'val str> {
177        if let ValueInner::Str(s) = value.0 {
178            Some(s)
179        } else {
180            None
181        }
182    }
183}
184
185/// A description of a reflected type
186#[derive(Clone, PartialEq, Debug)]
187pub enum ValueTy {
188    U8,
189    U16,
190    U32,
191    U64,
192    I8,
193    I16,
194    I32,
195    I64,
196    F32,
197    F64,
198    Bool,
199    Str,
200    String,
201    Bytes,
202    VecInstance,
203    Vec(Box<ValueTy>),
204    StructInstance,
205    Struct(TypeId),
206    HashMapInstance,
207    HashMap(Box<ValueTy>),
208    Enum(TypeId),
209    Option(Box<ValueTy>),
210}
211
212impl<'ty, T: Typed<'ty> + Clone + 'ty> Instance<'ty> for Option<T> {
213    fn name(&self) -> SmolStr {
214        format!("Option<{:?}>", T::ty()).into()
215    }
216
217    fn as_inst(&self) -> &(dyn Instance<'ty> + 'ty) {
218        self
219    }
220}
221
222impl<'ty, T: Typed<'ty> + Clone + 'ty> OptionInstance<'ty> for Option<T> {
223    fn value<'val>(&'val self) -> Option<Value<'val, 'ty>>
224    where
225        'ty: 'val,
226    {
227        self.as_ref().map(|val| val.as_value())
228    }
229
230    fn boxed_clone(&self) -> Box<dyn OptionInstance<'ty> + 'ty> {
231        Box::new(self.clone())
232    }
233
234    fn into_boxed_instance(self: Box<Self>) -> Box<dyn Instance<'ty> + 'ty> {
235        self
236    }
237}