1use crate::query::DirectiveLocation;
2
3#[cfg(feature = "sdl")]
4mod sdl;
5
6impl crate::query::IntrospectionQuery {
7 pub fn into_schema(self) -> Result<Schema, SchemaError> {
10 Schema::try_from(
11 self.introspected_schema
12 .ok_or(SchemaError::IntrospectionQueryFailed)?,
13 )
14 }
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18#[non_exhaustive]
19pub struct Schema {
21 pub query_type: String,
23 pub mutation_type: Option<String>,
25 pub subscription_type: Option<String>,
27 pub types: Vec<Type>,
29 pub directives: Vec<Directive>,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34#[non_exhaustive]
35pub struct Directive {
37 pub name: String,
39 pub description: Option<String>,
41 pub args: Vec<InputValue>,
43 pub is_repeatable: bool,
45 pub locations: Vec<DirectiveLocation>,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum Type {
52 Object(ObjectType),
54 InputObject(InputObjectType),
56 Enum(EnumType),
58 Interface(InterfaceType),
60 Union(UnionType),
62 Scalar(ScalarType),
64}
65
66impl Type {
67 pub fn name(&self) -> &str {
69 match self {
70 Type::Object(inner) => &inner.name,
71 Type::InputObject(inner) => &inner.name,
72 Type::Enum(inner) => &inner.name,
73 Type::Interface(inner) => &inner.name,
74 Type::Union(inner) => &inner.name,
75 Type::Scalar(inner) => &inner.name,
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct ObjectType {
84 pub name: String,
86 pub description: Option<String>,
88 pub fields: Vec<Field>,
90 pub interfaces: Vec<String>,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95#[non_exhaustive]
96pub struct InputObjectType {
99 pub name: String,
101 pub description: Option<String>,
103 pub fields: Vec<InputValue>,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq)]
108#[non_exhaustive]
109pub struct EnumType {
112 pub name: String,
114 pub description: Option<String>,
116 pub values: Vec<EnumValue>,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
121#[non_exhaustive]
122pub struct InterfaceType {
124 pub name: String,
126 pub description: Option<String>,
128 pub fields: Vec<Field>,
130 pub interfaces: Vec<String>,
132 pub possible_types: Vec<String>,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
137#[non_exhaustive]
138pub struct UnionType {
141 pub name: String,
143 pub description: Option<String>,
145 pub possible_types: Vec<String>,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
150#[non_exhaustive]
151pub struct ScalarType {
156 pub name: String,
158 pub description: Option<String>,
160 pub specified_by_url: Option<String>,
162}
163
164impl ScalarType {
165 pub fn is_builtin(&self) -> bool {
169 matches!(
170 self.name.as_str(),
171 "String" | "Boolean" | "Int" | "ID" | "Float"
172 )
173 }
174}
175
176#[derive(Debug, Clone, PartialEq, Eq)]
177#[non_exhaustive]
178pub struct EnumValue {
180 pub name: String,
182 pub description: Option<String>,
184 pub deprecated: Deprecated,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq)]
189pub enum Deprecated {
191 No,
193 Yes(Option<String>),
195}
196
197impl Deprecated {
198 fn new(is_deprecated: bool, deprecation_reason: Option<String>) -> Deprecated {
199 match (is_deprecated, deprecation_reason) {
200 (false, _) => Deprecated::No,
201 (true, reason) => Deprecated::Yes(reason),
202 }
203 }
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
207#[non_exhaustive]
208pub struct Field {
210 pub name: String,
212 pub description: Option<String>,
214 pub args: Vec<InputValue>,
216 pub ty: FieldType,
218 pub deprecated: Deprecated,
220}
221
222#[derive(Debug, Clone, PartialEq, Eq)]
223#[non_exhaustive]
224pub struct InputValue {
226 pub name: String,
228 pub description: Option<String>,
230 pub ty: FieldType,
232 pub default_value: Option<String>,
234}
235
236#[derive(Debug, Clone, PartialEq, Eq)]
237#[non_exhaustive]
238pub struct FieldType {
243 pub wrapping: FieldWrapping,
245 pub name: String,
247}
248
249impl std::fmt::Display for FieldType {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 let FieldType { wrapping, name } = self;
252 let wrapping_types = wrapping.into_iter().collect::<Vec<_>>();
253 for wrapping_type in &wrapping_types {
254 match wrapping_type {
255 WrappingType::List => write!(f, "[")?,
256 WrappingType::NonNull => {}
257 }
258 }
259 write!(f, "{name}")?;
260 for wrapping_type in wrapping_types.iter().rev() {
261 match wrapping_type {
262 WrappingType::List => write!(f, "]")?,
263 WrappingType::NonNull => write!(f, "!")?,
264 }
265 }
266 Ok(())
267 }
268}
269
270#[derive(Clone, Copy, PartialEq, Eq)]
271pub struct FieldWrapping([u8; 8]);
273
274impl std::fmt::Debug for FieldWrapping {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 write!(f, "{:?}", self.into_iter().collect::<Vec<_>>())
277 }
278}
279
280impl IntoIterator for FieldWrapping {
281 type Item = WrappingType;
282
283 type IntoIter = Box<dyn Iterator<Item = WrappingType>>;
284
285 fn into_iter(self) -> Self::IntoIter {
286 Box::new(
287 self.0
288 .into_iter()
289 .take_while(|&item| item != 0)
290 .flat_map(|item| match item {
291 1 => std::iter::once(WrappingType::List),
292 2 => std::iter::once(WrappingType::NonNull),
293 _ => unreachable!(),
294 }),
295 )
296 }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub enum WrappingType {
302 List,
307 NonNull,
309}
310
311#[derive(thiserror::Error, Debug, Eq, PartialEq)]
312pub enum SchemaError {
314 #[error("A type in the introspection output was missing a name")]
316 TypeMissingName,
317 #[error("Found a list wrapper type in a position that should contain a named type")]
319 UnexpectedList,
320 #[error("Found a non-null wrapper type in a position that should contain a named type")]
322 UnexpectedNonNull,
323 #[error("Found a wrapping type that had no inner ofType")]
325 WrappingTypeWithNoInner,
326 #[error("Found a wrapping type that was too nested")]
328 TooMuchWrapping,
329 #[error("The introspection query returned no data. Try looking in the response for errors")]
331 IntrospectionQueryFailed,
332}
333
334impl TryFrom<crate::query::IntrospectedSchema> for Schema {
335 type Error = SchemaError;
336
337 fn try_from(schema: crate::query::IntrospectedSchema) -> Result<Self, Self::Error> {
338 Ok(Schema {
339 query_type: schema.query_type.into_name()?,
340 mutation_type: schema.mutation_type.map(|ty| ty.into_name()).transpose()?,
341 subscription_type: schema
342 .subscription_type
343 .map(|ty| ty.into_name())
344 .transpose()?,
345 types: schema
346 .types
347 .into_iter()
348 .map(TryInto::try_into)
349 .collect::<Result<Vec<_>, _>>()?,
350 directives: schema
351 .directives
352 .into_iter()
353 .map(TryInto::try_into)
354 .collect::<Result<Vec<_>, _>>()?,
355 })
356 }
357}
358
359impl TryFrom<crate::query::Type> for Type {
360 type Error = SchemaError;
361
362 fn try_from(ty: crate::query::Type) -> Result<Self, Self::Error> {
363 match ty.kind {
364 crate::query::TypeKind::Scalar => Ok(Type::Scalar(ScalarType {
365 name: ty.name.ok_or(SchemaError::TypeMissingName)?,
366 description: ty.description,
367 specified_by_url: None,
368 })),
369 crate::query::TypeKind::Object => Ok(Type::Object(ObjectType {
370 name: ty.name.ok_or(SchemaError::TypeMissingName)?,
371 fields: ty
372 .fields
373 .unwrap_or_default()
374 .into_iter()
375 .map(TryInto::try_into)
376 .collect::<Result<Vec<_>, _>>()?,
377 description: ty.description,
378 interfaces: ty
379 .interfaces
380 .unwrap_or_default()
381 .into_iter()
382 .map(|ty| ty.into_name())
383 .collect::<Result<Vec<_>, _>>()?,
384 })),
385 crate::query::TypeKind::Interface => Ok(Type::Interface(InterfaceType {
386 name: ty.name.ok_or(SchemaError::TypeMissingName)?,
387 description: ty.description,
388 fields: ty
389 .fields
390 .unwrap_or_default()
391 .into_iter()
392 .map(TryInto::try_into)
393 .collect::<Result<Vec<_>, _>>()?,
394 interfaces: ty
395 .interfaces
396 .unwrap_or_default()
397 .into_iter()
398 .map(|ty| ty.into_name())
399 .collect::<Result<Vec<_>, _>>()?,
400 possible_types: ty
401 .possible_types
402 .unwrap_or_default()
403 .into_iter()
404 .map(|ty| ty.into_name())
405 .collect::<Result<Vec<_>, _>>()?,
406 })),
407 crate::query::TypeKind::Union => Ok(Type::Union(UnionType {
408 name: ty.name.ok_or(SchemaError::TypeMissingName)?,
409 description: ty.description,
410 possible_types: ty
411 .possible_types
412 .unwrap_or_default()
413 .into_iter()
414 .map(|ty| ty.into_name())
415 .collect::<Result<Vec<_>, _>>()?,
416 })),
417 crate::query::TypeKind::Enum => Ok(Type::Enum(EnumType {
418 name: ty.name.ok_or(SchemaError::TypeMissingName)?,
419 description: ty.description,
420 values: ty
421 .enum_values
422 .unwrap_or_default()
423 .into_iter()
424 .map(Into::into)
425 .collect(),
426 })),
427 crate::query::TypeKind::InputObject => Ok(Type::InputObject(InputObjectType {
428 name: ty.name.ok_or(SchemaError::TypeMissingName)?,
429 description: ty.description,
430 fields: ty
431 .input_fields
432 .unwrap_or_default()
433 .into_iter()
434 .map(InputValue::try_from)
435 .collect::<Result<Vec<_>, _>>()?,
436 })),
437 crate::query::TypeKind::List => Err(SchemaError::UnexpectedList),
438 crate::query::TypeKind::NonNull => Err(SchemaError::UnexpectedNonNull),
439 }
440 }
441}
442
443impl TryFrom<crate::query::Field> for Field {
444 type Error = SchemaError;
445
446 fn try_from(field: crate::query::Field) -> Result<Self, Self::Error> {
447 Ok(Field {
448 name: field.name,
449 description: field.description,
450 args: field
451 .args
452 .into_iter()
453 .map(TryInto::try_into)
454 .collect::<Result<Vec<_>, _>>()?,
455 ty: field.ty.try_into()?,
456 deprecated: Deprecated::new(field.is_deprecated, field.deprecation_reason),
457 })
458 }
459}
460
461impl TryFrom<crate::query::FieldType> for FieldType {
462 type Error = SchemaError;
463
464 fn try_from(field_type: crate::query::FieldType) -> Result<Self, Self::Error> {
465 let mut wrapping = [0; 8];
466 let mut wrapping_pos = 0;
467 let mut current_ty = field_type;
468 loop {
469 if wrapping_pos >= wrapping.len() {
470 return Err(SchemaError::TooMuchWrapping);
471 }
472 match current_ty.kind {
473 crate::query::TypeKind::List => {
474 wrapping[wrapping_pos] = 1;
475 wrapping_pos += 1;
476 current_ty = *current_ty
477 .of_type
478 .ok_or(SchemaError::WrappingTypeWithNoInner)?;
479 }
480 crate::query::TypeKind::NonNull => {
481 wrapping[wrapping_pos] = 2;
482 wrapping_pos += 1;
483 current_ty = *current_ty
484 .of_type
485 .ok_or(SchemaError::WrappingTypeWithNoInner)?;
486 }
487 _ => {
488 return Ok(FieldType {
489 name: current_ty.name.ok_or(SchemaError::TypeMissingName)?,
490 wrapping: FieldWrapping(wrapping),
491 })
492 }
493 };
494 }
495 }
496}
497
498impl TryFrom<crate::query::InputValue> for InputValue {
499 type Error = SchemaError;
500
501 fn try_from(value: crate::query::InputValue) -> Result<Self, Self::Error> {
502 Ok(InputValue {
503 name: value.name,
504 description: value.description,
505 ty: value.ty.try_into()?,
506 default_value: value.default_value,
507 })
508 }
509}
510
511impl From<crate::query::EnumValue> for EnumValue {
512 fn from(value: crate::query::EnumValue) -> Self {
513 EnumValue {
514 name: value.name,
515 description: value.description,
516 deprecated: Deprecated::new(value.is_deprecated, value.deprecation_reason),
517 }
518 }
519}
520
521impl TryFrom<crate::query::Directive> for Directive {
522 type Error = SchemaError;
523
524 fn try_from(value: crate::query::Directive) -> Result<Self, Self::Error> {
525 Ok(Directive {
526 name: value.name,
527 description: value.description,
528 args: value
529 .args
530 .into_iter()
531 .map(TryInto::try_into)
532 .collect::<Result<Vec<_>, _>>()?,
533 locations: value.locations,
534 is_repeatable: value.is_repeatable,
535 })
536 }
537}
538
539impl crate::query::NamedType {
540 fn into_name(self) -> Result<String, SchemaError> {
541 self.name.ok_or(SchemaError::TypeMissingName)
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548 use crate::query;
549
550 #[test]
551 fn test_field_type_to_string() {
552 let ty = FieldType::try_from(query::FieldType {
553 kind: query::TypeKind::NonNull,
554 name: None,
555 of_type: Some(Box::new(query::FieldType {
556 kind: query::TypeKind::List,
557 name: None,
558 of_type: Some(Box::new(query::FieldType {
559 kind: query::TypeKind::Scalar,
560 name: Some("Int".into()),
561 of_type: None,
562 })),
563 })),
564 })
565 .unwrap();
566
567 assert_eq!(ty.to_string(), "[Int]!");
568
569 let ty = FieldType::try_from(query::FieldType {
570 kind: query::TypeKind::Object,
571 name: Some("MyObject".into()),
572 of_type: None,
573 })
574 .unwrap();
575
576 assert_eq!(ty.to_string(), "MyObject");
577 }
578}