use crate::parser::grammar::field;
use crate::parser::grammar::fragment;
use crate::Parser;
use crate::SyntaxKind;
use crate::TokenKind;
use crate::S;
use crate::T;
use std::ops::ControlFlow;
pub(crate) fn selection_set(p: &mut Parser) {
if let Some(T!['{']) = p.peek() {
let _g = p.start_node(SyntaxKind::SELECTION_SET);
p.bump(S!['{']);
if p.recursion_limit.check_and_increment() {
p.limit_err("parser recursion limit reached");
return;
}
selection(p);
p.recursion_limit.decrement();
p.expect(T!['}'], S!['}']);
}
}
pub(crate) fn field_set(p: &mut Parser) {
if let Some(T!['{']) = p.peek() {
selection_set(p)
} else {
let _g = p.start_node(SyntaxKind::SELECTION_SET);
if p.recursion_limit.check_and_increment() {
p.limit_err("parser recursion limit reached");
return;
}
selection(p);
p.recursion_limit.decrement();
}
}
pub(crate) fn selection(p: &mut Parser) {
let mut has_selection = false;
p.peek_while(|p, kind| match kind {
T![...] => {
let next_token = p.peek_token_n(2);
match next_token {
Some(next_token) => {
if next_token.kind() == TokenKind::Name && next_token.data() != "on" {
fragment::fragment_spread(p);
} else if matches!(
next_token.kind(),
TokenKind::At | TokenKind::Name | TokenKind::LCurly
) {
fragment::inline_fragment(p);
} else {
p.err("expected an Inline Fragment or a Fragment Spread");
p.bump(S![...]);
}
has_selection = true;
ControlFlow::Continue(())
}
None => {
p.err_and_pop("expected an Inline Fragment or a Fragment Spread");
ControlFlow::Break(())
}
}
}
T!['{'] => ControlFlow::Break(()),
TokenKind::Name => {
field::field(p);
has_selection = true;
ControlFlow::Continue(())
}
_ => ControlFlow::Break(()),
});
if !has_selection {
p.err("expected at least one Selection in Selection Set");
}
}
#[cfg(test)]
mod test {
use crate::cst;
use crate::Parser;
use crate::TokenText;
#[test]
fn fragment_spread_in_selection() {
let input = "
{
animal
...snackSelection
... on Pet {
playmates {
count
}
}
}
";
let parser = Parser::new(input);
let cst = parser.parse();
assert_eq!(0, cst.errors().len());
let doc = cst.document();
for def in doc.definitions() {
if let cst::Definition::OperationDefinition(op_def) = def {
if let Some(selection_set) = op_def.selection_set() {
for selection in selection_set.selections() {
match selection {
cst::Selection::Field(field) => {
assert_eq!(
"animal".to_string(),
field.name().unwrap().text().to_string()
);
}
cst::Selection::FragmentSpread(f_spread) => {
assert_eq!(
"snackSelection".to_string(),
f_spread
.fragment_name()
.unwrap()
.name()
.unwrap()
.text()
.to_string()
)
}
cst::Selection::InlineFragment(inline_fragment) => {
assert_eq!(
"Pet".to_string(),
inline_fragment
.type_condition()
.unwrap()
.named_type()
.unwrap()
.name()
.unwrap()
.text()
.to_string()
);
}
}
}
}
}
}
}
#[test]
fn do_query_variables_match() {
let input = "
query GraphQuery($graph_id: ID!, $variant: String) {
service(id: $graph_id) {
schema(tag: $variant) {
document
}
}
}
";
let parser = Parser::new(input);
let cst = parser.parse();
assert_eq!(0, cst.errors().len());
let doc = cst.document();
for def in doc.definitions() {
if let cst::Definition::OperationDefinition(op_def) = def {
assert_eq!(op_def.name().unwrap().text(), "GraphQuery");
let variable_defs = op_def.variable_definitions();
let variables: Vec<TokenText> = variable_defs
.iter()
.flat_map(|v| v.variable_definitions())
.filter_map(|v| Some(v.variable()?.name()?.text()))
.collect();
if let Some(selection_set) = op_def.selection_set() {
let mut vec = Vec::default();
let used_vars = get_variables_from_selection(&mut vec, selection_set);
assert!(do_variables_match(&variables, used_vars));
}
}
}
fn get_variables_from_selection(
used_vars: &mut Vec<TokenText>,
selection_set: cst::SelectionSet,
) -> &Vec<TokenText> {
for selection in selection_set.selections() {
match selection {
cst::Selection::Field(field) => {
let arguments = field.arguments();
let mut vars: Vec<TokenText> = arguments
.iter()
.flat_map(|a| a.arguments())
.filter_map(|v| {
if let cst::Value::Variable(var) = v.value()? {
return Some(var.name()?.text());
}
None
})
.collect();
used_vars.append(&mut vars);
if let Some(selection_set) = field.selection_set() {
get_variables_from_selection(used_vars, selection_set);
}
}
_ => unimplemented!(),
}
}
used_vars
}
fn do_variables_match(a: &[TokenText], b: &[TokenText]) -> bool {
let matching = a.iter().zip(b.iter()).filter(|&(a, b)| a == b).count();
matching == a.len() && matching == b.len()
}
}
#[test]
fn it_gets_nested_selection_set_fields() {
let query = r#"
query SomeQuery(
$param1: String!
$param2: String!
) {
item1(
param1: $param1
param2: $param2
) {
id
...Fragment1
... on Fragment2 {
field3 {
field4
}
}
}
}"#;
let parser = Parser::new(query);
let cst = parser.parse();
assert_eq!(cst.errors().len(), 0);
let doc = cst.document();
for def in doc.definitions() {
if let cst::Definition::OperationDefinition(op_def) = def {
let selection_set = op_def.selection_set().unwrap();
for selection in selection_set.selections() {
if let cst::Selection::Field(field) = selection {
assert_eq!("item1", field.name().unwrap().text().as_ref());
let selection_set = field.selection_set().unwrap();
for selection in selection_set.selections() {
match selection {
cst::Selection::Field(field) => {
assert_eq!("id", field.name().unwrap().text().as_ref());
}
cst::Selection::FragmentSpread(fragment_spread) => {
assert_eq!(
"Fragment1",
fragment_spread
.fragment_name()
.unwrap()
.name()
.unwrap()
.text()
.as_ref()
);
}
cst::Selection::InlineFragment(inline_fragment) => {
assert_eq!(
"Fragment2",
inline_fragment
.type_condition()
.unwrap()
.named_type()
.unwrap()
.name()
.unwrap()
.text()
.as_ref()
);
}
}
}
}
}
}
}
}
#[test]
fn it_errors_when_selection_set_is_empty() {
let schema = r#"
query($foo: Int) {}
"#;
let parser = Parser::new(schema);
let cst = parser.parse();
assert_eq!(cst.errors().len(), 1);
assert_eq!(cst.document().definitions().count(), 1);
}
#[test]
fn it_errors_when_selection_set_recursion_limit_exceeded() {
let schema = r#"
query {
Q1 {
Q2 {
url
}
}
}
"#;
let parser = Parser::new(schema).recursion_limit(1);
let cst = parser.parse();
assert_eq!(cst.recursion_limit().high, 2);
assert_eq!(cst.errors().len(), 1);
assert_eq!(cst.document().definitions().count(), 1);
}
#[test]
fn it_passes_when_selection_set_recursion_limit_is_not_exceeded() {
let schema = r#"
query {
Q1 {
Q2 {
url
}
}
}
"#;
let parser = Parser::new(schema).recursion_limit(7);
let cst = parser.parse();
assert_eq!(cst.recursion_limit().high, 3);
assert_eq!(cst.errors().len(), 0);
assert_eq!(cst.document().definitions().count(), 1);
}
#[test]
fn it_errors_when_selection_set_recursion_limit_is_exceeded_with_inline_fragment() {
let schema = r#"
query {
Q1 {
Q2 {
... on Page {
price
name
}
}
}
}
"#;
let parser = Parser::new(schema).recursion_limit(2);
let cst = parser.parse();
assert_eq!(cst.recursion_limit().high, 3);
assert_eq!(cst.errors().len(), 1);
assert_eq!(cst.document().definitions().count(), 1);
}
#[test]
fn it_errors_when_selection_set_recursion_limit_is_exceeded_fragment_spread() {
let schema = r#"
query {
Q1 {
Q2 {
product {
...Page
}
}
}
}
"#;
let parser = Parser::new(schema).recursion_limit(2);
let cst = parser.parse();
assert_eq!(cst.recursion_limit().high, 3);
assert_eq!(cst.errors().len(), 1);
assert_eq!(dbg!(cst.document()).definitions().count(), 1);
}
#[test]
fn it_errors_when_selection_set_recursion_limit_is_exceeded_in_fragment_definition() {
let schema = r#"
query ExampleQuery {
topProducts {
name
}
... multipleSubscriptions
}
fragment multipleSubscriptions on Subscription {
newMessage {
body
sender
}
}
"#;
let parser = Parser::new(schema).recursion_limit(1);
let cst = parser.parse();
assert_eq!(cst.recursion_limit().high, 2);
assert_eq!(cst.errors().len(), 1);
assert_eq!(cst.document().definitions().count(), 2);
}
#[test]
fn it_passes_when_selection_set_recursion_limit_is_at_default() {
let schema = r#"
query {
Q1 {
Q2 {
Q3 {
Q4 {
Q5 { url }
}
}
}
},
}
"#;
let parser = Parser::new(schema);
let cst = parser.parse();
assert_eq!(cst.recursion_limit().high, 6);
assert_eq!(cst.errors().len(), 0);
assert_eq!(cst.document().definitions().count(), 1);
}
}