Skip to main content

bluejay_parser/ast/executable/
field.rs

1use crate::ast::executable::SelectionSet;
2use crate::ast::{
3    DepthLimiter, FromTokens, IsMatch, ParseError, Tokens, VariableArguments, VariableDirectives,
4};
5use crate::lexical_token::{Name, PunctuatorType};
6use crate::{HasSpan, Span};
7use std::cmp::{Eq, PartialEq};
8use std::hash::{Hash, Hasher};
9
10#[derive(Debug)]
11pub struct Field<'a> {
12    alias: Option<Name<'a>>,
13    name: Name<'a>,
14    arguments: Option<VariableArguments<'a>>,
15    directives: Option<VariableDirectives<'a>>,
16    selection_set: Option<SelectionSet<'a>>,
17    span: Span,
18}
19
20impl<'a> FromTokens<'a> for Field<'a> {
21    #[inline]
22    fn from_tokens(
23        tokens: &mut impl Tokens<'a>,
24        depth_limiter: DepthLimiter,
25    ) -> Result<Self, ParseError> {
26        // Consume the first name, then check if followed by `:` (alias syntax).
27        // This avoids the peek(1) that requires buffering 2 tokens.
28        let first_name = tokens.expect_name()?;
29        let (alias, name) = if tokens.next_if_punctuator(PunctuatorType::Colon).is_some() {
30            let name = tokens.expect_name()?;
31            (Some(first_name), name)
32        } else {
33            (None, first_name)
34        };
35        let arguments = if VariableArguments::is_match(tokens) {
36            Some(VariableArguments::from_tokens(
37                tokens,
38                depth_limiter.bump()?,
39            )?)
40        } else {
41            None
42        };
43        let directives = if VariableDirectives::is_match(tokens) {
44            Some(VariableDirectives::from_tokens(
45                tokens,
46                depth_limiter.bump()?,
47            )?)
48        } else {
49            None
50        };
51        let selection_set = if SelectionSet::is_match(tokens) {
52            Some(SelectionSet::from_tokens(tokens, depth_limiter.bump()?)?)
53        } else {
54            None
55        };
56        let start_span = alias.as_ref().unwrap_or(&name).span();
57        let directives_span = directives.as_ref().and_then(|directives| directives.span());
58        let end_span = if let Some(selection_set) = &selection_set {
59            selection_set.span()
60        } else if let Some(directive_span) = directives_span {
61            directive_span
62        } else if let Some(arguments) = &arguments {
63            arguments.span()
64        } else {
65            name.span()
66        };
67        let span = start_span.merge(end_span);
68        Ok(Self {
69            alias,
70            name,
71            arguments,
72            directives,
73            selection_set,
74            span,
75        })
76    }
77}
78
79impl<'a> IsMatch<'a> for Field<'a> {
80    #[inline]
81    fn is_match(tokens: &mut impl Tokens<'a>) -> bool {
82        tokens.peek_name(0).is_some()
83    }
84}
85
86impl<'a> Field<'a> {
87    pub fn response_key(&self) -> &str {
88        if let Some(alias) = &self.alias {
89            alias.as_ref()
90        } else {
91            self.name.as_ref()
92        }
93    }
94
95    pub fn name(&self) -> &Name<'a> {
96        &self.name
97    }
98
99    pub fn arguments(&self) -> Option<&VariableArguments<'_>> {
100        self.arguments.as_ref()
101    }
102
103    pub fn selection_set(&self) -> Option<&SelectionSet<'_>> {
104        self.selection_set.as_ref()
105    }
106}
107
108impl<'a> bluejay_core::executable::Field for Field<'a> {
109    type Arguments = VariableArguments<'a>;
110    type Directives = VariableDirectives<'a>;
111    type SelectionSet = SelectionSet<'a>;
112
113    fn alias(&self) -> Option<&str> {
114        self.alias.as_ref().map(|name| name.as_ref())
115    }
116
117    fn name(&self) -> &str {
118        self.name.as_ref()
119    }
120
121    fn arguments(&self) -> Option<&Self::Arguments> {
122        self.arguments.as_ref()
123    }
124
125    fn directives(&self) -> Option<&Self::Directives> {
126        self.directives.as_ref()
127    }
128
129    fn selection_set(&self) -> Option<&Self::SelectionSet> {
130        self.selection_set.as_ref()
131    }
132}
133
134impl HasSpan for Field<'_> {
135    fn span(&self) -> &Span {
136        &self.span
137    }
138}
139
140impl Hash for Field<'_> {
141    fn hash<H: Hasher>(&self, state: &mut H) {
142        self.span().hash(state);
143    }
144}
145
146impl PartialEq for Field<'_> {
147    fn eq(&self, other: &Self) -> bool {
148        self.span() == other.span()
149    }
150}
151
152impl Eq for Field<'_> {}