Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,26 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- [September 2025] GraphQL spec: ([#1347])
- Made `includeDeprecated` argument of `__Type.fields`, `__Type.enumValues`, `__Type.inputFields`, `__Field.args` and `__Directive.args` fields non-`Null`. ([#1348], [graphql/graphql-spec#1142])
- Made `@deprecated(reason:)` argument non-`Null`. ([#1348], [graphql/graphql-spec#1040])
- Added `description` field to `ast::Operation`, `ast::Fragment` and `ast::VariableDefinition`. ([#1349], [graphql/graphql-spec#1170])
- Changed `ScalarToken::String` to contain raw quoted and escaped `StringLiteral` (was unquoted but escaped string before). ([#1349])
- Added `LexerError::UnterminatedBlockString` variant. ([#1349])

### Added

- [September 2025] GraphQL spec: ([#1347])
- `__Type.isOneOf` field. ([#1348], [graphql/graphql-spec#825])
- `SCHEMA`, `OBJECT`, `ARGUMENT_DEFINITION`, `INTERFACE`, `UNION`, `ENUM`, `INPUT_OBJECT` and `INPUT_FIELD_DEFINITION` values to `__DirectiveLocation` enum. ([#1348])
- Arguments and input object fields deprecation: ([#1348], [#864], [graphql/graphql-spec#525], [graphql/graphql-spec#805])
- Arguments and input object fields deprecation: ([#1348], [#864], [graphql/graphql-spec#525], [graphql/graphql-spec#805])
- Placing `#[graphql(deprecated)]` and `#[deprecated]` attributes on struct fields in `#[derive(GraphQLInputObject)]` macro.
- Placing `#[graphql(deprecated)]` attribute on method arguments in `#[graphql_object]` and `#[graphql_interface]` macros.
- Placing `@deprecated` directive on arguments and input object fields.
- `includeDeprecated` argument to `__Type.inputFields`, `__Field.args` and `__Directive.args` fields.
- `__InputValue.isDeprecated` and `__InputValue.deprecationReason` fields.
- `schema::meta::Argument::deprecation_status` field.
- Support for variable-length escaped Unicode characters (e.g. `\u{110000}`) in strings. ([#1349], [graphql/graphql-spec#849], [graphql/graphql-spec#687])
- Full Unicode range support. ([#1349], [graphql/graphql-spec#849], [graphql/graphql-spec#687])
- Support parsing descriptions on operations, fragments and variable definitions. ([#1349], [graphql/graphql-spec#1170])
- Support for [block strings][0180-1]. ([#1349])

### Changed

Expand All @@ -38,15 +45,21 @@ All user visible changes to `juniper` crate will be documented in this file. Thi

- Incorrect `__Type.specifiedByUrl` field to `__Type.specifiedByURL`. ([#1348])
- Missing `@specifiedBy(url:)` directive in [SDL] generated by `RootNode::as_sdl()` and `RootNode::as_document()` methods. ([#1348])
- Incorrect double escaping in `ScalarToken::String` `Display`ing. ([#1349])

[#864]: /../../issues/864
[#1347]: /../../issues/1347
[#1348]: /../../pull/1348
[#1349]: /../../pull/1349
[graphql/graphql-spec#525]: https://github.com/graphql/graphql-spec/pull/525
[graphql/graphql-spec#687]: https://github.com/graphql/graphql-spec/issues/687
[graphql/graphql-spec#805]: https://github.com/graphql/graphql-spec/pull/805
[graphql/graphql-spec#825]: https://github.com/graphql/graphql-spec/pull/825
[graphql/graphql-spec#849]: https://github.com/graphql/graphql-spec/pull/849
[graphql/graphql-spec#1040]: https://github.com/graphql/graphql-spec/pull/1040
[graphql/graphql-spec#1142]: https://github.com/graphql/graphql-spec/pull/1142
[graphql/graphql-spec#1170]: https://github.com/graphql/graphql-spec/pull/1170
[0180-1]: https://spec.graphql.org/September2025/#sec-String-Value.Block-Strings



Expand Down
13 changes: 13 additions & 0 deletions juniper/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ pub enum InputValue<S = DefaultScalarValue> {

#[derive(Clone, Debug, PartialEq)]
pub struct VariableDefinition<'a, S> {
pub description: Option<Spanning<Cow<'a, str>>>,
pub var_type: Spanning<Type<&'a str>>,
pub default_value: Option<Spanning<InputValue<S>>>,
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
Expand Down Expand Up @@ -384,6 +385,7 @@ pub enum OperationType {
#[expect(missing_docs, reason = "self-explanatory")]
#[derive(Clone, Debug, PartialEq)]
pub struct Operation<'a, S> {
pub description: Option<Spanning<Cow<'a, str>>>,
pub operation_type: OperationType,
pub name: Option<Spanning<&'a str>>,
pub variable_definitions: Option<Spanning<VariableDefinitions<'a, S>>>,
Expand All @@ -394,6 +396,7 @@ pub struct Operation<'a, S> {
#[derive(Clone, Debug, PartialEq)]
pub struct Fragment<'a, S> {
pub name: Spanning<&'a str>,
pub description: Option<Spanning<Cow<'a, str>>>,
pub type_condition: Spanning<&'a str>,
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
pub selection_set: Vec<Selection<'a, S>>,
Expand All @@ -406,6 +409,16 @@ pub enum Definition<'a, S> {
Fragment(Spanning<Fragment<'a, S>>),
}

impl<'a, S> Definition<'a, S> {
/// Sets or resets the provided `description` for this [`Definition`].
pub(crate) fn set_description(&mut self, description: Option<Spanning<Cow<'a, str>>>) {
match self {
Self::Operation(op) => op.item.description = description,
Self::Fragment(frag) => frag.item.description = description,
}
}
}

#[doc(hidden)]
pub type Document<'a, S> = [Definition<'a, S>];
#[doc(hidden)]
Expand Down
11 changes: 10 additions & 1 deletion juniper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ extern crate self as juniper;
mod for_benches_only {
use bencher as _;
}
#[cfg(test)]
mod for_feature_gated_tests_only {
#[cfg(not(feature = "chrono"))]
use chrono as _;
#[cfg(not(feature = "jiff"))]
use jiff as _;
#[cfg(not(feature = "anyhow"))]
use serial_test as _;
}

// These are required by the code generated via the `juniper_codegen` macros.
#[doc(hidden)]
Expand Down Expand Up @@ -87,7 +96,7 @@ pub use crate::{
},
introspection::IntrospectionFormat,
macros::helper::subscription::{ExtractTypeFromStream, IntoFieldResult},
parser::{ParseError, ScalarToken, Span, Spanning},
parser::{ParseError, ScalarToken, Span, Spanning, StringLiteral},
schema::{
meta,
model::{RootNode, SchemaType},
Expand Down
61 changes: 45 additions & 16 deletions juniper/src/parser/document.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::ast::{
Arguments, Definition, Directive, Field, Fragment, FragmentSpread, InlineFragment, InputValue,
Operation, OperationType, OwnedDocument, Selection, Type, VariableDefinition,
VariableDefinitions,
};
use std::borrow::Cow;

use crate::{
ast::{
Arguments, Definition, Directive, Field, Fragment, FragmentSpread, InlineFragment,
InputValue, Operation, OperationType, OwnedDocument, Selection, Type, VariableDefinition,
VariableDefinitions,
},
parser::{
Lexer, OptionParseResult, ParseError, ParseResult, Parser, Spanning, Token,
Lexer, OptionParseResult, ParseError, ParseResult, Parser, ScalarToken, Spanning, Token,
UnlocatedParseResult, value::parse_value_literal,
},
schema::{
Expand All @@ -25,7 +26,7 @@ where
S: ScalarValue,
{
let mut lexer = Lexer::new(s);
let mut parser = Parser::new(&mut lexer).map_err(|s| s.map(ParseError::LexerError))?;
let mut parser = Parser::new(&mut lexer).map_err(|s| s.map(Into::into))?;
parse_document(&mut parser, schema)
}

Expand Down Expand Up @@ -54,18 +55,25 @@ fn parse_definition<'a, S>(
where
S: ScalarValue,
{
match parser.peek().item {
let description = parse_description(parser)?;

let mut def = match parser.peek().item {
// Descriptions are not permitted on query shorthand.
// See: https://spec.graphql.org/September2025#sel-GAFTRJABAByBz7P
Token::CurlyOpen if description.is_some() => {
return Err(parser.next_token()?.map(ParseError::unexpected_token));
}
Token::CurlyOpen
| Token::Name("query")
| Token::Name("mutation")
| Token::Name("subscription") => Ok(Definition::Operation(parse_operation_definition(
parser, schema,
)?)),
Token::Name("fragment") => Ok(Definition::Fragment(parse_fragment_definition(
parser, schema,
)?)),
_ => Err(parser.next_token()?.map(ParseError::unexpected_token)),
}
| Token::Name("subscription") => {
Definition::Operation(parse_operation_definition(parser, schema)?)
}
Token::Name("fragment") => Definition::Fragment(parse_fragment_definition(parser, schema)?),
_ => return Err(parser.next_token()?.map(ParseError::unexpected_token)),
};
def.set_description(description);
Ok(def)
}

fn parse_operation_definition<'a, S>(
Expand All @@ -85,6 +93,7 @@ where
Operation {
operation_type: OperationType::Query,
name: None,
description: None,
variable_definitions: None,
directives: None,
selection_set: selection_set.item,
Expand Down Expand Up @@ -115,6 +124,7 @@ where
Operation {
operation_type: operation_type.item,
name,
description: None,
variable_definitions,
directives: directives.map(|s| s.item),
selection_set: selection_set.item,
Expand Down Expand Up @@ -158,6 +168,7 @@ where
&selection_set.span.end,
Fragment {
name,
description: None,
type_condition: type_cond,
directives: directives.map(|s| s.item),
selection_set: selection_set.item,
Expand Down Expand Up @@ -429,6 +440,8 @@ fn parse_variable_definition<'a, S>(
where
S: ScalarValue,
{
let description = parse_description(parser)?;

let start_pos = parser.expect(&Token::Dollar)?.span.start;
let var_name = parser.expect_name()?;
parser.expect(&Token::Colon)?;
Expand All @@ -452,6 +465,7 @@ where
(
Spanning::start_end(&start_pos, &var_name.span.end, var_name.item),
VariableDefinition {
description,
var_type,
default_value,
directives: directives.map(|s| s.item),
Expand All @@ -460,6 +474,21 @@ where
))
}

fn parse_description<'a>(parser: &mut Parser<'a>) -> OptionParseResult<Cow<'a, str>> {
if !matches!(parser.peek().item, Token::Scalar(ScalarToken::String(_))) {
Ok(None)
} else {
let token = parser.next_token()?;
let Token::Scalar(ScalarToken::String(lit)) = token.item else {
unreachable!("already checked to be `ScalarToken::String`")
};
Ok(Some(Spanning::new(
token.span,
lit.parse().map_err(|e| Spanning::new(token.span, e))?,
)))
}
}

fn parse_directives<'a, S>(
parser: &mut Parser<'a>,
schema: &SchemaType<S>,
Expand Down
Loading