async_graphql/dynamic/
enum.rs

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