junction_typeinfo/
lib.rs

1//! This crate defines an extremely basic reflection API, for use in
2//! `junction-client`. It does not have a stable public API, and will not be
3//! checked for breaking changes. Use it at your own risk.
4
5use std::collections::BTreeMap;
6
7pub use junction_typeinfo_derive::TypeInfo;
8
9/// A kind of type.
10///
11/// The not intended to be complete model of Rust data types- if we need it for
12/// defining a type in junction-client, it's probably here and if it's not, we
13/// probably left it out. The types here are also inspired by what's available in
14/// Python and Typescript.
15///
16/// The goal of this crate is to be able to use this data model to do some basic
17/// reflection on Rust structs, convert to a language-specific data model, and
18/// then generate code based on that language specific model. See
19/// junction-api-gen for language specific data models and codegen.
20///
21/// This is also not the same meaning of "kind" that gets used type-theory, it's
22/// here instead of "type" because it doesn't conflict with reserved identifiers
23/// in Rust.
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum Kind {
26    Bool,
27    String,
28    Int,
29    Float,
30    Duration,
31    Union(&'static str, Vec<Variant>),
32    Tuple(Vec<Kind>),
33    Array(Box<Kind>),
34    Map(Box<Kind>, Box<Kind>),
35    Object(&'static str),
36}
37
38/// A trait for types that can be reflected on.
39///
40/// All of the methods here except for `kind` have default impls for simple
41/// types and should not be overridden unless you know what you're doing.
42pub trait TypeInfo {
43    /// The [Kind] of this type.
44    fn kind() -> Kind;
45
46    /// Any documentation for this type. Mostly set for derive macros.
47    fn doc() -> Option<&'static str> {
48        None
49    }
50
51    /// Any fields that should be treated as part of this struct. Only
52    /// really makes sense to derive.
53    fn fields() -> Vec<Field> {
54        Vec::new()
55    }
56
57    /// Whether or not this field can be nullable.
58    ///
59    /// This should only ever be set for [Option], and doing otherwise is a
60    /// crime.
61    fn nullable() -> bool {
62        false
63    }
64
65    /// Bundle all of the info about this type into an [Item].
66    fn item() -> Item {
67        Item {
68            kind: Self::kind(),
69            fields: Self::fields(),
70            nullable: Self::nullable(),
71            doc: Self::doc(),
72        }
73    }
74
75    /// Any fields defined in Union variants. This must be empty for any types
76    /// with a [Kind] that is not [Kind::Union].
77    fn variant_fields() -> Vec<Field> {
78        let mut fields: BTreeMap<&str, Vec<_>> = BTreeMap::new();
79
80        if let Kind::Union(_, variants) = Self::kind() {
81            for variant in variants {
82                if let Variant::Struct(v) = variant {
83                    for field in v.fields {
84                        let fields: &mut Vec<Field> = fields.entry(field.name).or_default();
85
86                        match fields.first_mut() {
87                            Some(f) => {
88                                merge_fields(f, field);
89                            }
90                            _ => fields.push(field),
91                        }
92                    }
93                }
94            }
95        }
96
97        let mut fields: Vec<_> = fields.into_values().flatten().collect();
98        fields.sort_by_key(|f| f.name);
99        fields.dedup_by_key(|f| f.name);
100        fields
101    }
102
103    /// The fields that should be included when this type is flattened with
104    /// `serde(flatten)`.
105    fn flatten_fields() -> Vec<Field> {
106        match Self::kind() {
107            Kind::Union(_, _) => Self::variant_fields(),
108            Kind::Object(_) => Self::fields(),
109            _ => Vec::new(),
110        }
111    }
112}
113
114fn merge_fields(a: &mut Field, b: Field) {
115    // short-circuit. if the fields are exactly identical, do nothing
116    if a.kind == b.kind {
117        return;
118    }
119
120    // if this field isn't already a Union, make it one. drop the doc for now
121    match &mut a.kind {
122        Kind::Union(_, _) => (),
123        other => {
124            a.doc = None;
125            a.kind = Kind::Union(a.name, vec![Variant::Newtype(other.clone())]);
126        }
127    }
128    // get a mutable ref to the inside of this union type
129    let a_vars = match &mut a.kind {
130        Kind::Union(_, vars) => vars,
131        _ => unreachable!(),
132    };
133    // extend the possibilities
134    let b_vars = match b.kind {
135        Kind::Union(_, vars) => vars,
136        other => vec![Variant::Newtype(other)],
137    };
138    a_vars.extend(b_vars);
139    a_vars.dedup();
140}
141
142/// A type's [Kind], fields, nullability, and documentation.
143#[derive(Clone, Debug, PartialEq, Eq)]
144pub struct Item {
145    pub kind: Kind,
146    pub fields: Vec<Field>,
147    pub nullable: bool,
148    pub doc: Option<&'static str>,
149}
150
151/// The field in a `struct` or a struct-like `enum` variant.
152#[derive(Clone, Debug, PartialEq, Eq)]
153pub struct Field {
154    pub name: &'static str,
155    pub kind: Kind,
156    pub nullable: bool,
157    pub doc: Option<&'static str>,
158}
159
160/// A variant in a an enum.
161#[derive(Clone, Debug, PartialEq, Eq)]
162pub enum Variant {
163    Literal(&'static str),
164    Newtype(Kind),
165    Tuple(Vec<Kind>),
166    Struct(StructVariant),
167}
168
169/// A struct variant in an enum.
170#[derive(Clone, Debug, PartialEq, Eq)]
171pub struct StructVariant {
172    pub parent: &'static str,
173    pub name: &'static str,
174    pub doc: Option<&'static str>,
175    pub fields: Vec<Field>,
176}
177
178macro_rules! impl_type {
179    ($ty:ty, $kind:expr) => {
180        impl crate::TypeInfo for $ty {
181            fn kind() -> crate::Kind {
182                $kind
183            }
184        }
185    };
186}
187
188// bool
189impl_type!(bool, Kind::Bool);
190
191// string
192impl_type!(String, Kind::String);
193impl_type!(&'static str, Kind::String);
194
195// int
196//
197// NOTE: u64 and i64 are going to be hard to represent in JavaScript/TypescriptA
198// because f64 can't store ints past 2^53 -1. leave them out of here until we
199// figure out if it makes sense to add a BigInt kind.
200impl_type!(u8, Kind::Int);
201impl_type!(u16, Kind::Int);
202impl_type!(u32, Kind::Int);
203impl_type!(i8, Kind::Int);
204impl_type!(i16, Kind::Int);
205impl_type!(i32, Kind::Int);
206
207// float
208impl_type!(f32, Kind::Float);
209impl_type!(f64, Kind::Float);
210
211/// An Option<T> is the same as a nullable T in our target languages.
212impl<T: TypeInfo> TypeInfo for Option<T> {
213    fn kind() -> Kind {
214        T::kind()
215    }
216
217    fn nullable() -> bool {
218        true
219    }
220
221    fn fields() -> Vec<Field> {
222        T::fields()
223    }
224}
225
226/// A Vec<T> wraps the Kind of the T's it contains.
227impl<T: TypeInfo> TypeInfo for Vec<T> {
228    fn kind() -> Kind {
229        crate::Kind::Array(Box::new(T::kind()))
230    }
231
232    fn fields() -> Vec<Field> {
233        Vec::new()
234    }
235}
236
237impl<K: TypeInfo, V: TypeInfo> TypeInfo for BTreeMap<K, V> {
238    fn kind() -> Kind {
239        crate::Kind::Map(Box::new(K::kind()), Box::new(V::kind()))
240    }
241}
242
243macro_rules! tuple_impl {
244    ( $($type_param:tt),+ ) => {
245        /// This trait is implemented for tuples up to 5 items long.
246        impl<$($type_param: crate::TypeInfo),+> crate::TypeInfo for ($($type_param),+,) {
247            fn kind() -> Kind {
248                crate::Kind::Tuple(vec![
249                    $(
250                        <$type_param as crate::TypeInfo>::kind(),
251                    )+
252                ])
253            }
254        }
255    };
256}
257
258tuple_impl!(T1);
259tuple_impl!(T1, T2);
260tuple_impl!(T1, T2, T3);
261tuple_impl!(T1, T2, T3, T4);
262tuple_impl!(T1, T2, T3, T4, T5);
263
264// doctests below for compilation failures/unsupported cases in derive.
265//
266// these are here instead of in `tests/` because doctests are unsupported in
267// binaries.
268//
269// https://github.com/rust-lang/cargo/issues/5477
270
271/// ```compile_fail
272/// use junction_typeinfo::TypeInfo;
273///
274/// #[derive(TypeInfo)]
275/// struct Foo;
276/// ```
277#[doc(hidden)]
278#[allow(unused)]
279#[cfg(doctest)]
280fn test_unsupported_unit_struct() {
281    panic!("this function is just a doctest")
282}
283
284/// ```compile_fail
285/// use junction_typeinfo::TypeInfo;
286///
287/// #[derive(TypeInfo)]
288/// struct Foo();
289/// ```
290#[doc(hidden)]
291#[allow(unused)]
292#[cfg(doctest)]
293fn test_unsupported_fieldless_tuple_struct() {
294    panic!("this function is just a doctest")
295}
296
297/// ```compile_fail
298/// use junction_typeinfo::TypeInfo;
299///
300/// #[derive(TypeInfo)]
301/// struct Foo(String);
302/// ```
303#[doc(hidden)]
304#[allow(unused)]
305#[cfg(doctest)]
306fn test_unsupported_newtype_struct() {
307    panic!("this function is just a doctest")
308}
309
310/// ```compile_fail
311/// use junction_typeinfo::TypeInfo;
312///
313/// #[derive(TypeInfo)]
314/// union Foo {
315///   a: f32,
316///   b: u32,
317/// }
318/// ```
319#[doc(hidden)]
320#[allow(unused)]
321#[cfg(doctest)]
322fn test_unsupported_union() {
323    panic!("this function is just a doctest")
324}
325
326/// ```compile_fail
327/// use junction_typeinfo::TypeInfo;
328/// use serde::Serialize;
329///
330/// #[derive(TypeInfo, Serialize)]
331/// #[serde(tag = "type")]
332/// enum Foo {
333///   String(String),
334///   Int(usize),
335/// }
336/// ```
337#[cfg(doctest)]
338fn test_unsupported_serde_flatten() {
339    panic!("this function is just a doctest")
340}