apollo_smith/
union.rs

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