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}