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}