use crate::ast::InvalidNameError;
use crate::ast::Name;
use crate::schema::NamedType;
use std::fmt;
use std::str::FromStr;
#[macro_export]
macro_rules! coord {
( @ $name:ident ) => {
$crate::coordinate::DirectiveCoordinate {
directive: $crate::name!($name),
}
};
( @ $name:ident ( $arg:ident : ) ) => {
$crate::coordinate::DirectiveArgumentCoordinate {
directive: $crate::name!($name),
argument: $crate::name!($arg),
}
};
( $name:ident ) => {
$crate::coordinate::TypeCoordinate {
ty: $crate::name!($name),
}
};
( $name:ident . $attribute:ident ) => {
$crate::coordinate::TypeAttributeCoordinate {
ty: $crate::name!($name),
attribute: $crate::name!($attribute),
}
};
( $name:ident . $field:ident ( $arg:ident : ) ) => {
$crate::coordinate::FieldArgumentCoordinate {
ty: $crate::name!($name),
field: $crate::name!($field),
argument: $crate::name!($arg),
}
};
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TypeCoordinate {
pub ty: NamedType,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TypeAttributeCoordinate {
pub ty: NamedType,
pub attribute: Name,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FieldArgumentCoordinate {
pub ty: NamedType,
pub field: Name,
pub argument: Name,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DirectiveCoordinate {
pub directive: Name,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DirectiveArgumentCoordinate {
pub directive: Name,
pub argument: Name,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SchemaCoordinate {
Type(TypeCoordinate),
TypeAttribute(TypeAttributeCoordinate),
FieldArgument(FieldArgumentCoordinate),
Directive(DirectiveCoordinate),
DirectiveArgument(DirectiveArgumentCoordinate),
}
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum SchemaCoordinateParseError {
#[error("invalid schema coordinate")]
InvalidFormat,
#[error(transparent)]
InvalidName(#[from] InvalidNameError),
}
impl TypeCoordinate {
pub fn with_attribute(&self, attribute: Name) -> TypeAttributeCoordinate {
TypeAttributeCoordinate {
ty: self.ty.clone(),
attribute,
}
}
}
impl From<NamedType> for TypeCoordinate {
fn from(ty: NamedType) -> Self {
Self { ty }
}
}
impl FromStr for TypeCoordinate {
type Err = SchemaCoordinateParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Ok(Self {
ty: NamedType::try_from(input)?,
})
}
}
impl TypeAttributeCoordinate {
pub fn type_coordinate(&self) -> TypeCoordinate {
TypeCoordinate {
ty: self.ty.clone(),
}
}
pub fn with_argument(&self, argument: Name) -> FieldArgumentCoordinate {
FieldArgumentCoordinate {
ty: self.ty.clone(),
field: self.attribute.clone(),
argument,
}
}
}
impl FromStr for TypeAttributeCoordinate {
type Err = SchemaCoordinateParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let Some((type_name, field)) = input.split_once('.') else {
return Err(SchemaCoordinateParseError::InvalidFormat);
};
Ok(Self {
ty: NamedType::try_from(type_name)?,
attribute: Name::try_from(field)?,
})
}
}
impl FieldArgumentCoordinate {
pub fn type_coordinate(&self) -> TypeCoordinate {
TypeCoordinate {
ty: self.ty.clone(),
}
}
pub fn field_coordinate(&self) -> TypeAttributeCoordinate {
TypeAttributeCoordinate {
ty: self.ty.clone(),
attribute: self.field.clone(),
}
}
}
impl FromStr for FieldArgumentCoordinate {
type Err = SchemaCoordinateParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let Some((field, rest)) = input.split_once('(') else {
return Err(SchemaCoordinateParseError::InvalidFormat);
};
let field = TypeAttributeCoordinate::from_str(field)?;
let Some((argument, ")")) = rest.split_once(':') else {
return Err(SchemaCoordinateParseError::InvalidFormat);
};
Ok(Self {
ty: field.ty,
field: field.attribute,
argument: Name::try_from(argument)?,
})
}
}
impl DirectiveCoordinate {
pub fn with_argument(&self, argument: Name) -> DirectiveArgumentCoordinate {
DirectiveArgumentCoordinate {
directive: self.directive.clone(),
argument,
}
}
}
impl From<Name> for DirectiveCoordinate {
fn from(directive: Name) -> Self {
Self { directive }
}
}
impl FromStr for DirectiveCoordinate {
type Err = SchemaCoordinateParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
if let Some(directive) = input.strip_prefix('@') {
Ok(Self {
directive: Name::try_from(directive)?,
})
} else {
Err(SchemaCoordinateParseError::InvalidFormat)
}
}
}
impl DirectiveArgumentCoordinate {
pub fn directive_coordinate(&self) -> DirectiveCoordinate {
DirectiveCoordinate {
directive: self.directive.clone(),
}
}
}
impl FromStr for DirectiveArgumentCoordinate {
type Err = SchemaCoordinateParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
let Some((directive, rest)) = input.split_once('(') else {
return Err(SchemaCoordinateParseError::InvalidFormat);
};
let directive = DirectiveCoordinate::from_str(directive)?;
let Some((argument, ")")) = rest.split_once(':') else {
return Err(SchemaCoordinateParseError::InvalidFormat);
};
Ok(Self {
directive: directive.directive,
argument: Name::try_from(argument)?,
})
}
}
impl FromStr for SchemaCoordinate {
type Err = SchemaCoordinateParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
if input.starts_with('@') {
DirectiveArgumentCoordinate::from_str(input)
.map(Self::DirectiveArgument)
.or_else(|_| DirectiveCoordinate::from_str(input).map(Self::Directive))
} else {
FieldArgumentCoordinate::from_str(input)
.map(Self::FieldArgument)
.or_else(|_| TypeAttributeCoordinate::from_str(input).map(Self::TypeAttribute))
.or_else(|_| TypeCoordinate::from_str(input).map(Self::Type))
}
}
}
impl From<TypeCoordinate> for SchemaCoordinate {
fn from(inner: TypeCoordinate) -> Self {
Self::Type(inner)
}
}
impl From<TypeAttributeCoordinate> for SchemaCoordinate {
fn from(inner: TypeAttributeCoordinate) -> Self {
Self::TypeAttribute(inner)
}
}
impl From<FieldArgumentCoordinate> for SchemaCoordinate {
fn from(inner: FieldArgumentCoordinate) -> Self {
Self::FieldArgument(inner)
}
}
impl From<DirectiveCoordinate> for SchemaCoordinate {
fn from(inner: DirectiveCoordinate) -> Self {
Self::Directive(inner)
}
}
impl From<DirectiveArgumentCoordinate> for SchemaCoordinate {
fn from(inner: DirectiveArgumentCoordinate) -> Self {
Self::DirectiveArgument(inner)
}
}
impl fmt::Display for TypeCoordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { ty } = self;
write!(f, "{ty}")
}
}
impl fmt::Display for TypeAttributeCoordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
ty,
attribute: field,
} = self;
write!(f, "{ty}.{field}")
}
}
impl fmt::Display for FieldArgumentCoordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
ty,
field,
argument,
} = self;
write!(f, "{ty}.{field}({argument}:)")
}
}
impl fmt::Display for DirectiveCoordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { directive } = self;
write!(f, "@{directive}")
}
}
impl fmt::Display for DirectiveArgumentCoordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
directive,
argument,
} = self;
write!(f, "@{directive}({argument}:)")
}
}
impl fmt::Display for SchemaCoordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Type(inner) => inner.fmt(f),
Self::TypeAttribute(inner) => inner.fmt(f),
Self::FieldArgument(inner) => inner.fmt(f),
Self::Directive(inner) => inner.fmt(f),
Self::DirectiveArgument(inner) => inner.fmt(f),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalid_coordinates() {
SchemaCoordinate::from_str("Type\\.field(arg:)").expect_err("invalid character");
SchemaCoordinate::from_str("@directi^^ve").expect_err("invalid character");
SchemaCoordinate::from_str("@directi@ve").expect_err("invalid character");
SchemaCoordinate::from_str("@ spaces ").expect_err("invalid character");
SchemaCoordinate::from_str("@(:)").expect_err("directive argument syntax without names");
SchemaCoordinate::from_str("@dir(:)")
.expect_err("directive argument syntax without argument name");
SchemaCoordinate::from_str("@(arg:)")
.expect_err("directive argument syntax without directive name");
SchemaCoordinate::from_str("Type.")
.expect_err("type attribute syntax without attribute name");
SchemaCoordinate::from_str(".field").expect_err("type attribute syntax without type name");
SchemaCoordinate::from_str("Type.field(:)")
.expect_err("field argument syntax without field name");
SchemaCoordinate::from_str("Type.field(arg)").expect_err("field argument syntax without :");
}
}