apollo_compiler/
coordinate.rs

1//! Parsing and printing for schema coordinates as described in [the RFC].
2//!
3//! Schema coordinates uniquely point to an item defined in a schema.
4//!
5//! [the RFC]: https://github.com/graphql/graphql-wg/blob/main/rfcs/SchemaCoordinates.md
6
7use crate::schema::Component;
8use crate::schema::DirectiveDefinition;
9use crate::schema::EnumValueDefinition;
10use crate::schema::ExtendedType;
11use crate::schema::FieldDefinition;
12use crate::schema::InputValueDefinition;
13use crate::schema::NamedType;
14use crate::schema::Schema;
15use crate::InvalidNameError;
16use crate::Name;
17use crate::Node;
18use std::fmt;
19use std::str::FromStr;
20
21/// Create a [`DirectiveCoordinate`], [`DirectiveArgumentCoordinate`],
22/// [`TypeCoordinate`], [`TypeAttributeCoordinate`],
23/// or [`FieldArgumentCoordinate`] at compile time.
24///
25/// ```rust
26/// use apollo_compiler::coord;
27///
28/// assert_eq!(coord!(@directive).to_string(), "@directive");
29/// assert_eq!(coord!(@directive(arg:)).to_string(), "@directive(arg:)");
30/// assert_eq!(coord!(Type).to_string(), "Type");
31/// assert_eq!(coord!(Type.field).to_string(), "Type.field");
32/// assert_eq!(coord!(Type.field(arg:)).to_string(), "Type.field(arg:)");
33/// assert_eq!(coord!(EnumType.ENUM_VALUE).to_string(), "EnumType.ENUM_VALUE");
34/// ```
35///
36/// All possible return types of this macro implement
37/// [`From`]`<`[`SchemaCoordinate`]`>` so they can be converted using `.into()`:
38///
39/// ```
40/// use apollo_compiler::coord;
41/// use apollo_compiler::coordinate::SchemaCoordinate;
42///
43/// let _: SchemaCoordinate = coord!(Query).into();
44/// ```
45#[macro_export]
46macro_rules! coord {
47    ( @ $name:ident ) => {
48        $crate::coordinate::DirectiveCoordinate {
49            directive: $crate::name!($name),
50        }
51    };
52    ( @ $name:ident ( $arg:ident : ) ) => {
53        $crate::coordinate::DirectiveArgumentCoordinate {
54            directive: $crate::name!($name),
55            argument: $crate::name!($arg),
56        }
57    };
58    ( $name:ident ) => {
59        $crate::coordinate::TypeCoordinate {
60            ty: $crate::name!($name),
61        }
62    };
63    ( $name:ident . $attribute:ident ) => {
64        $crate::coordinate::TypeAttributeCoordinate {
65            ty: $crate::name!($name),
66            attribute: $crate::name!($attribute),
67        }
68    };
69    ( $name:ident . $field:ident ( $arg:ident : ) ) => {
70        $crate::coordinate::FieldArgumentCoordinate {
71            ty: $crate::name!($name),
72            field: $crate::name!($field),
73            argument: $crate::name!($arg),
74        }
75    };
76}
77
78/// A schema coordinate targeting a type definition: `Type`.
79///
80/// # Example
81/// ```
82/// use apollo_compiler::name;
83/// use apollo_compiler::coordinate::TypeCoordinate;
84///
85/// assert_eq!(TypeCoordinate { ty: name!("Type") }.to_string(), "Type");
86/// ```
87#[derive(Debug, Clone, PartialEq, Eq, Hash)]
88pub struct TypeCoordinate {
89    pub ty: NamedType,
90}
91
92/// A schema coordinate targeting a field definition or an enum value: `Type.field`, `Enum.VALUE`.
93///
94/// Type attribute coordinate syntax can refer to object or interface field definitions, input
95/// field definitions, and enum values. [`TypeAttributeCoordinate::lookup`] returns an enum to
96/// account for those possibilities. To look up a specific kind of type attribute, there are
97/// convenience methods:
98/// - [`TypeAttributeCoordinate::lookup_field`] for object or interface fields
99/// - [`TypeAttributeCoordinate::lookup_input_field`] for input fields
100/// - [`TypeAttributeCoordinate::lookup_enum_value`] for enum values
101///
102/// # Example
103/// ```
104/// use apollo_compiler::name;
105/// use apollo_compiler::coordinate::TypeAttributeCoordinate;
106///
107/// assert_eq!(TypeAttributeCoordinate {
108///     ty: name!("Type"),
109///     attribute: name!("field"),
110/// }.to_string(), "Type.field");
111/// ```
112#[derive(Debug, Clone, PartialEq, Eq, Hash)]
113pub struct TypeAttributeCoordinate {
114    pub ty: NamedType,
115    pub attribute: Name,
116}
117
118/// A schema coordinate targeting a field argument definition: `Type.field(argument:)`.
119///
120/// # Example
121/// ```
122/// use apollo_compiler::name;
123/// use apollo_compiler::coordinate::FieldArgumentCoordinate;
124///
125/// assert_eq!(FieldArgumentCoordinate {
126///     ty: name!("Type"),
127///     field: name!("field"),
128///     argument: name!("argument"),
129/// }.to_string(), "Type.field(argument:)");
130/// ```
131#[derive(Debug, Clone, PartialEq, Eq, Hash)]
132pub struct FieldArgumentCoordinate {
133    pub ty: NamedType,
134    pub field: Name,
135    pub argument: Name,
136}
137
138/// A schema coordinate targeting a directive definition: `@directive`.
139#[derive(Debug, Clone, PartialEq, Eq, Hash)]
140pub struct DirectiveCoordinate {
141    pub directive: Name,
142}
143
144/// A schema coordinate targeting a directive argument definition: `@directive(argument:)`.
145#[derive(Debug, Clone, PartialEq, Eq, Hash)]
146pub struct DirectiveArgumentCoordinate {
147    pub directive: Name,
148    pub argument: Name,
149}
150
151/// Any schema coordinate.
152///
153/// # Example
154/// ```
155/// use apollo_compiler::name;
156/// use apollo_compiler::coordinate::{SchemaCoordinate, FieldArgumentCoordinate};
157///
158/// let coord: SchemaCoordinate = "Type.field(argument:)".parse().unwrap();
159/// assert_eq!(coord, SchemaCoordinate::FieldArgument(
160///     FieldArgumentCoordinate {
161///         ty: name!("Type"),
162///         field: name!("field"),
163///         argument: name!("argument"),
164///     },
165/// ));
166/// ```
167#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub enum SchemaCoordinate {
169    Type(TypeCoordinate),
170    TypeAttribute(TypeAttributeCoordinate),
171    FieldArgument(FieldArgumentCoordinate),
172    Directive(DirectiveCoordinate),
173    DirectiveArgument(DirectiveArgumentCoordinate),
174}
175
176/// Errors that can occur while parsing a schema coordinate.
177#[derive(Debug, Clone, thiserror::Error)]
178#[non_exhaustive]
179pub enum SchemaCoordinateParseError {
180    /// Invalid format, eg. unexpected characters
181    #[error("invalid schema coordinate")]
182    InvalidFormat,
183    /// A name part contains invalid characters
184    #[error(transparent)]
185    InvalidName(#[from] InvalidNameError),
186}
187
188/// The error type for [`SchemaCoordinate::lookup`] and other `lookup*` methods.
189#[derive(Debug, thiserror::Error)]
190#[non_exhaustive]
191pub enum SchemaLookupError<'coord, 'schema> {
192    /// The requested type does not exist in the schema.
193    #[error("type `{0}` does not exist")]
194    MissingType(&'coord NamedType),
195    /// The requested field or enum value does not exist on its type.
196    #[error("type does not have attribute `{0}`")]
197    MissingAttribute(&'coord Name),
198    /// The requested argument can not be looked up because its type does not support arguments.
199    #[error("type attribute `{0}` is not a field and can not have arguments")]
200    InvalidArgumentAttribute(&'coord Name),
201    /// The requested argument does not exist on its field or directive.
202    #[error("field or directive does not have argument `{0}`")]
203    MissingArgument(&'coord Name),
204    /// The requested field or enum value can not be looked up because its type does not support
205    /// fields.
206    #[error("type does not have attributes")]
207    InvalidType(&'schema ExtendedType),
208}
209
210/// Return type of [`TypeAttributeCoordinate::lookup`], for coordinates of the form `Type.field`.
211#[derive(Debug, Clone, PartialEq, Eq)]
212// Should this be non-exhaustive? Allows for future extension should unions ever be added.
213#[non_exhaustive]
214pub enum TypeAttributeLookup<'schema> {
215    Field(&'schema Component<FieldDefinition>),
216    InputField(&'schema Component<InputValueDefinition>),
217    EnumValue(&'schema Component<EnumValueDefinition>),
218}
219
220/// Return type of [`SchemaCoordinate::lookup`].
221#[derive(Debug, Clone, PartialEq, Eq)]
222#[non_exhaustive]
223pub enum SchemaCoordinateLookup<'schema> {
224    Type(&'schema ExtendedType),
225    Directive(&'schema Node<DirectiveDefinition>),
226    Field(&'schema Component<FieldDefinition>),
227    InputField(&'schema Component<InputValueDefinition>),
228    EnumValue(&'schema Component<EnumValueDefinition>),
229    Argument(&'schema Node<InputValueDefinition>),
230}
231
232impl TypeCoordinate {
233    /// Create a schema coordinate that points to an attribute on this type.
234    ///
235    /// For object types and interfaces, the resulting coordinate points to a field. For enums, the
236    /// resulting coordinate points to a value.
237    pub fn with_attribute(&self, attribute: Name) -> TypeAttributeCoordinate {
238        TypeAttributeCoordinate {
239            ty: self.ty.clone(),
240            attribute,
241        }
242    }
243
244    fn lookup_ref<'coord, 'schema>(
245        ty: &'coord NamedType,
246        schema: &'schema Schema,
247    ) -> Result<&'schema ExtendedType, SchemaLookupError<'coord, 'schema>> {
248        schema
249            .types
250            .get(ty)
251            .ok_or(SchemaLookupError::MissingType(ty))
252    }
253
254    /// Look up this type coordinate in a schema.
255    pub fn lookup<'coord, 'schema>(
256        &'coord self,
257        schema: &'schema Schema,
258    ) -> Result<&'schema ExtendedType, SchemaLookupError<'coord, 'schema>> {
259        Self::lookup_ref(&self.ty, schema)
260    }
261}
262
263impl FromStr for TypeCoordinate {
264    type Err = SchemaCoordinateParseError;
265    fn from_str(input: &str) -> Result<Self, Self::Err> {
266        Ok(Self {
267            ty: NamedType::try_from(input)?,
268        })
269    }
270}
271
272impl TypeAttributeCoordinate {
273    /// Create a schema coordinate that points to the type this attribute is part of.
274    pub fn type_coordinate(&self) -> TypeCoordinate {
275        TypeCoordinate {
276            ty: self.ty.clone(),
277        }
278    }
279
280    /// Assume this attribute is a field, and create a schema coordinate that points to an argument on this field.
281    pub fn with_argument(&self, argument: Name) -> FieldArgumentCoordinate {
282        FieldArgumentCoordinate {
283            ty: self.ty.clone(),
284            field: self.attribute.clone(),
285            argument,
286        }
287    }
288
289    fn lookup_ref<'coord, 'schema>(
290        ty: &'coord NamedType,
291        attribute: &'coord Name,
292        schema: &'schema Schema,
293    ) -> Result<TypeAttributeLookup<'schema>, SchemaLookupError<'coord, 'schema>> {
294        let ty = TypeCoordinate::lookup_ref(ty, schema)?;
295        match ty {
296            ExtendedType::Enum(enum_) => enum_
297                .values
298                .get(attribute)
299                .ok_or(SchemaLookupError::MissingAttribute(attribute))
300                .map(TypeAttributeLookup::EnumValue),
301            ExtendedType::InputObject(input_object) => input_object
302                .fields
303                .get(attribute)
304                .ok_or(SchemaLookupError::MissingAttribute(attribute))
305                .map(TypeAttributeLookup::InputField),
306            ExtendedType::Object(object) => object
307                .fields
308                .get(attribute)
309                .ok_or(SchemaLookupError::MissingAttribute(attribute))
310                .map(TypeAttributeLookup::Field),
311            ExtendedType::Interface(interface) => interface
312                .fields
313                .get(attribute)
314                .ok_or(SchemaLookupError::MissingAttribute(attribute))
315                .map(TypeAttributeLookup::Field),
316            ExtendedType::Union(_) | ExtendedType::Scalar(_) => {
317                Err(SchemaLookupError::InvalidType(ty))
318            }
319        }
320    }
321
322    /// Look up this type attribute in a schema.
323    pub fn lookup<'coord, 'schema>(
324        &'coord self,
325        schema: &'schema Schema,
326    ) -> Result<TypeAttributeLookup<'schema>, SchemaLookupError<'coord, 'schema>> {
327        Self::lookup_ref(&self.ty, &self.attribute, schema)
328    }
329
330    /// Look up this field definition in a schema. If the attribute does not refer to an object or
331    /// interface field, returns `SchemaLookupError::InvalidType`.
332    pub fn lookup_field<'coord, 'schema>(
333        &'coord self,
334        schema: &'schema Schema,
335    ) -> Result<&'schema Component<FieldDefinition>, SchemaLookupError<'coord, 'schema>> {
336        let ty = TypeCoordinate::lookup_ref(&self.ty, schema)?;
337        match ty {
338            ExtendedType::Object(object) => object
339                .fields
340                .get(&self.attribute)
341                .ok_or(SchemaLookupError::MissingAttribute(&self.attribute)),
342            ExtendedType::Interface(interface) => interface
343                .fields
344                .get(&self.attribute)
345                .ok_or(SchemaLookupError::MissingAttribute(&self.attribute)),
346            _ => Err(SchemaLookupError::InvalidType(ty)),
347        }
348    }
349
350    /// Look up this input field definition in a schema. If the attribute does not refer to an
351    /// input field, returns `SchemaLookupError::InvalidType`.
352    pub fn lookup_input_field<'coord, 'schema>(
353        &'coord self,
354        schema: &'schema Schema,
355    ) -> Result<&'schema Component<InputValueDefinition>, SchemaLookupError<'coord, 'schema>> {
356        let ty = TypeCoordinate::lookup_ref(&self.ty, schema)?;
357        match ty {
358            ExtendedType::InputObject(object) => object
359                .fields
360                .get(&self.attribute)
361                .ok_or(SchemaLookupError::MissingAttribute(&self.attribute)),
362            _ => Err(SchemaLookupError::InvalidType(ty)),
363        }
364    }
365
366    /// Look up this enum value definition in a schema. If the attribute does not refer to an
367    /// enum, returns `SchemaLookupError::InvalidType`.
368    pub fn lookup_enum_value<'coord, 'schema>(
369        &'coord self,
370        schema: &'schema Schema,
371    ) -> Result<&'schema Component<EnumValueDefinition>, SchemaLookupError<'coord, 'schema>> {
372        let ty = TypeCoordinate::lookup_ref(&self.ty, schema)?;
373        match ty {
374            ExtendedType::Enum(enum_) => enum_
375                .values
376                .get(&self.attribute)
377                .ok_or(SchemaLookupError::MissingAttribute(&self.attribute)),
378            _ => Err(SchemaLookupError::InvalidType(ty)),
379        }
380    }
381}
382
383impl FromStr for TypeAttributeCoordinate {
384    type Err = SchemaCoordinateParseError;
385    fn from_str(input: &str) -> Result<Self, Self::Err> {
386        let Some((type_name, field)) = input.split_once('.') else {
387            return Err(SchemaCoordinateParseError::InvalidFormat);
388        };
389        Ok(Self {
390            ty: NamedType::try_from(type_name)?,
391            attribute: Name::try_from(field)?,
392        })
393    }
394}
395
396impl FieldArgumentCoordinate {
397    /// Create a schema coordinate that points to the type this argument is defined in.
398    pub fn type_coordinate(&self) -> TypeCoordinate {
399        TypeCoordinate {
400            ty: self.ty.clone(),
401        }
402    }
403
404    /// Create a schema coordinate that points to the field this argument is defined in.
405    pub fn field_coordinate(&self) -> TypeAttributeCoordinate {
406        TypeAttributeCoordinate {
407            ty: self.ty.clone(),
408            attribute: self.field.clone(),
409        }
410    }
411
412    fn lookup_ref<'coord, 'schema>(
413        ty: &'coord NamedType,
414        field: &'coord Name,
415        argument: &'coord Name,
416        schema: &'schema Schema,
417    ) -> Result<&'schema Node<InputValueDefinition>, SchemaLookupError<'coord, 'schema>> {
418        match TypeAttributeCoordinate::lookup_ref(ty, field, schema)? {
419            TypeAttributeLookup::Field(field) => field
420                .argument_by_name(argument)
421                .ok_or(SchemaLookupError::MissingArgument(argument)),
422            _ => Err(SchemaLookupError::InvalidArgumentAttribute(field)),
423        }
424    }
425
426    /// Look up this argument definition in a schema.
427    pub fn lookup<'coord, 'schema>(
428        &'coord self,
429        schema: &'schema Schema,
430    ) -> Result<&'schema Node<InputValueDefinition>, SchemaLookupError<'coord, 'schema>> {
431        Self::lookup_ref(&self.ty, &self.field, &self.argument, schema)
432    }
433}
434
435impl FromStr for FieldArgumentCoordinate {
436    type Err = SchemaCoordinateParseError;
437    fn from_str(input: &str) -> Result<Self, Self::Err> {
438        let Some((field, rest)) = input.split_once('(') else {
439            return Err(SchemaCoordinateParseError::InvalidFormat);
440        };
441        let field = TypeAttributeCoordinate::from_str(field)?;
442
443        let Some((argument, ")")) = rest.split_once(':') else {
444            return Err(SchemaCoordinateParseError::InvalidFormat);
445        };
446        Ok(Self {
447            ty: field.ty,
448            field: field.attribute,
449            argument: Name::try_from(argument)?,
450        })
451    }
452}
453
454impl DirectiveCoordinate {
455    /// Create a schema coordinate that points to an argument of this directive.
456    pub fn with_argument(&self, argument: Name) -> DirectiveArgumentCoordinate {
457        DirectiveArgumentCoordinate {
458            directive: self.directive.clone(),
459            argument,
460        }
461    }
462
463    fn lookup_ref<'coord, 'schema>(
464        directive: &'coord Name,
465        schema: &'schema Schema,
466    ) -> Result<&'schema Node<DirectiveDefinition>, SchemaLookupError<'coord, 'schema>> {
467        schema
468            .directive_definitions
469            .get(directive)
470            .ok_or(SchemaLookupError::MissingType(directive))
471    }
472
473    /// Look up this directive in a schema.
474    pub fn lookup<'coord, 'schema>(
475        &'coord self,
476        schema: &'schema Schema,
477    ) -> Result<&'schema Node<DirectiveDefinition>, SchemaLookupError<'coord, 'schema>> {
478        Self::lookup_ref(&self.directive, schema)
479    }
480}
481
482impl From<Name> for DirectiveCoordinate {
483    fn from(directive: Name) -> Self {
484        Self { directive }
485    }
486}
487
488impl FromStr for DirectiveCoordinate {
489    type Err = SchemaCoordinateParseError;
490    fn from_str(input: &str) -> Result<Self, Self::Err> {
491        if let Some(directive) = input.strip_prefix('@') {
492            Ok(Self {
493                directive: Name::try_from(directive)?,
494            })
495        } else {
496            Err(SchemaCoordinateParseError::InvalidFormat)
497        }
498    }
499}
500
501impl DirectiveArgumentCoordinate {
502    /// Create a schema coordinate that points to the directive this argument is defined in.
503    pub fn directive_coordinate(&self) -> DirectiveCoordinate {
504        DirectiveCoordinate {
505            directive: self.directive.clone(),
506        }
507    }
508
509    fn lookup_ref<'coord, 'schema>(
510        directive: &'coord Name,
511        argument: &'coord Name,
512        schema: &'schema Schema,
513    ) -> Result<&'schema Node<InputValueDefinition>, SchemaLookupError<'coord, 'schema>> {
514        DirectiveCoordinate::lookup_ref(directive, schema)?
515            .argument_by_name(argument)
516            .ok_or(SchemaLookupError::MissingArgument(argument))
517    }
518
519    /// Look up this directive argument in a schema.
520    pub fn lookup<'coord, 'schema>(
521        &'coord self,
522        schema: &'schema Schema,
523    ) -> Result<&'schema Node<InputValueDefinition>, SchemaLookupError<'coord, 'schema>> {
524        Self::lookup_ref(&self.directive, &self.argument, schema)
525    }
526}
527
528impl FromStr for DirectiveArgumentCoordinate {
529    type Err = SchemaCoordinateParseError;
530    fn from_str(input: &str) -> Result<Self, Self::Err> {
531        let Some((directive, rest)) = input.split_once('(') else {
532            return Err(SchemaCoordinateParseError::InvalidFormat);
533        };
534        let directive = DirectiveCoordinate::from_str(directive)?;
535
536        let Some((argument, ")")) = rest.split_once(':') else {
537            return Err(SchemaCoordinateParseError::InvalidFormat);
538        };
539        Ok(Self {
540            directive: directive.directive,
541            argument: Name::try_from(argument)?,
542        })
543    }
544}
545
546impl<'schema> From<&'schema ExtendedType> for SchemaCoordinateLookup<'schema> {
547    fn from(inner: &'schema ExtendedType) -> Self {
548        Self::Type(inner)
549    }
550}
551
552impl<'schema> From<&'schema Node<DirectiveDefinition>> for SchemaCoordinateLookup<'schema> {
553    fn from(inner: &'schema Node<DirectiveDefinition>) -> Self {
554        Self::Directive(inner)
555    }
556}
557
558impl<'schema> From<&'schema Component<FieldDefinition>> for SchemaCoordinateLookup<'schema> {
559    fn from(inner: &'schema Component<FieldDefinition>) -> Self {
560        Self::Field(inner)
561    }
562}
563
564impl<'schema> From<&'schema Component<InputValueDefinition>> for SchemaCoordinateLookup<'schema> {
565    fn from(inner: &'schema Component<InputValueDefinition>) -> Self {
566        Self::InputField(inner)
567    }
568}
569
570impl<'schema> From<&'schema Component<EnumValueDefinition>> for SchemaCoordinateLookup<'schema> {
571    fn from(inner: &'schema Component<EnumValueDefinition>) -> Self {
572        Self::EnumValue(inner)
573    }
574}
575
576impl<'schema> From<TypeAttributeLookup<'schema>> for SchemaCoordinateLookup<'schema> {
577    fn from(attr: TypeAttributeLookup<'schema>) -> Self {
578        match attr {
579            TypeAttributeLookup::Field(field) => SchemaCoordinateLookup::Field(field),
580            TypeAttributeLookup::InputField(field) => SchemaCoordinateLookup::InputField(field),
581            TypeAttributeLookup::EnumValue(field) => SchemaCoordinateLookup::EnumValue(field),
582        }
583    }
584}
585
586impl<'schema> From<&'schema Node<InputValueDefinition>> for SchemaCoordinateLookup<'schema> {
587    fn from(inner: &'schema Node<InputValueDefinition>) -> Self {
588        Self::Argument(inner)
589    }
590}
591
592impl SchemaCoordinate {
593    /// Look up this coordinate in a schema.
594    pub fn lookup<'coord, 'schema>(
595        &'coord self,
596        schema: &'schema Schema,
597    ) -> Result<SchemaCoordinateLookup<'schema>, SchemaLookupError<'coord, 'schema>> {
598        match self {
599            SchemaCoordinate::Type(coordinate) => coordinate.lookup(schema).map(Into::into),
600            SchemaCoordinate::TypeAttribute(coordinate) => {
601                coordinate.lookup(schema).map(Into::into)
602            }
603            SchemaCoordinate::FieldArgument(coordinate) => {
604                coordinate.lookup(schema).map(Into::into)
605            }
606            SchemaCoordinate::Directive(coordinate) => coordinate.lookup(schema).map(Into::into),
607            SchemaCoordinate::DirectiveArgument(coordinate) => {
608                coordinate.lookup(schema).map(Into::into)
609            }
610        }
611    }
612}
613
614impl FromStr for SchemaCoordinate {
615    type Err = SchemaCoordinateParseError;
616    fn from_str(input: &str) -> Result<Self, Self::Err> {
617        if input.starts_with('@') {
618            DirectiveArgumentCoordinate::from_str(input)
619                .map(Self::DirectiveArgument)
620                .or_else(|_| DirectiveCoordinate::from_str(input).map(Self::Directive))
621        } else {
622            FieldArgumentCoordinate::from_str(input)
623                .map(Self::FieldArgument)
624                .or_else(|_| TypeAttributeCoordinate::from_str(input).map(Self::TypeAttribute))
625                .or_else(|_| TypeCoordinate::from_str(input).map(Self::Type))
626        }
627    }
628}
629
630impl From<TypeCoordinate> for SchemaCoordinate {
631    fn from(inner: TypeCoordinate) -> Self {
632        Self::Type(inner)
633    }
634}
635
636impl From<TypeAttributeCoordinate> for SchemaCoordinate {
637    fn from(inner: TypeAttributeCoordinate) -> Self {
638        Self::TypeAttribute(inner)
639    }
640}
641
642impl From<FieldArgumentCoordinate> for SchemaCoordinate {
643    fn from(inner: FieldArgumentCoordinate) -> Self {
644        Self::FieldArgument(inner)
645    }
646}
647
648impl From<DirectiveCoordinate> for SchemaCoordinate {
649    fn from(inner: DirectiveCoordinate) -> Self {
650        Self::Directive(inner)
651    }
652}
653
654impl From<DirectiveArgumentCoordinate> for SchemaCoordinate {
655    fn from(inner: DirectiveArgumentCoordinate) -> Self {
656        Self::DirectiveArgument(inner)
657    }
658}
659
660impl fmt::Display for TypeCoordinate {
661    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
662        let Self { ty } = self;
663        write!(f, "{ty}")
664    }
665}
666
667impl fmt::Display for TypeAttributeCoordinate {
668    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669        let Self {
670            ty,
671            attribute: field,
672        } = self;
673        write!(f, "{ty}.{field}")
674    }
675}
676
677impl fmt::Display for FieldArgumentCoordinate {
678    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
679        let Self {
680            ty,
681            field,
682            argument,
683        } = self;
684        write!(f, "{ty}.{field}({argument}:)")
685    }
686}
687
688impl fmt::Display for DirectiveCoordinate {
689    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
690        let Self { directive } = self;
691        write!(f, "@{directive}")
692    }
693}
694
695impl fmt::Display for DirectiveArgumentCoordinate {
696    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
697        let Self {
698            directive,
699            argument,
700        } = self;
701        write!(f, "@{directive}({argument}:)")
702    }
703}
704
705impl fmt::Display for SchemaCoordinate {
706    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
707        match self {
708            Self::Type(inner) => inner.fmt(f),
709            Self::TypeAttribute(inner) => inner.fmt(f),
710            Self::FieldArgument(inner) => inner.fmt(f),
711            Self::Directive(inner) => inner.fmt(f),
712            Self::DirectiveArgument(inner) => inner.fmt(f),
713        }
714    }
715}
716
717#[cfg(test)]
718mod tests {
719    use super::*;
720
721    #[test]
722    fn invalid_coordinates() {
723        SchemaCoordinate::from_str("Type\\.field(arg:)").expect_err("invalid character");
724        SchemaCoordinate::from_str("@directi^^ve").expect_err("invalid character");
725        SchemaCoordinate::from_str("@directi@ve").expect_err("invalid character");
726        SchemaCoordinate::from_str("@  spaces  ").expect_err("invalid character");
727
728        SchemaCoordinate::from_str("@(:)").expect_err("directive argument syntax without names");
729        SchemaCoordinate::from_str("@dir(:)")
730            .expect_err("directive argument syntax without argument name");
731        SchemaCoordinate::from_str("@(arg:)")
732            .expect_err("directive argument syntax without directive name");
733
734        SchemaCoordinate::from_str("Type.")
735            .expect_err("type attribute syntax without attribute name");
736        SchemaCoordinate::from_str(".field").expect_err("type attribute syntax without type name");
737        SchemaCoordinate::from_str("Type.field(:)")
738            .expect_err("field argument syntax without field name");
739        SchemaCoordinate::from_str("Type.field(arg)").expect_err("field argument syntax without :");
740    }
741}