1use 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#[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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
89pub struct TypeCoordinate {
90 pub ty: NamedType,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash)]
114pub struct TypeAttributeCoordinate {
115 pub ty: NamedType,
116 pub attribute: Name,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Hash)]
133pub struct FieldArgumentCoordinate {
134 pub ty: NamedType,
135 pub field: Name,
136 pub argument: Name,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Hash)]
141pub struct DirectiveCoordinate {
142 pub directive: Name,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Hash)]
147pub struct DirectiveArgumentCoordinate {
148 pub directive: Name,
149 pub argument: Name,
150}
151
152#[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#[derive(Debug, Clone, thiserror::Error)]
179#[non_exhaustive]
180pub enum SchemaCoordinateParseError {
181 #[error("invalid schema coordinate")]
183 InvalidFormat,
184 #[error(transparent)]
186 InvalidName(#[from] InvalidNameError),
187}
188
189#[derive(Debug, thiserror::Error)]
191#[non_exhaustive]
192pub enum SchemaLookupError<'coord, 'schema> {
193 #[error("type `{0}` does not exist")]
195 MissingType(&'coord NamedType),
196 #[error("type does not have attribute `{0}`")]
198 MissingAttribute(&'coord Name),
199 #[error("type attribute `{0}` is not a field and can not have arguments")]
201 InvalidArgumentAttribute(&'coord Name),
202 #[error("field or directive does not have argument `{0}`")]
204 MissingArgument(&'coord Name),
205 #[error("type does not have attributes")]
208 InvalidType(&'schema ExtendedType),
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
213#[non_exhaustive]
215pub enum TypeAttributeLookup<'schema> {
216 Field(&'schema Component<FieldDefinition>),
217 InputField(&'schema Component<InputValueDefinition>),
218 EnumValue(&'schema Component<EnumValueDefinition>),
219}
220
221#[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 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 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 pub fn type_coordinate(&self) -> TypeCoordinate {
276 TypeCoordinate {
277 ty: self.ty.clone(),
278 }
279 }
280
281 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 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 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 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 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 pub fn type_coordinate(&self) -> TypeCoordinate {
400 TypeCoordinate {
401 ty: self.ty.clone(),
402 }
403 }
404
405 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 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 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 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 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 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 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}