Skip to main content

apollo_smith/
union.rs

1use crate::description::Description;
2use crate::directive::Directive;
3use crate::directive::DirectiveLocation;
4use crate::name::Name;
5use crate::DocumentBuilder;
6use apollo_compiler::ast;
7use arbitrary::Result as ArbitraryResult;
8use indexmap::IndexMap;
9use indexmap::IndexSet;
10
11/// UnionDefs are an abstract type where no common fields are declared.
12///
13/// *UnionDefTypeDefinition*:
14///     Description? **union** Name Directives? UnionDefMemberTypes?
15///
16/// Detailed documentation can be found in [GraphQL spec](https://spec.graphql.org/October2021/#sec-UnionDef).
17#[derive(Debug, Clone)]
18pub struct UnionTypeDef {
19    pub(crate) name: Name,
20    pub(crate) description: Option<Description>,
21    pub(crate) members: IndexSet<Name>,
22    pub(crate) directives: IndexMap<Name, Directive>,
23    pub(crate) extend: bool,
24}
25
26impl From<UnionTypeDef> for ast::Definition {
27    fn from(x: UnionTypeDef) -> Self {
28        if x.extend {
29            ast::UnionTypeExtension {
30                name: x.name.into(),
31                directives: Directive::to_ast(x.directives),
32                members: x.members.into_iter().map(Into::into).collect(),
33            }
34            .into()
35        } else {
36            ast::UnionTypeDefinition {
37                description: x.description.map(Into::into),
38                name: x.name.into(),
39                directives: Directive::to_ast(x.directives),
40                members: x.members.into_iter().map(Into::into).collect(),
41            }
42            .into()
43        }
44    }
45}
46
47impl TryFrom<apollo_parser::cst::UnionTypeDefinition> for UnionTypeDef {
48    type Error = crate::FromError;
49
50    fn try_from(union_def: apollo_parser::cst::UnionTypeDefinition) -> Result<Self, Self::Error> {
51        Ok(Self {
52            name: union_def
53                .name()
54                .expect("object type definition must have a name")
55                .into(),
56            description: union_def.description().map(Description::from),
57            directives: union_def
58                .directives()
59                .map(Directive::convert_directives)
60                .transpose()?
61                .unwrap_or_default(),
62            extend: false,
63            members: union_def
64                .union_member_types()
65                .map(|members| {
66                    members
67                        .named_types()
68                        .map(|n| n.name().unwrap().into())
69                        .collect()
70                })
71                .unwrap_or_default(),
72        })
73    }
74}
75
76impl TryFrom<apollo_parser::cst::UnionTypeExtension> for UnionTypeDef {
77    type Error = crate::FromError;
78
79    fn try_from(union_def: apollo_parser::cst::UnionTypeExtension) -> Result<Self, Self::Error> {
80        Ok(Self {
81            name: union_def
82                .name()
83                .expect("object type definition must have a name")
84                .into(),
85            description: None,
86            directives: union_def
87                .directives()
88                .map(|d| {
89                    d.directives()
90                        .map(|d| Ok((d.name().unwrap().into(), Directive::try_from(d)?)))
91                        .collect::<Result<_, crate::FromError>>()
92                })
93                .transpose()?
94                .unwrap_or_default(),
95            extend: true,
96            members: union_def
97                .union_member_types()
98                .map(|members| {
99                    members
100                        .named_types()
101                        .map(|n| n.name().unwrap().into())
102                        .collect()
103                })
104                .unwrap_or_default(),
105        })
106    }
107}
108
109impl DocumentBuilder<'_> {
110    /// Create an arbitrary `UnionTypeDef`
111    pub fn union_type_definition(&mut self) -> ArbitraryResult<UnionTypeDef> {
112        let extend = !self.union_type_defs.is_empty() && self.u.arbitrary().unwrap_or(false);
113        let name = if extend {
114            let available_unions: Vec<&Name> = self
115                .union_type_defs
116                .iter()
117                .filter_map(|union| {
118                    if union.extend {
119                        None
120                    } else {
121                        Some(&union.name)
122                    }
123                })
124                .collect();
125            (*self.u.choose(&available_unions)?).clone()
126        } else {
127            self.type_name()?
128        };
129        let description = self
130            .u
131            .arbitrary()
132            .unwrap_or(false)
133            .then(|| self.description())
134            .transpose()?;
135        let directives = self.directives(DirectiveLocation::Union)?;
136        // Union members must be Object base types — built-in scalars,
137        // other unions, interfaces, enums, and input objects are all
138        // invalid as members.
139        //
140        // See <https://spec.graphql.org/October2021/#sec-Unions>.
141        let existing_members: IndexSet<Name> = self
142            .union_type_defs
143            .iter()
144            .filter(|u| u.name == name)
145            .flat_map(|u| u.members.iter().cloned())
146            .collect();
147        let object_types: Vec<_> = self
148            .list_existing_object_types()
149            .into_iter()
150            .filter(|o| !existing_members.contains(o.name()))
151            .collect();
152        if object_types.is_empty() {
153            return Err(arbitrary::Error::IncorrectFormat);
154        }
155        let members = (0..self.u.int_in_range(2..=10)?)
156            .map(|_| Ok(self.u.choose(&object_types)?.name().clone()))
157            .collect::<ArbitraryResult<IndexSet<_>>>()?;
158
159        if extend && directives.is_empty() && members.is_empty() {
160            return Err(arbitrary::Error::IncorrectFormat);
161        }
162
163        Ok(UnionTypeDef {
164            name,
165            description,
166            members,
167            directives,
168            extend,
169        })
170    }
171}