Skip to content

Commit ae6ec5d

Browse files
committed
Support descriptions on operations and fragments
1 parent 5455fc9 commit ae6ec5d

File tree

4 files changed

+314
-18
lines changed

4 files changed

+314
-18
lines changed

juniper/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
1616
- Made `includeDeprecated` argument of `__Type.fields`, `__Type.enumValues`, `__Type.inputFields`, `__Field.args` and `__Directive.args` fields non-`Null`. ([#1348], [graphql/graphql-spec#1142])
1717
- Made `@deprecated(reason:)` argument non-`Null`. ([#1348], [graphql/graphql-spec#1040])
1818
- Changed `ScalarToken::String` to contain raw quoted and escaped `StringLiteral` (was unquoted but escaped string before). ([#1349])
19+
- Added `description` field to `ast::Operation` and `ast::Fragment` field. ([#1349], [graphql/graphql-spec#1170])
1920
- Added `LexerError::UnterminatedBlockString` variant. ([#1349])
2021

2122
### Added
@@ -32,6 +33,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
3233
- `schema::meta::Argument::deprecation_status` field.
3334
- Support for variable-length escaped Unicode characters (e.g. `\u{110000}`) in strings. ([#1349], [graphql/graphql-spec#849], [graphql/graphql-spec#687])
3435
- Full Unicode range support. ([#1349], [graphql/graphql-spec#849], [graphql/graphql-spec#687])
36+
- Support parsing descriptions on fragments. ([#1349], [graphql/graphql-spec#1170])
3537
- Support for [block strings][0180-1]. ([#1349])
3638

3739
### Changed
@@ -56,6 +58,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
5658
[graphql/graphql-spec#849]: https://github.com/graphql/graphql-spec/pull/849
5759
[graphql/graphql-spec#1040]: https://github.com/graphql/graphql-spec/pull/1040
5860
[graphql/graphql-spec#1142]: https://github.com/graphql/graphql-spec/pull/1142
61+
[graphql/graphql-spec#1170]: https://github.com/graphql/graphql-spec/pull/1170
5962
[0180-1]: https://spec.graphql.org/September2025/#sec-String-Value.Block-Strings
6063

6164

juniper/src/ast.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ pub enum OperationType {
384384
#[expect(missing_docs, reason = "self-explanatory")]
385385
#[derive(Clone, Debug, PartialEq)]
386386
pub struct Operation<'a, S> {
387-
//pub description: Option<Spanning<&'a str>>,
387+
pub description: Option<Spanning<Cow<'a, str>>>,
388388
pub operation_type: OperationType,
389389
pub name: Option<Spanning<&'a str>>,
390390
pub variable_definitions: Option<Spanning<VariableDefinitions<'a, S>>>,
@@ -395,6 +395,7 @@ pub struct Operation<'a, S> {
395395
#[derive(Clone, Debug, PartialEq)]
396396
pub struct Fragment<'a, S> {
397397
pub name: Spanning<&'a str>,
398+
pub description: Option<Spanning<Cow<'a, str>>>,
398399
pub type_condition: Spanning<&'a str>,
399400
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
400401
pub selection_set: Vec<Selection<'a, S>>,
@@ -407,6 +408,16 @@ pub enum Definition<'a, S> {
407408
Fragment(Spanning<Fragment<'a, S>>),
408409
}
409410

411+
impl<'a, S> Definition<'a, S> {
412+
/// Sets or resets the provided `description` for this [`Definition`].
413+
pub(crate) fn set_description(&mut self, description: Option<Spanning<Cow<'a, str>>>) {
414+
match self {
415+
Self::Operation(op) => op.item.description = description,
416+
Self::Fragment(frag) => frag.item.description = description,
417+
}
418+
}
419+
}
420+
410421
#[doc(hidden)]
411422
pub type Document<'a, S> = [Definition<'a, S>];
412423
#[doc(hidden)]

juniper/src/parser/document.rs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use crate::ast::{
2-
Arguments, Definition, Directive, Field, Fragment, FragmentSpread, InlineFragment, InputValue,
3-
Operation, OperationType, OwnedDocument, Selection, Type, VariableDefinition,
4-
VariableDefinitions,
5-
};
1+
use std::borrow::Cow;
62

73
use crate::{
4+
ast::{
5+
Arguments, Definition, Directive, Field, Fragment, FragmentSpread, InlineFragment,
6+
InputValue, Operation, OperationType, OwnedDocument, Selection, Type, VariableDefinition,
7+
VariableDefinitions,
8+
},
89
parser::{
9-
Lexer, OptionParseResult, ParseError, ParseResult, Parser, Spanning, Token,
10+
Lexer, OptionParseResult, ParseError, ParseResult, Parser, ScalarToken, Spanning, Token,
1011
UnlocatedParseResult, value::parse_value_literal,
1112
},
1213
schema::{
@@ -54,18 +55,25 @@ fn parse_definition<'a, S>(
5455
where
5556
S: ScalarValue,
5657
{
57-
match parser.peek().item {
58+
let description = parse_description(parser)?;
59+
60+
let mut def = match parser.peek().item {
61+
// Descriptions are not permitted on query shorthand.
62+
// See: https://spec.graphql.org/September2025#sel-GAFTRJABAByBz7P
63+
Token::CurlyOpen if description.is_some() => {
64+
return Err(parser.next_token()?.map(ParseError::unexpected_token));
65+
}
5866
Token::CurlyOpen
5967
| Token::Name("query")
6068
| Token::Name("mutation")
61-
| Token::Name("subscription") => Ok(Definition::Operation(parse_operation_definition(
62-
parser, schema,
63-
)?)),
64-
Token::Name("fragment") => Ok(Definition::Fragment(parse_fragment_definition(
65-
parser, schema,
66-
)?)),
67-
_ => Err(parser.next_token()?.map(ParseError::unexpected_token)),
68-
}
69+
| Token::Name("subscription") => {
70+
Definition::Operation(parse_operation_definition(parser, schema)?)
71+
}
72+
Token::Name("fragment") => Definition::Fragment(parse_fragment_definition(parser, schema)?),
73+
_ => return Err(parser.next_token()?.map(ParseError::unexpected_token)),
74+
};
75+
def.set_description(description);
76+
Ok(def)
6977
}
7078

7179
fn parse_operation_definition<'a, S>(
@@ -85,6 +93,7 @@ where
8593
Operation {
8694
operation_type: OperationType::Query,
8795
name: None,
96+
description: None,
8897
variable_definitions: None,
8998
directives: None,
9099
selection_set: selection_set.item,
@@ -115,6 +124,7 @@ where
115124
Operation {
116125
operation_type: operation_type.item,
117126
name,
127+
description: None,
118128
variable_definitions,
119129
directives: directives.map(|s| s.item),
120130
selection_set: selection_set.item,
@@ -158,6 +168,7 @@ where
158168
&selection_set.span.end,
159169
Fragment {
160170
name,
171+
description: None,
161172
type_condition: type_cond,
162173
directives: directives.map(|s| s.item),
163174
selection_set: selection_set.item,
@@ -460,6 +471,21 @@ where
460471
))
461472
}
462473

474+
fn parse_description<'a>(parser: &mut Parser<'a>) -> OptionParseResult<Cow<'a, str>> {
475+
if !matches!(parser.peek().item, Token::Scalar(ScalarToken::String(_))) {
476+
Ok(None)
477+
} else {
478+
let token = parser.next_token()?;
479+
let Token::Scalar(ScalarToken::String(lit)) = token.item else {
480+
unreachable!("already checked to be `ScalarToken::String`")
481+
};
482+
Ok(Some(Spanning::new(
483+
token.span,
484+
lit.parse().map_err(|e| Spanning::new(token.span, e))?,
485+
)))
486+
}
487+
}
488+
463489
fn parse_directives<'a, S>(
464490
parser: &mut Parser<'a>,
465491
schema: &SchemaType<S>,

0 commit comments

Comments
 (0)