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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ endif

book.build:
mdbook build book/ $(if $(call eq,$(out),),,-d $(out))
rm -rf $(or $(out),book/_rendered)/lib.rs


# Spellcheck Book.
Expand Down
2 changes: 1 addition & 1 deletion book/src/types/enums.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ enum Episode {
#
# fn main() {}
```
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `camelCase` and `none` (disables any renaming).
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `snake_case`, `camelCase` and `none` (disables any renaming).


### Documentation and deprecation
Expand Down
88 changes: 79 additions & 9 deletions book/src/types/input_objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ In [Juniper], defining a [GraphQL input object][0] is quite straightforward and
#[derive(GraphQLInputObject)]
struct Coordinate {
latitude: f64,
longitude: f64
longitude: f64,
}

struct Root;
Expand All @@ -32,27 +32,48 @@ impl Root {
# fn main() {}
```

[`@oneOf`] [input objects][0] could be defined by using the [`#[derive(GraphQLInputObject)]` attribute][2] on a [Rust enum][enum]:
```rust
# #![expect(unused_variables, reason = "example")]
# extern crate juniper;
# use juniper::{GraphQLInputObject, ID};
#
#[derive(GraphQLInputObject)]
enum UserBy {
Id(ID), // Every `enum` variant declares a `Null`able input object field,
Name(String), // so there is no need to use `Option<String>` explicitly.
}
#
# fn main() {}
```


### Renaming

Just as with [defining GraphQL objects](objects/index.md#renaming), by default [struct] fields are converted from [Rust]'s standard `snake_case` naming convention into [GraphQL]'s `camelCase` convention:
Just as with [defining GraphQL objects](objects/index.md#renaming), by default [struct] fields (or [enum] variants) are converted from [Rust]'s standard naming convention into [GraphQL]'s `camelCase` convention:
```rust
# extern crate juniper;
# use juniper::GraphQLInputObject;
# use juniper::{GraphQLInputObject, ID};
#
#[derive(GraphQLInputObject)]
struct Person {
first_name: String, // exposed as `firstName` in GraphQL schema
last_name: String, // exposed as `lastName` in GraphQL schema
}

#[derive(GraphQLInputObject)]
enum UserBy {
Id(ID), // exposed as `id` in GraphQL schema
Name(String), // exposed as `name` in GraphQL schema
}
#
# fn main() {}
```

We can override the name by using the `#[graphql(name = "...")]` attribute:
```rust
# extern crate juniper;
# use juniper::GraphQLInputObject;
# use juniper::{GraphQLInputObject, ID};
#
#[derive(GraphQLInputObject)]
#[graphql(name = "WebPerson")] // now exposed as `WebPerson` in GraphQL schema
Expand All @@ -62,14 +83,22 @@ struct Person {
#[graphql(name = "websiteURL")]
website_url: Option<String>, // now exposed as `websiteURL` in GraphQL schema
}

#[derive(GraphQLInputObject)]
#[graphql(name = "By")] // now exposed as `By` in GraphQL schema
enum UserBy {
#[graphql(name = "ID")]
Id(ID), // now exposed as `ID` in GraphQL schema
Name(String),
}
#
# fn main() {}
```

Or provide a different renaming policy for all the [struct] fields:
```rust
# extern crate juniper;
# use juniper::GraphQLInputObject;
# use juniper::{GraphQLInputObject, ID};
#
#[derive(GraphQLInputObject)]
#[graphql(rename_all = "none")] // disables any renaming
Expand All @@ -78,18 +107,25 @@ struct Person {
age: i32,
website_url: Option<String>, // exposed as `website_url` in GraphQL schema
}

#[derive(GraphQLInputObject)]
#[graphql(rename_all = "none")] // disables any renaming
enum UserBy {
Id(ID), // exposed as `Id` in GraphQL schema
Name(String), // exposed as `Name` in GraphQL schema
}
#
# fn main() {}
```
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `camelCase` and `none` (disables any renaming).
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `snake_case`, `camelCase` and `none` (disables any renaming).


### Documentation and deprecation

Similarly, [GraphQL input fields][1] may also be [documented][7] and [deprecated][9] via `#[graphql(description = "...")]` and `#[graphql(deprecated = "...")]`/[`#[deprecated]`][13] attributes:
```rust
# extern crate juniper;
# use juniper::GraphQLInputObject;
# use juniper::{GraphQLInputObject, ID};
#
/// This doc comment is visible only in Rust API docs.
#[derive(GraphQLInputObject)]
Expand All @@ -112,6 +148,28 @@ struct Person {
#[deprecated]
another: Option<f64>, // has no description in GraphQL schema
}

/// This doc comment is visible only in Rust API docs.
#[derive(GraphQLInputObject)]
#[graphql(description = "This description is visible only in GraphQL schema.")]
enum UserBy {
/// This doc comment is visible only in Rust API docs.
#[graphql(desc = "This description is visible only in GraphQL schema.")]
// ^^^^ shortcut for a `description` argument
Id(ID),

/// This doc comment is visible in both Rust API docs and GraphQL schema
/// descriptions.
// `enum` variants represent `Null`able input fields already, so can be naturally
// deprecated without any default values.
#[graphql(deprecated = "Just because.")]
Name(String),

// If no explicit deprecation reason is provided,
// then the default "No longer supported" one is used.
#[deprecated]
Bio(String), // has no description in GraphQL schema
}
#
# fn main() {}
```
Expand All @@ -120,11 +178,11 @@ struct Person {

### Ignoring

By default, all [struct] fields are included into the generated [GraphQL input object][0] type. To prevent inclusion of a specific field annotate it with the `#[graphql(ignore)]` attribute:
By default, all [struct] fields (or [enum] variants) are included into the generated [GraphQL input object][0] type. To prevent inclusion of a specific field/variant annotate it with the `#[graphql(ignore)]` attribute:
> **WARNING**: Ignored fields must either implement `Default` or be annotated with the `#[graphql(default = <expression>)]` argument.
```rust
# extern crate juniper;
# use juniper::GraphQLInputObject;
# use juniper::{GraphQLInputObject, ID};
#
enum System {
Cartesian,
Expand All @@ -146,6 +204,15 @@ struct Point2D {
// ^^^^ alternative naming, up to your preference
shift: f64,
}

#[derive(GraphQLInputObject)]
enum UserBy {
Id(ID),
// Ignored `enum` variants naturally doesn't require `Default` implementation or
// `default` value being specified, as they're just never constructed from an input.
#[graphql(ignore)]
Name(String),
}
#
# fn main() {}
```
Expand All @@ -154,6 +221,9 @@ struct Point2D {




[`@oneOf`]: https://spec.graphql.org/September2025#sec--oneOf
[enum]: https://doc.rust-lang.org/stable/reference/items/enumerations.html
[GraphQL]: https://graphql.org
[Juniper]: https://docs.rs/juniper
[Rust]: https://www.rust-lang.org
Expand Down
2 changes: 1 addition & 1 deletion book/src/types/interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ trait Person {
#
# fn main() {}
```
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `camelCase` and `none` (disables any renaming).
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `snake_case`, `camelCase` and `none` (disables any renaming).


### Documentation and deprecation
Expand Down
2 changes: 1 addition & 1 deletion book/src/types/objects/complex_fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl Person {
#
# fn main() {}
```
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `camelCase` and `none` (disables any renaming).
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `snake_case`, `camelCase` and `none` (disables any renaming).


### Documentation and deprecation
Expand Down
2 changes: 1 addition & 1 deletion book/src/types/objects/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ struct Person {
#
# fn main() {}
```
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `camelCase` and `none` (disables any renaming).
> **TIP**: Supported policies are: `SCREAMING_SNAKE_CASE`, `snake_case`, `camelCase` and `none` (disables any renaming).


### Deprecation
Expand Down
10 changes: 9 additions & 1 deletion juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ 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], [#1062], [#1055], [graphql/graphql-spec#825])
- `@oneOf` built-in directive.
- `__Type.isOneOf` field. ([#1348])
- `schema::meta::InputObjectMeta::is_one_of` field.
- `enum`s support to `#[derive(GraphQLInputObject)]` macro.
- `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 All @@ -37,6 +41,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- 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])
- Support of `#[graphql(rename_all = "snake_case")]` attribute in macros. ([#1354])

### Changed

Expand All @@ -50,10 +55,13 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- Incorrect double escaping in `ScalarToken::String` `Display`ing. ([#1349])

[#864]: /../../issues/864
[#1055]: /../../issues/1055
[#1062]: /../../issues/1062
[#1347]: /../../issues/1347
[#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
Loading