Skip to main content

apollo_compiler/
coordinate.rs

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