async_graphql/dynamic/
enum.rs

1use indexmap::IndexMap;
2
3use super::{Directive, directive::to_meta_directive_invocation};
4use crate::{
5    dynamic::SchemaError,
6    registry::{Deprecation, MetaEnumValue, MetaType, Registry},
7};
8
9/// A GraphQL enum item
10#[derive(Debug)]
11pub struct EnumItem {
12    pub(crate) name: String,
13    pub(crate) description: Option<String>,
14    pub(crate) deprecation: Deprecation,
15    inaccessible: bool,
16    tags: Vec<String>,
17    pub(crate) directives: Vec<Directive>,
18}
19
20impl<T: Into<String>> From<T> for EnumItem {
21    #[inline]
22    fn from(name: T) -> Self {
23        EnumItem {
24            name: name.into(),
25            description: None,
26            deprecation: Deprecation::NoDeprecated,
27            inaccessible: false,
28            tags: Vec::new(),
29            directives: Vec::new(),
30        }
31    }
32}
33
34impl EnumItem {
35    /// Create a new EnumItem
36    #[inline]
37    pub fn new(name: impl Into<String>) -> Self {
38        name.into().into()
39    }
40
41    impl_set_description!();
42    impl_set_deprecation!();
43    impl_set_inaccessible!();
44    impl_set_tags!();
45    impl_directive!();
46}
47
48/// A GraphQL enum type
49#[derive(Debug)]
50pub struct Enum {
51    pub(crate) name: String,
52    pub(crate) description: Option<String>,
53    pub(crate) enum_values: IndexMap<String, EnumItem>,
54    inaccessible: bool,
55    tags: Vec<String>,
56    pub(crate) directives: Vec<Directive>,
57    requires_scopes: Vec<String>,
58}
59
60impl Enum {
61    /// Create a GraphqL enum type
62    #[inline]
63    pub fn new(name: impl Into<String>) -> Self {
64        Self {
65            name: name.into(),
66            description: None,
67            enum_values: Default::default(),
68            inaccessible: false,
69            tags: Vec::new(),
70            directives: Vec::new(),
71            requires_scopes: Vec::new(),
72        }
73    }
74
75    impl_set_description!();
76    impl_directive!();
77
78    /// Add an item
79    #[inline]
80    pub fn item(mut self, item: impl Into<EnumItem>) -> Self {
81        let item = item.into();
82        self.enum_values.insert(item.name.clone(), item);
83        self
84    }
85
86    /// Add items
87    pub fn items(mut self, items: impl IntoIterator<Item = impl Into<EnumItem>>) -> Self {
88        for item in items {
89            let item = item.into();
90            self.enum_values.insert(item.name.clone(), item);
91        }
92        self
93    }
94
95    impl_set_inaccessible!();
96    impl_set_tags!();
97
98    /// Returns the type name
99    #[inline]
100    pub fn type_name(&self) -> &str {
101        &self.name
102    }
103
104    pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> {
105        let mut enum_values = IndexMap::new();
106
107        for item in self.enum_values.values() {
108            enum_values.insert(
109                item.name.clone(),
110                MetaEnumValue {
111                    name: item.name.as_str().into(),
112                    description: item.description.clone(),
113                    deprecation: item.deprecation.clone(),
114                    visible: None,
115                    inaccessible: item.inaccessible,
116                    tags: item.tags.clone(),
117                    directive_invocations: to_meta_directive_invocation(item.directives.clone()),
118                },
119            );
120        }
121
122        registry.types.insert(
123            self.name.clone(),
124            MetaType::Enum {
125                name: self.name.clone(),
126                description: self.description.clone(),
127                enum_values,
128                visible: None,
129                inaccessible: self.inaccessible,
130                tags: self.tags.clone(),
131                rust_typename: None,
132                directive_invocations: to_meta_directive_invocation(self.directives.clone()),
133                requires_scopes: self.requires_scopes.clone(),
134            },
135        );
136
137        Ok(())
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use crate::{Name, PathSegment, Pos, ServerError, Value, dynamic::*, value};
144
145    #[tokio::test]
146    async fn enum_type() {
147        let my_enum = Enum::new("MyEnum").item("A").item("B");
148
149        let query = Object::new("Query")
150            .field(Field::new(
151                "value",
152                TypeRef::named_nn(my_enum.type_name()),
153                |_| FieldFuture::new(async { Ok(Some(Value::from(Name::new("A")))) }),
154            ))
155            .field(
156                Field::new("value2", TypeRef::named_nn(my_enum.type_name()), |ctx| {
157                    FieldFuture::new(async move {
158                        Ok(Some(FieldValue::value(Name::new(
159                            ctx.args.try_get("input")?.enum_name()?,
160                        ))))
161                    })
162                })
163                .argument(InputValue::new(
164                    "input",
165                    TypeRef::named_nn(my_enum.type_name()),
166                )),
167            )
168            .field(Field::new(
169                "errValue",
170                TypeRef::named_nn(my_enum.type_name()),
171                |_| FieldFuture::new(async { Ok(Some(Value::from(Name::new("C")))) }),
172            ));
173        let schema = Schema::build("Query", None, None)
174            .register(my_enum)
175            .register(query)
176            .finish()
177            .unwrap();
178
179        assert_eq!(
180            schema
181                .execute("{ value value2(input: B) }")
182                .await
183                .into_result()
184                .unwrap()
185                .data,
186            value!({
187                "value": "A",
188                "value2": "B"
189            })
190        );
191
192        assert_eq!(
193            schema
194                .execute("{ errValue }")
195                .await
196                .into_result()
197                .unwrap_err(),
198            vec![ServerError {
199                message: "internal: invalid item for enum \"MyEnum\"".to_owned(),
200                source: None,
201                locations: vec![Pos { column: 3, line: 1 }],
202                path: vec![PathSegment::Field("errValue".to_owned())],
203                extensions: None,
204            }]
205        );
206    }
207}