Skip to main content

apollo_smith/
ty.rs

1use crate::input_value::InputValue;
2use crate::name::Name;
3use crate::DocumentBuilder;
4use apollo_compiler::ast;
5use arbitrary::Result as ArbitraryResult;
6use once_cell::sync::Lazy;
7
8static BUILTIN_SCALAR_NAMES: Lazy<[Ty; 5]> = Lazy::new(|| {
9    [
10        Ty::Named(Name::new(String::from("Int"))),
11        Ty::Named(Name::new(String::from("Float"))),
12        Ty::Named(Name::new(String::from("String"))),
13        Ty::Named(Name::new(String::from("Boolean"))),
14        Ty::Named(Name::new(String::from("ID"))),
15    ]
16});
17
18/// Convenience Type_ implementation used when creating a Field.
19/// Can be a `NamedType`, a `NonNull` or a `List`.
20///
21/// This enum is resposible for encoding creating values such as `String!`, `[[[[String]!]!]!]!`, etc.
22#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub enum Ty {
24    /// The Non-Null field type.
25    Named(Name),
26    /// The List field type.
27    List(Box<Ty>),
28    /// The Named field type.
29    NonNull(Box<Ty>),
30}
31
32impl From<Ty> for ast::Type {
33    fn from(val: Ty) -> Self {
34        match val {
35            Ty::Named(name) => Self::Named(name.into()),
36            Ty::List(ty) => Self::from(*ty).list(),
37            Ty::NonNull(ty) => Self::from(*ty).non_null(),
38        }
39    }
40}
41
42impl From<apollo_parser::cst::Type> for Ty {
43    fn from(ty: apollo_parser::cst::Type) -> Self {
44        match ty {
45            apollo_parser::cst::Type::NamedType(named_ty) => named_ty.into(),
46            apollo_parser::cst::Type::ListType(list_type) => {
47                Self::List(Box::new(list_type.ty().unwrap().into()))
48            }
49            apollo_parser::cst::Type::NonNullType(non_null) => {
50                if let Some(named_ty) = non_null.named_type() {
51                    Self::NonNull(Box::new(named_ty.into()))
52                } else if let Some(list_type) = non_null.list_type() {
53                    Self::NonNull(Box::new(Self::List(Box::new(
54                        list_type.ty().unwrap().into(),
55                    ))))
56                } else {
57                    panic!("a non null type must have a type")
58                }
59            }
60        }
61    }
62}
63
64impl From<apollo_parser::cst::NamedType> for Ty {
65    fn from(ty: apollo_parser::cst::NamedType) -> Self {
66        Self::Named(ty.name().unwrap().into())
67    }
68}
69
70impl Ty {
71    pub(crate) fn name(&self) -> &Name {
72        match self {
73            Ty::Named(name) => name,
74            Ty::List(list) => list.name(),
75            Ty::NonNull(non_null) => non_null.name(),
76        }
77    }
78
79    /// Returns `true` if the ty is [`Named`].
80    ///
81    /// [`Named`]: Ty::Named
82    pub fn is_named(&self) -> bool {
83        matches!(self, Self::Named(..))
84    }
85
86    pub(crate) fn is_builtin(&self) -> bool {
87        BUILTIN_SCALAR_NAMES.contains(&Ty::Named(self.name().clone()))
88    }
89
90    /// Strip the outermost `NonNull` wrapper, if present, making this type nullable.
91    pub(crate) fn into_nullable(self) -> Self {
92        match self {
93            Ty::NonNull(inner) => *inner,
94            other => other,
95        }
96    }
97}
98
99impl DocumentBuilder<'_> {
100    /// Create an arbitrary `Ty`
101    pub fn ty(&mut self) -> ArbitraryResult<Ty> {
102        self.generate_ty(true)
103    }
104
105    /// Choose an arbitrary existing `Ty` given a slice of existing types
106    pub fn choose_ty(&mut self, existing_types: &[Ty]) -> ArbitraryResult<Ty> {
107        self.choose_ty_given_nullable(existing_types, true)
108    }
109
110    /// Choose an arbitrary existing named `Ty` given a slice of existing types
111    pub fn choose_named_ty(&mut self, existing_types: &[Ty]) -> ArbitraryResult<Ty> {
112        let used_type_names: Vec<&Ty> = existing_types
113            .iter()
114            .chain(BUILTIN_SCALAR_NAMES.iter())
115            .collect();
116
117        Ok(self.u.choose(&used_type_names)?.to_owned().clone())
118    }
119
120    fn choose_ty_given_nullable(
121        &mut self,
122        existing_types: &[Ty],
123        is_nullable: bool,
124    ) -> ArbitraryResult<Ty> {
125        let ty: Ty = match self.u.int_in_range(0..=2usize)? {
126            // Named type
127            0 => {
128                let used_type_names: Vec<&Ty> = existing_types
129                    .iter()
130                    .chain(BUILTIN_SCALAR_NAMES.iter())
131                    .collect();
132
133                self.u.choose(&used_type_names)?.to_owned().clone()
134            }
135            // List type
136            1 => Ty::List(Box::new(
137                self.choose_ty_given_nullable(existing_types, true)?,
138            )),
139            // Non Null type
140            2 => {
141                if is_nullable {
142                    Ty::NonNull(Box::new(
143                        self.choose_ty_given_nullable(existing_types, false)?,
144                    ))
145                } else {
146                    self.choose_ty_given_nullable(existing_types, is_nullable)?
147                }
148            }
149            _ => unreachable!(),
150        };
151
152        Ok(ty)
153    }
154
155    fn generate_ty(&mut self, is_nullable: bool) -> ArbitraryResult<Ty> {
156        let ty = match self.u.int_in_range(0..=2usize)? {
157            // Named type
158            0 => Ty::Named(self.name()?),
159            // List type
160            1 => Ty::List(Box::new(self.generate_ty(true)?)),
161            // Non Null type
162            2 => {
163                if is_nullable {
164                    Ty::NonNull(Box::new(self.generate_ty(false)?))
165                } else {
166                    self.generate_ty(is_nullable)?
167                }
168            }
169            _ => unreachable!(),
170        };
171
172        Ok(ty)
173    }
174
175    /// List all existing (already created) `Ty`
176    pub(crate) fn list_existing_types(&self) -> Vec<Ty> {
177        self.object_type_defs
178            .iter()
179            .map(|o| Ty::Named(o.name.clone()))
180            .chain(
181                self.enum_type_defs
182                    .iter()
183                    .map(|e| Ty::Named(e.name.clone())),
184            )
185            .collect()
186    }
187
188    /// List the existing types that are valid in input positions (argument
189    /// types, input-object fields, variable definitions). Per the GraphQL
190    /// spec, only scalars, enums, and input objects qualify. Built-in
191    /// scalars are added separately by `choose_ty`.
192    pub(crate) fn list_existing_input_types(&self) -> Vec<Ty> {
193        self.scalar_type_defs
194            .iter()
195            .map(|s| Ty::Named(s.name.clone()))
196            .chain(
197                self.enum_type_defs
198                    .iter()
199                    .map(|e| Ty::Named(e.name.clone())),
200            )
201            .chain(
202                self.input_object_type_defs
203                    .iter()
204                    .map(|io| Ty::Named(io.name.clone())),
205            )
206            .collect()
207    }
208
209    /// List all existing object (already created) `Ty`
210    pub(crate) fn list_existing_object_types(&self) -> Vec<Ty> {
211        self.object_type_defs
212            .iter()
213            .map(|o| Ty::Named(o.name.clone()))
214            .collect()
215    }
216
217    #[allow(dead_code)]
218    pub(crate) fn generate_value_for_type(&mut self, _ty: &Ty) -> InputValue {
219        todo!()
220    }
221}