async_graphql/dynamic/
scalar.rs

1use std::{
2    fmt::{self, Debug},
3    sync::Arc,
4};
5
6use super::{Directive, directive::to_meta_directive_invocation};
7use crate::{
8    Value,
9    dynamic::SchemaError,
10    registry::{MetaType, Registry, ScalarValidatorFn},
11};
12
13/// A GraphQL scalar type
14///
15/// # Examples
16///
17/// ```
18/// use async_graphql::{dynamic::*, value, Value};
19///
20/// let my_scalar = Scalar::new("MyScalar");
21///
22/// let query = Object::new("Query").field(Field::new("value", TypeRef::named_nn(my_scalar.type_name()), |ctx| {
23///     FieldFuture::new(async move { Ok(Some(Value::from("abc"))) })
24/// }));
25///
26/// # tokio::runtime::Runtime::new().unwrap().block_on(async move {
27///
28/// let schema = Schema::build(query.type_name(), None, None)
29///     .register(my_scalar)
30///     .register(query)
31///     .finish()?;
32///
33/// assert_eq!(
34///    schema
35///        .execute("{ value }")
36///        .await
37///        .into_result()
38///        .unwrap()
39///        .data,
40///    value!({ "value": "abc" })
41/// );
42///
43/// # Ok::<_, SchemaError>(())
44/// # }).unwrap();
45/// ```
46pub struct Scalar {
47    pub(crate) name: String,
48    pub(crate) description: Option<String>,
49    pub(crate) specified_by_url: Option<String>,
50    pub(crate) validator: Option<ScalarValidatorFn>,
51    inaccessible: bool,
52    tags: Vec<String>,
53    pub(crate) directives: Vec<Directive>,
54    requires_scopes: Vec<String>,
55}
56
57impl Debug for Scalar {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        f.debug_struct("Scalar")
60            .field("name", &self.name)
61            .field("description", &self.description)
62            .field("specified_by_url", &self.specified_by_url)
63            .field("inaccessible", &self.inaccessible)
64            .field("tags", &self.tags)
65            .field("requires_scopes", &self.requires_scopes)
66            .finish()
67    }
68}
69
70impl Scalar {
71    /// Create a GraphQL scalar type
72    #[inline]
73    pub fn new(name: impl Into<String>) -> Self {
74        Self {
75            name: name.into(),
76            description: None,
77            specified_by_url: None,
78            validator: None,
79            inaccessible: false,
80            tags: Vec::new(),
81            directives: Vec::new(),
82            requires_scopes: Vec::new(),
83        }
84    }
85
86    impl_set_description!();
87    impl_set_inaccessible!();
88    impl_set_tags!();
89    impl_directive!();
90
91    /// Set the validator
92    #[inline]
93    pub fn validator(self, validator: impl Fn(&Value) -> bool + Send + Sync + 'static) -> Self {
94        Self {
95            validator: Some(Arc::new(validator)),
96            ..self
97        }
98    }
99
100    #[inline]
101    pub(crate) fn validate(&self, value: &Value) -> bool {
102        match &self.validator {
103            Some(validator) => (validator)(value),
104            None => true,
105        }
106    }
107
108    /// Set the specified by url
109    #[inline]
110    pub fn specified_by_url(self, specified_by_url: impl Into<String>) -> Self {
111        Self {
112            specified_by_url: Some(specified_by_url.into()),
113            ..self
114        }
115    }
116
117    /// Returns the type name
118    #[inline]
119    pub fn type_name(&self) -> &str {
120        &self.name
121    }
122
123    pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> {
124        registry.types.insert(
125            self.name.clone(),
126            MetaType::Scalar {
127                name: self.name.clone(),
128                description: self.description.clone(),
129                is_valid: self.validator.clone(),
130                visible: None,
131                inaccessible: self.inaccessible,
132                tags: self.tags.clone(),
133                specified_by_url: self.specified_by_url.clone(),
134                directive_invocations: to_meta_directive_invocation(self.directives.clone()),
135                requires_scopes: self.requires_scopes.clone(),
136            },
137        );
138        Ok(())
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use async_graphql_parser::Pos;
145
146    use crate::{PathSegment, ServerError, dynamic::*, value};
147
148    #[tokio::test]
149    async fn custom_scalar() {
150        let scalar = Scalar::new("MyScalar");
151        let query = Object::new("Query").field(Field::new(
152            "value",
153            TypeRef::named_nn(scalar.type_name()),
154            |_| {
155                FieldFuture::new(async move {
156                    Ok(Some(value!({
157                        "a": 1,
158                        "b": "abc",
159                    })))
160                })
161            },
162        ));
163
164        let schema = Schema::build(query.type_name(), None, None)
165            .register(query)
166            .register(scalar)
167            .finish()
168            .unwrap();
169
170        assert_eq!(
171            schema
172                .execute("{ value }")
173                .await
174                .into_result()
175                .unwrap()
176                .data,
177            value!({
178                "value": {
179                    "a": 1,
180                    "b": "abc",
181                }
182            })
183        );
184    }
185
186    #[tokio::test]
187    async fn invalid_scalar_value() {
188        let scalar = Scalar::new("MyScalar");
189        let query = Object::new("Query").field(Field::new(
190            "value",
191            TypeRef::named_nn(scalar.type_name()),
192            |_| FieldFuture::new(async move { Ok(Some(FieldValue::owned_any(10i32))) }),
193        ));
194
195        let schema = Schema::build(query.type_name(), None, None)
196            .register(query)
197            .register(scalar)
198            .finish()
199            .unwrap();
200
201        assert_eq!(
202            schema.execute("{ value }").await.into_result().unwrap_err(),
203            vec![ServerError {
204                message: "internal: invalid value for scalar \"MyScalar\", expected \"FieldValue::Value\""
205                    .to_owned(),
206                source: None,
207                locations: vec![Pos { column: 3, line: 1 }],
208                path: vec![PathSegment::Field("value".to_owned())],
209                extensions: None,
210            }]
211        );
212    }
213}