Skip to content
Draft
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
6 changes: 5 additions & 1 deletion juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
### Added

- [September 2025] GraphQL spec: ([#1347])
- `__Type.isOneOf` field. ([#1348], [graphql/graphql-spec#825])
- `@oneOf` input objects: ([#1354], [graphql/graphql-spec#825])
- `@oneOf` built-in directive.
- `__Type.isOneOf` field. ([#1348])
- `schema::meta::InputObjectMeta::is_one_of` field.
- `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])
- Placing `#[graphql(deprecated)]` and `#[deprecated]` attributes on struct fields in `#[derive(GraphQLInputObject)]` macro.
Expand Down Expand Up @@ -54,6 +57,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1348]: /../../pull/1348
[#1349]: /../../pull/1349
[#1353]: /../../pull/1353
[#1354]: /../../pull/1354
[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
Expand Down
12 changes: 12 additions & 0 deletions juniper/src/schema/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,8 @@ pub struct InputObjectMeta<S> {
pub description: Option<ArcStr>,
#[doc(hidden)]
pub input_fields: Vec<Argument<S>>,
#[doc(hidden)]
pub is_one_of: bool,
#[debug(ignore)]
pub(crate) try_parse_fn: InputValueParseFn<S>,
}
Expand All @@ -364,6 +366,7 @@ impl<S> InputObjectMeta<S> {
name: name.into(),
description: None,
input_fields: input_fields.to_vec(),
is_one_of: false,
try_parse_fn: try_parse_fn::<S, T>,
}
}
Expand All @@ -377,6 +380,15 @@ impl<S> InputObjectMeta<S> {
self
}

/// Marks this [`InputObjectMeta`] type as [`@oneOf`].
///
/// [`@oneOf`]: https://spec.graphql.org/September2025#sec--oneOf
#[must_use]
pub fn one_of(mut self) -> Self {
self.is_one_of = true;
self
}

/// Wraps this [`InputObjectMeta`] type into a generic [`MetaType`].
pub fn into_meta(self) -> MetaType<S> {
MetaType::InputObject(self)
Expand Down
54 changes: 34 additions & 20 deletions juniper/src/schema/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,16 @@ impl<S> SchemaType<S> {

registry.get_type::<SchemaType<S>>(&());

let deprecated_directive = DirectiveType::new_deprecated(&mut registry);
let include_directive = DirectiveType::new_include(&mut registry);
let one_of_directive = DirectiveType::new_one_of();
let skip_directive = DirectiveType::new_skip(&mut registry);
let deprecated_directive = DirectiveType::new_deprecated(&mut registry);
let specified_by_directive = DirectiveType::new_specified_by(&mut registry);
directives.insert(include_directive.name.clone(), include_directive);
directives.insert(skip_directive.name.clone(), skip_directive);
directives.insert(deprecated_directive.name.clone(), deprecated_directive);
directives.insert(specified_by_directive.name.clone(), specified_by_directive);
directives.insert(one_of_directive.name.clone(), one_of_directive);

let mut meta_fields = vec![
registry.field::<SchemaType<S>>(arcstr::literal!("__schema"), &()),
Expand Down Expand Up @@ -585,28 +587,33 @@ impl<S> DirectiveType<S> {
}
}

fn new_include(registry: &mut Registry<S>) -> Self
fn new_deprecated(registry: &mut Registry<S>) -> Self
where
S: ScalarValue,
{
Self::new(
arcstr::literal!("include"),
arcstr::literal!("deprecated"),
&[
DirectiveLocation::Field,
DirectiveLocation::FragmentSpread,
DirectiveLocation::InlineFragment,
DirectiveLocation::FieldDefinition,
DirectiveLocation::ArgumentDefinition,
DirectiveLocation::InputFieldDefinition,
DirectiveLocation::EnumValue,
],
&[registry.arg::<bool>(arcstr::literal!("if"), &())],
&[registry.arg_with_default::<String>(
arcstr::literal!("reason"),
&"No longer supported".into(),
&(),
)],
false,
)
}

fn new_skip(registry: &mut Registry<S>) -> Self
fn new_include(registry: &mut Registry<S>) -> Self
where
S: ScalarValue,
{
Self::new(
arcstr::literal!("skip"),
arcstr::literal!("include"),
&[
DirectiveLocation::Field,
DirectiveLocation::FragmentSpread,
Expand All @@ -617,23 +624,30 @@ impl<S> DirectiveType<S> {
)
}

fn new_deprecated(registry: &mut Registry<S>) -> Self
fn new_one_of() -> Self
where
S: ScalarValue,
{
Self::new(
arcstr::literal!("deprecated"),
arcstr::literal!("oneOf"),
&[DirectiveLocation::InputObject],
&[],
false,
)
}

fn new_skip(registry: &mut Registry<S>) -> Self
where
S: ScalarValue,
{
Self::new(
arcstr::literal!("skip"),
&[
DirectiveLocation::FieldDefinition,
DirectiveLocation::ArgumentDefinition,
DirectiveLocation::InputFieldDefinition,
DirectiveLocation::EnumValue,
DirectiveLocation::Field,
DirectiveLocation::FragmentSpread,
DirectiveLocation::InlineFragment,
],
&[registry.arg_with_default::<String>(
arcstr::literal!("reason"),
&"No longer supported".into(),
&(),
)],
&[registry.arg::<bool>(arcstr::literal!("if"), &())],
false,
)
}
Expand Down
3 changes: 1 addition & 2 deletions juniper/src/schema/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,7 @@ impl<'a, S: ScalarValue + 'a> TypeType<'a, S> {
fn is_one_of(&self) -> Option<bool> {
match self {
Self::Concrete(t) => match t {
// TODO: Implement once `@oneOf` is implemented for input objects.
MetaType::InputObject(InputObjectMeta { .. }) => Some(false),
MetaType::InputObject(InputObjectMeta { is_one_of, .. }) => Some(*is_one_of),
MetaType::Enum(..)
| MetaType::Interface(..)
| MetaType::List(..)
Expand Down
51 changes: 34 additions & 17 deletions juniper/src/schema/translate/graphql_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ impl GraphQLParserTranslator {
default_value: default_value
.as_ref()
.map(|x| GraphQLParserTranslator::translate_value(x)),
directives: generate_directives(deprecation_status),
directives: deprecation_directive(deprecation_status)
.map(|d| vec![d])
.unwrap_or_default(),
}
}

Expand Down Expand Up @@ -159,7 +161,7 @@ impl GraphQLParserTranslator {
name: name.as_str().into(),
directives: specified_by_url
.as_deref()
.map(|url| vec![specified_by_url_to_directive(url)])
.map(|url| vec![specified_by_url_directive(url)])
.unwrap_or_default(),
}),
meta::MetaType::Enum(meta::EnumMeta {
Expand Down Expand Up @@ -209,12 +211,15 @@ impl GraphQLParserTranslator {
name,
description,
input_fields,
is_one_of,
try_parse_fn: _,
}) => schema::TypeDefinition::InputObject(schema::InputObjectType {
position: Pos::default(),
description: description.as_deref().map(Into::into),
name: name.as_str().into(),
directives: vec![],
directives: is_one_of
.then(|| vec![one_of_directive()])
.unwrap_or_default(),
fields: input_fields
.iter()
.filter(|x| !x.is_builtin())
Expand Down Expand Up @@ -255,7 +260,9 @@ impl GraphQLParserTranslator {
position: Pos::default(),
name: name.as_str().into(),
description: description.as_deref().map(Into::into),
directives: generate_directives(deprecation_status),
directives: deprecation_directive(deprecation_status)
.map(|d| vec![d])
.unwrap_or_default(),
}
}

Expand All @@ -275,7 +282,9 @@ impl GraphQLParserTranslator {
position: Pos::default(),
name: name.as_str().into(),
description: description.as_deref().map(Into::into),
directives: generate_directives(deprecation_status),
directives: deprecation_directive(deprecation_status)
.map(|d| vec![d])
.unwrap_or_default(),
field_type: GraphQLParserTranslator::translate_type(field_type),
arguments: arguments
.as_ref()
Expand All @@ -290,7 +299,11 @@ impl GraphQLParserTranslator {
}
}

fn deprecation_to_directive<'a, T>(
/// Forms a [`@deprecated(reason:)`] [`schema::Directive`] out of the provided
/// [`meta::DeprecationStatus`].
///
/// [`@deprecated(reason:)`]: https://spec.graphql.org/September2025#sec--deprecated
fn deprecation_directive<'a, T>(
status: &meta::DeprecationStatus,
) -> Option<schema::Directive<'a, T>>
where
Expand All @@ -309,28 +322,32 @@ where
}
}

/// Returns the `@specifiedBy(url:)` [`schema::Directive`] for the provided `url`.
fn specified_by_url_to_directive<'a, T>(url: &str) -> schema::Directive<'a, T>
/// Forms a [`@oneOf`] [`schema::Directive`].
///
/// [`@oneOf`]: https://spec.graphql.org/September2025#sec--oneOf
fn one_of_directive<'a, T>() -> schema::Directive<'a, T>
where
T: schema::Text<'a>,
{
schema::Directive {
position: Pos::default(),
name: "specifiedBy".into(),
arguments: vec![("url".into(), schema::Value::String(url.into()))],
name: "oneOf".into(),
arguments: vec![],
}
}

// Right now the only directive supported is `@deprecated`.
// `@skip` and `@include` are dealt with elsewhere.
// https://spec.graphql.org/October2021#sec-Type-System.Directives.Built-in-Directives
fn generate_directives<'a, T>(status: &meta::DeprecationStatus) -> Vec<schema::Directive<'a, T>>
/// Forms a `@specifiedBy(url:)` [`schema::Directive`] out of the provided `url`.
///
/// [`@specifiedBy(url:)`]: https://spec.graphql.org/September2025#sec--specifiedBy
fn specified_by_url_directive<'a, T>(url: &str) -> schema::Directive<'a, T>
where
T: schema::Text<'a>,
{
deprecation_to_directive(status)
.map(|d| vec![d])
.unwrap_or_default()
schema::Directive {
position: Pos::default(),
name: "specifiedBy".into(),
arguments: vec![("url".into(), schema::Value::String(url.into()))],
}
}

/// Sorts the provided [`schema::Document`] in the "type-then-name" manner.
Expand Down
Loading