async_graphql/dynamic/
scalar.rs

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