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