bluejay_parser/ast/definition/
directive_definition.rs

1use crate::ast::definition::{ArgumentsDefinition, Context};
2use crate::ast::{FromTokens, Parse, ParseError, Tokens, TryFromTokens};
3use crate::lexical_token::{Name, PunctuatorType, StringValue};
4use crate::Span;
5use bluejay_core::definition::{
6    DirectiveDefinition as CoreDirectiveDefinition, DirectiveLocation as CoreDirectiveLocation,
7};
8use bluejay_core::AsIter;
9use std::str::FromStr;
10use strum::{EnumIter, IntoStaticStr};
11
12#[derive(IntoStaticStr, EnumIter, Clone, Copy, Debug, PartialEq)]
13#[strum(serialize_all = "camelCase")]
14pub enum BuiltinDirectiveDefinition {
15    Deprecated,
16    Include,
17    OneOf,
18    Skip,
19    SpecifiedBy,
20}
21
22impl BuiltinDirectiveDefinition {
23    const SKIP_DEFINITION: &'static str =
24        "directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT";
25    const INCLUDE_DEFINITION: &'static str =
26        "directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT";
27    const DEPRECATED_DEFINITION: &'static str = "directive @deprecated(reason: String = \"No longer supported\") on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE";
28    const SPECIFIED_BY_DEFINITION: &'static str = "directive @specifiedBy(url: String!) on SCALAR";
29    const ONE_OF_DEFINITION: &'static str = "directive @oneOf on INPUT_OBJECT";
30
31    fn definition(&self) -> &'static str {
32        match self {
33            Self::Deprecated => Self::DEPRECATED_DEFINITION,
34            Self::Include => Self::INCLUDE_DEFINITION,
35            Self::OneOf => Self::ONE_OF_DEFINITION,
36            Self::Skip => Self::SKIP_DEFINITION,
37            Self::SpecifiedBy => Self::SPECIFIED_BY_DEFINITION,
38        }
39    }
40}
41
42impl<'a, C: Context> From<BuiltinDirectiveDefinition> for DirectiveDefinition<'a, C> {
43    fn from(value: BuiltinDirectiveDefinition) -> Self {
44        let mut definition = DirectiveDefinition::parse(value.definition()).unwrap();
45
46        definition.is_builtin = true;
47        definition
48    }
49}
50
51#[derive(Debug)]
52pub struct DirectiveDefinition<'a, C: Context> {
53    description: Option<StringValue<'a>>,
54    name: Name<'a>,
55    arguments_definition: Option<ArgumentsDefinition<'a, C>>,
56    is_repeatable: bool,
57    locations: DirectiveLocations,
58    is_builtin: bool,
59}
60
61impl<'a, C: Context> CoreDirectiveDefinition for DirectiveDefinition<'a, C> {
62    type ArgumentsDefinition = ArgumentsDefinition<'a, C>;
63    type DirectiveLocations = DirectiveLocations;
64
65    fn description(&self) -> Option<&str> {
66        self.description.as_ref().map(AsRef::as_ref)
67    }
68
69    fn name(&self) -> &str {
70        self.name.as_ref()
71    }
72
73    fn arguments_definition(&self) -> Option<&Self::ArgumentsDefinition> {
74        self.arguments_definition.as_ref()
75    }
76
77    fn is_repeatable(&self) -> bool {
78        self.is_repeatable
79    }
80
81    fn locations(&self) -> &Self::DirectiveLocations {
82        &self.locations
83    }
84
85    fn is_builtin(&self) -> bool {
86        self.is_builtin
87    }
88}
89
90impl<'a, C: Context> DirectiveDefinition<'a, C> {
91    pub(crate) const DIRECTIVE_IDENTIFIER: &'static str = "directive";
92    const REPEATABLE_IDENTIFIER: &'static str = "repeatable";
93    const ON_IDENTIFIER: &'static str = "on";
94
95    pub(crate) fn name_token(&self) -> &Name<'a> {
96        &self.name
97    }
98
99    pub(crate) fn name(&self) -> &'a str {
100        self.name.as_str()
101    }
102}
103
104impl<'a, C: Context> FromTokens<'a> for DirectiveDefinition<'a, C> {
105    fn from_tokens(tokens: &mut impl Tokens<'a>) -> Result<Self, ParseError> {
106        let description = tokens.next_if_string_value();
107        tokens.expect_name_value(Self::DIRECTIVE_IDENTIFIER)?;
108        tokens.expect_punctuator(PunctuatorType::At)?;
109        let name = tokens.expect_name()?;
110        let arguments_definition = ArgumentsDefinition::try_from_tokens(tokens).transpose()?;
111        let is_repeatable = tokens
112            .next_if_name_matches(Self::REPEATABLE_IDENTIFIER)
113            .is_some();
114        tokens.expect_name_value(Self::ON_IDENTIFIER)?;
115        let locations = DirectiveLocations::from_tokens(tokens)?;
116        Ok(Self {
117            description,
118            name,
119            arguments_definition,
120            is_repeatable,
121            locations,
122            is_builtin: false,
123        })
124    }
125}
126
127#[derive(Debug)]
128pub struct DirectiveLocation {
129    inner: CoreDirectiveLocation,
130    _span: Span,
131}
132
133impl<'a> FromTokens<'a> for DirectiveLocation {
134    fn from_tokens(tokens: &mut impl Tokens<'a>) -> Result<Self, ParseError> {
135        tokens.expect_name().and_then(
136            |name| match CoreDirectiveLocation::from_str(name.as_ref()) {
137                Ok(inner) => Ok(Self {
138                    inner,
139                    _span: name.into(),
140                }),
141                Err(_) => Err(ParseError::ExpectedOneOf {
142                    span: name.into(),
143                    values: CoreDirectiveLocation::POSSIBLE_VALUES,
144                }),
145            },
146        )
147    }
148}
149
150impl AsRef<CoreDirectiveLocation> for DirectiveLocation {
151    fn as_ref(&self) -> &CoreDirectiveLocation {
152        &self.inner
153    }
154}
155
156#[derive(Debug)]
157#[repr(transparent)]
158pub struct DirectiveLocations(Vec<DirectiveLocation>);
159
160impl AsIter for DirectiveLocations {
161    type Item = CoreDirectiveLocation;
162    type Iterator<'a> = std::iter::Map<
163        std::slice::Iter<'a, DirectiveLocation>,
164        fn(&'a DirectiveLocation) -> &'a CoreDirectiveLocation,
165    >;
166
167    fn iter(&self) -> Self::Iterator<'_> {
168        self.0.iter().map(AsRef::as_ref)
169    }
170}
171
172impl<'a> FromTokens<'a> for DirectiveLocations {
173    fn from_tokens(tokens: &mut impl Tokens<'a>) -> Result<Self, ParseError> {
174        tokens.next_if_punctuator(PunctuatorType::Pipe);
175        let mut directive_locations: Vec<DirectiveLocation> =
176            vec![DirectiveLocation::from_tokens(tokens)?];
177        while tokens.next_if_punctuator(PunctuatorType::Pipe).is_some() {
178            directive_locations.push(DirectiveLocation::from_tokens(tokens)?);
179        }
180        Ok(Self(directive_locations))
181    }
182}