bluejay_parser/ast/definition/
directive_definition.rs

1use crate::ast::definition::{ArgumentsDefinition, Context};
2use crate::ast::{DepthLimiter, 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<C: Context> From<BuiltinDirectiveDefinition> for DirectiveDefinition<'_, 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(
106        tokens: &mut impl Tokens<'a>,
107        depth_limiter: DepthLimiter,
108    ) -> Result<Self, ParseError> {
109        let description = tokens.next_if_string_value();
110        tokens.expect_name_value(Self::DIRECTIVE_IDENTIFIER)?;
111        tokens.expect_punctuator(PunctuatorType::At)?;
112        let name = tokens.expect_name()?;
113        let arguments_definition =
114            ArgumentsDefinition::try_from_tokens(tokens, depth_limiter.bump()?).transpose()?;
115        let is_repeatable = tokens
116            .next_if_name_matches(Self::REPEATABLE_IDENTIFIER)
117            .is_some();
118        tokens.expect_name_value(Self::ON_IDENTIFIER)?;
119        let locations = DirectiveLocations::from_tokens(tokens, depth_limiter.bump()?)?;
120        Ok(Self {
121            description,
122            name,
123            arguments_definition,
124            is_repeatable,
125            locations,
126            is_builtin: false,
127        })
128    }
129}
130
131#[derive(Debug)]
132pub struct DirectiveLocation {
133    inner: CoreDirectiveLocation,
134    _span: Span,
135}
136
137impl<'a> FromTokens<'a> for DirectiveLocation {
138    fn from_tokens(tokens: &mut impl Tokens<'a>, _: DepthLimiter) -> Result<Self, ParseError> {
139        tokens.expect_name().and_then(
140            |name| match CoreDirectiveLocation::from_str(name.as_ref()) {
141                Ok(inner) => Ok(Self {
142                    inner,
143                    _span: name.into(),
144                }),
145                Err(_) => Err(ParseError::ExpectedOneOf {
146                    span: name.into(),
147                    values: CoreDirectiveLocation::POSSIBLE_VALUES,
148                }),
149            },
150        )
151    }
152}
153
154impl AsRef<CoreDirectiveLocation> for DirectiveLocation {
155    fn as_ref(&self) -> &CoreDirectiveLocation {
156        &self.inner
157    }
158}
159
160#[derive(Debug)]
161#[repr(transparent)]
162pub struct DirectiveLocations(Vec<DirectiveLocation>);
163
164impl AsIter for DirectiveLocations {
165    type Item = CoreDirectiveLocation;
166    type Iterator<'a> = std::iter::Map<
167        std::slice::Iter<'a, DirectiveLocation>,
168        fn(&'a DirectiveLocation) -> &'a CoreDirectiveLocation,
169    >;
170
171    fn iter(&self) -> Self::Iterator<'_> {
172        self.0.iter().map(AsRef::as_ref)
173    }
174}
175
176impl<'a> FromTokens<'a> for DirectiveLocations {
177    fn from_tokens(
178        tokens: &mut impl Tokens<'a>,
179        depth_limiter: DepthLimiter,
180    ) -> Result<Self, ParseError> {
181        tokens.next_if_punctuator(PunctuatorType::Pipe);
182        let mut directive_locations: Vec<DirectiveLocation> = vec![DirectiveLocation::from_tokens(
183            tokens,
184            depth_limiter.bump()?,
185        )?];
186        while tokens.next_if_punctuator(PunctuatorType::Pipe).is_some() {
187            directive_locations.push(DirectiveLocation::from_tokens(
188                tokens,
189                depth_limiter.bump()?,
190            )?);
191        }
192        Ok(Self(directive_locations))
193    }
194}