async_graphql/dynamic/
scalar.rs1use 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
13pub 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 #[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 #[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 #[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 #[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}