apollo_smith/
name.rs

1use crate::DocumentBuilder;
2use arbitrary::Result as ArbitraryResult;
3use std::fmt::Write as _;
4
5const CHARSET_LETTERS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_";
6const CHARSET_NUMBERS: &[u8] = b"0123456789";
7const RESERVED_KEYWORDS: &[&str] = &[
8    "on",
9    "Int",
10    "Float",
11    "String",
12    "Boolean",
13    "ID",
14    "type",
15    "enum",
16    "union",
17    "extend",
18    "scalar",
19    "directive",
20    "query",
21    "mutation",
22    "subscription",
23    "schema",
24    "interface",
25];
26
27/// Name is useful to name different elements.
28///
29/// GraphQL Documents are full of named things: operations, fields, arguments, types, directives, fragments, and variables.
30/// All names must follow the same grammatical form.
31/// Names in GraphQL are case-sensitive. That is to say name, Name, and NAME all refer to different names.
32/// Underscores are significant, which means other_name and othername are two different names
33///
34/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#Name).
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub struct Name {
37    pub(crate) name: String,
38}
39
40impl From<Name> for String {
41    fn from(val: Name) -> Self {
42        val.name
43    }
44}
45
46impl From<Name> for apollo_compiler::Name {
47    fn from(value: Name) -> Self {
48        (&value).into()
49    }
50}
51
52impl From<&'_ Name> for apollo_compiler::Name {
53    fn from(value: &'_ Name) -> Self {
54        // FIXME: falliable instead of unwrap?
55        // Names from `DocumentBuilder` do have valid syntax,
56        // but the `new` constructor accepts any string
57        apollo_compiler::Name::new(&value.name).unwrap()
58    }
59}
60
61impl From<apollo_parser::cst::Name> for Name {
62    fn from(name: apollo_parser::cst::Name) -> Self {
63        Self {
64            name: name.ident_token().unwrap().to_string(),
65        }
66    }
67}
68
69impl Name {
70    pub const fn new(name: String) -> Self {
71        Self { name }
72    }
73}
74
75impl DocumentBuilder<'_> {
76    /// Create an arbitrary `Name`
77    pub fn name(&mut self) -> ArbitraryResult<Name> {
78        Ok(Name::new(self.limited_string(30)?))
79    }
80
81    /// Create an arbitrary type `Name`
82    pub fn type_name(&mut self) -> ArbitraryResult<Name> {
83        let mut new_name = self.limited_string(30)?;
84        if self.list_existing_type_names().any(|n| n.name == new_name) {
85            let _ = write!(
86                new_name,
87                "{}",
88                self.object_type_defs.len() + self.enum_type_defs.len() + self.directive_defs.len()
89            );
90        }
91        Ok(Name::new(new_name))
92    }
93
94    /// Create an arbitrary `Name` with an index included in the name (to avoid name conflict)
95    pub fn name_with_index(&mut self, index: usize) -> ArbitraryResult<Name> {
96        let mut name = self.limited_string(30)?;
97        let _ = write!(name, "{index}");
98
99        Ok(Name::new(name))
100    }
101
102    // Mirror what happens in `Arbitrary for String`, but do so with a clamped size.
103    pub(crate) fn limited_string(&mut self, max_size: usize) -> ArbitraryResult<String> {
104        loop {
105            let size = self.u.int_in_range(1..=max_size)?;
106
107            let gen_str = String::from_utf8(
108                (0..size)
109                    .map(|curr_idx| {
110                        let idx = self.u.arbitrary::<usize>()?;
111
112                        // Cannot start with a number
113                        let ch = if curr_idx == 0 {
114                            // len - 1 to not have a _ at the begining
115                            CHARSET_LETTERS[idx % (CHARSET_LETTERS.len() - 1)]
116                        } else {
117                            let idx = idx % (CHARSET_LETTERS.len() + CHARSET_NUMBERS.len());
118                            if idx < CHARSET_LETTERS.len() {
119                                CHARSET_LETTERS[idx]
120                            } else {
121                                CHARSET_NUMBERS[idx - CHARSET_LETTERS.len()]
122                            }
123                        };
124
125                        Ok(ch)
126                    })
127                    .collect::<ArbitraryResult<Vec<u8>>>()?,
128            )
129            .unwrap();
130            let new_gen = gen_str.trim_end_matches('_');
131            if !new_gen.is_empty() && !RESERVED_KEYWORDS.contains(&new_gen) {
132                break Ok(new_gen.to_string());
133            }
134        }
135    }
136
137    fn list_existing_type_names(&self) -> impl Iterator<Item = &Name> {
138        self.object_type_defs
139            .iter()
140            .map(|o| &o.name)
141            .chain(self.interface_type_defs.iter().map(|itf| &itf.name))
142            .chain(self.enum_type_defs.iter().map(|itf| &itf.name))
143            .chain(self.directive_defs.iter().map(|itf| &itf.name))
144            .chain(self.union_type_defs.iter().map(|itf| &itf.name))
145            .chain(self.input_object_type_defs.iter().map(|itf| &itf.name))
146            .chain(self.scalar_type_defs.iter().map(|itf| &itf.name))
147            .chain(self.directive_defs.iter().map(|itf| &itf.name))
148            .chain(self.fragment_defs.iter().map(|itf| &itf.name))
149            .chain(self.operation_defs.iter().filter_map(|op| op.name.as_ref()))
150    }
151}