Skip to content

feat: use ArcStr for storing strings in Schema #1247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
May 13, 2025
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
3 changes: 1 addition & 2 deletions benches/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ impl Query {
}
}

pub fn new_schema() -> RootNode<'static, Query, EmptyMutation<Context>, EmptySubscription<Context>>
{
pub fn new_schema() -> RootNode<Query, EmptyMutation<Context>, EmptySubscription<Context>> {
RootNode::new(Query, EmptyMutation::new(), EmptySubscription::new())
}

Expand Down
4 changes: 2 additions & 2 deletions book/src/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl Mutation {

// Root schema consists of a query, a mutation, and a subscription.
// Request queries can be executed against a `RootNode`.
type Schema = juniper::RootNode<'static, Query, Mutation, EmptySubscription<Context>>;
type Schema = juniper::RootNode<Query, Mutation, EmptySubscription<Context>>;
#
# fn main() {
# _ = Schema::new(Query, Mutation, EmptySubscription::new());
Expand Down Expand Up @@ -190,7 +190,7 @@ impl Query {
}
}

type Schema = juniper::RootNode<'static, Query, EmptyMutation<Ctx>, EmptySubscription<Ctx>>;
type Schema = juniper::RootNode<Query, EmptyMutation<Ctx>, EmptySubscription<Ctx>>;

fn main() {
// Create a context.
Expand Down
4 changes: 2 additions & 2 deletions book/src/schema/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl Mutation {
}
}

type Schema = RootNode<'static, Query, Mutation, EmptySubscription>;
type Schema = RootNode<Query, Mutation, EmptySubscription>;
#
# fn main() {}
```
Expand Down Expand Up @@ -138,7 +138,7 @@ impl Query {
}
}

type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>;
type Schema = RootNode<Query, EmptyMutation, EmptySubscription>;

fn main() {
// Run the built-in introspection query.
Expand Down
2 changes: 1 addition & 1 deletion book/src/schema/introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl Query {
}
}

type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>;
type Schema = RootNode<Query, EmptyMutation, EmptySubscription>;

fn main() {
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new())
Expand Down
2 changes: 1 addition & 1 deletion book/src/schema/subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ While we can implement [`SubscriptionCoordinator`] ourselves, [Juniper] contains
# }
# }
#
type Schema = RootNode<'static, Query, EmptyMutation<Database>, Subscription>;
type Schema = RootNode<Query, EmptyMutation<Database>, Subscription>;

fn schema() -> Schema {
Schema::new(Query, EmptyMutation::new(), Subscription)
Expand Down
36 changes: 36 additions & 0 deletions juniper/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,39 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
- `uuid::Uuid`.
- Renamed `ObjectId` scalar to `ObjectID` in types: ([#1277])
- `bson::oid::ObjectId`.
- Optimized schema implementation with [`arcstr` crate]: ([#1247], [#819])
- `ast::Type`:
- Removed lifetime parameters.
- Made it generic over string type.
- `MetaType`:
- Removed lifetime parameters.
- Made `name()`, `description()` and `specified_by_url()` methods returning `ArcStr`.
- `EnumMeta`, `InputObjectMeta`, `InterfaceMeta`, `ListMeta`, `NullableMeta`, `ObjectMeta`, `PlaceholderMeta`, `ScalarMeta` and `UnionMeta`:
- Removed lifetime parameters.
- `meta::Field` and `meta::Argument`:
- Removed lifetime parameters.
- `meta::EnumValue`:
- Made `name` and `description` fields using `ArcStr`.
- `DeprecationStatus`:
- Made `Deprecated` variant using `ArcStr`.
- Made `reason()` method returning `ArcStr`.
- `DirectiveType`:
- Removed lifetime parameters.
- Made `name` and `description` fields using `ArcStr`.
- `SchemaType`:
- Removed lifetime parameters.
- Made `is_subtype()` method accepting `DynType` instead of `Type`.
- `RootNode`:
- Removed lifetime parameters.
- `Registry`:
- Removed lifetime parameters.
- `types::Name` and `types::NameParseError`:
- Made fields using `ArcStr` instead of `String`.
- Replaced `FromStr` impl of `types::Name` with `new()` constructor.
- `GraphQLType`:
- Made `name()` method returning `ArcStr`.
- `GraphQLValue`:
- Made `type_name()` method returning `ArcStr`.

### Added

Expand All @@ -53,6 +86,8 @@ All user visible changes to `juniper` crate will be documented in this file. Thi

- Incorrect error propagation inside fragments. ([#1318], [#1287])

[#819]: /../../issues/819
[#1247]: /../../pull/1247
[#1252]: /../../pull/1252
[#1270]: /../../issues/1270
[#1271]: /../../pull/1271
Expand Down Expand Up @@ -264,6 +299,7 @@ See [old CHANGELOG](/../../blob/juniper-v0.15.12/juniper/CHANGELOG.md).


[`anyhow` crate]: https://docs.rs/anyhow
[`arcstr` crate]: https://docs.rs/arcstr
[`bigdecimal` crate]: https://docs.rs/bigdecimal
[`bson` crate]: https://docs.rs/bson
[`chrono` crate]: https://docs.rs/chrono
Expand Down
1 change: 1 addition & 0 deletions juniper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ uuid = ["dep:uuid"]

[dependencies]
anyhow = { version = "1.0.47", optional = true }
arcstr = { version = "1.1", default-features = false }
async-trait = "0.1.39"
auto_enums = "0.8"
bigdecimal = { version = "0.4", optional = true }
Expand Down
144 changes: 77 additions & 67 deletions juniper/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::{borrow::Cow, fmt, hash::Hash, slice, vec};

use arcstr::ArcStr;

use indexmap::IndexMap;

use crate::{
Expand All @@ -8,24 +10,70 @@ use crate::{
value::{DefaultScalarValue, ScalarValue},
};

/// A type literal in the syntax tree
/// Type literal in a syntax tree.
///
/// This enum carries no semantic information and might refer to types that do
/// not exist.
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Type<'a> {
/// A nullable named type, e.g. `String`
Named(Cow<'a, str>),
/// A nullable list type, e.g. `[String]`
/// This enum carries no semantic information and might refer to types that do not exist.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Type<N = ArcStr> {
/// `null`able named type, e.g. `String`.
Named(N),

/// `null`able list type, e.g. `[String]`.
///
/// The list itself is what's nullable, the containing type might be non-null.
List(Box<Type<'a>>, Option<usize>),
/// A non-null named type, e.g. `String!`
NonNullNamed(Cow<'a, str>),
/// A non-null list type, e.g. `[String]!`.
/// The list itself is `null`able, the containing [`Type`] might be non-`null`.
List(Box<Type<N>>, Option<usize>),

/// Non-`null` named type, e.g. `String!`.
NonNullNamed(N),

/// Non-`null` list type, e.g. `[String]!`.
///
/// The list itself is what's non-null, the containing type might be null.
NonNullList(Box<Type<'a>>, Option<usize>),
/// The list itself is non-`null`, the containing [`Type`] might be `null`able.
NonNullList(Box<Type<N>>, Option<usize>),
}

impl<N: fmt::Display> fmt::Display for Type<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Named(n) => write!(f, "{n}"),
Self::NonNullNamed(n) => write!(f, "{n}!"),
Self::List(t, _) => write!(f, "[{t}]"),
Self::NonNullList(t, _) => write!(f, "[{t}]!"),
}
}
}

impl<N: AsRef<str>> Type<N> {
/// Returns the name of this named [`Type`].
///
/// Only applies to named [`Type`]s. Lists will return [`None`].
#[must_use]
pub fn name(&self) -> Option<&str> {
match self {
Self::Named(n) | Self::NonNullNamed(n) => Some(n.as_ref()),
Self::List(..) | Self::NonNullList(..) => None,
}
}

/// Returns the innermost name of this [`Type`] by unpacking lists.
///
/// All [`Type`] literals contain exactly one named type.
#[must_use]
pub fn innermost_name(&self) -> &str {
match self {
Self::Named(n) | Self::NonNullNamed(n) => n.as_ref(),
Self::List(l, ..) | Self::NonNullList(l, ..) => l.innermost_name(),
}
}

/// Indicates whether this [`Type`] can only represent non-`null` values.
#[must_use]
pub fn is_non_null(&self) -> bool {
match self {
Self::NonNullList(..) | Self::NonNullNamed(..) => true,
Self::List(..) | Self::Named(..) => false,
}
}
}

/// A JSON-like value that can be passed into the query execution, either
Expand All @@ -34,8 +82,8 @@ pub enum Type<'a> {
///
/// Lists and objects variants are _spanned_, i.e. they contain a reference to
/// their position in the source file, if available.
#[derive(Clone, Debug, PartialEq)]
#[expect(missing_docs, reason = "self-explanatory")]
#[derive(Clone, Debug, PartialEq)]
pub enum InputValue<S = DefaultScalarValue> {
Null,
Scalar(S),
Expand All @@ -45,24 +93,24 @@ pub enum InputValue<S = DefaultScalarValue> {
Object(Vec<(Spanning<String>, Spanning<InputValue<S>>)>),
}

#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct VariableDefinition<'a, S> {
pub var_type: Spanning<Type<'a>>,
pub var_type: Spanning<Type<&'a str>>,
pub default_value: Option<Spanning<InputValue<S>>>,
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
}

#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Arguments<'a, S> {
pub items: Vec<(Spanning<&'a str>, Spanning<InputValue<S>>)>,
}

#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct VariableDefinitions<'a, S> {
pub items: Vec<(Spanning<&'a str>, VariableDefinition<'a, S>)>,
}

#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Field<'a, S> {
pub alias: Option<Spanning<&'a str>>,
pub name: Spanning<&'a str>,
Expand All @@ -71,13 +119,13 @@ pub struct Field<'a, S> {
pub selection_set: Option<Vec<Selection<'a, S>>>,
}

#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct FragmentSpread<'a, S> {
pub name: Spanning<&'a str>,
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
}

#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct InlineFragment<'a, S> {
pub type_condition: Option<Spanning<&'a str>>,
pub directives: Option<Vec<Spanning<Directive<'a, S>>>>,
Expand All @@ -99,15 +147,15 @@ pub struct InlineFragment<'a, S> {
/// }
/// }
/// ```
#[derive(Clone, PartialEq, Debug)]
#[expect(missing_docs, reason = "self-explanatory")]
#[derive(Clone, Debug, PartialEq)]
pub enum Selection<'a, S = DefaultScalarValue> {
Field(Spanning<Field<'a, S>>),
FragmentSpread(Spanning<FragmentSpread<'a, S>>),
InlineFragment(Spanning<InlineFragment<'a, S>>),
}

#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Directive<'a, S> {
pub name: Spanning<&'a str>,
pub arguments: Option<Spanning<Arguments<'a, S>>>,
Expand All @@ -122,7 +170,7 @@ pub enum OperationType {
}

#[expect(missing_docs, reason = "self-explanatory")]
#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Operation<'a, S> {
pub operation_type: OperationType,
pub name: Option<Spanning<&'a str>>,
Expand All @@ -131,7 +179,7 @@ pub struct Operation<'a, S> {
pub selection_set: Vec<Selection<'a, S>>,
}

#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Fragment<'a, S> {
pub name: Spanning<&'a str>,
pub type_condition: Spanning<&'a str>,
Expand All @@ -140,7 +188,7 @@ pub struct Fragment<'a, S> {
}

#[doc(hidden)]
#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub enum Definition<'a, S> {
Operation(Spanning<Operation<'a, S>>),
Fragment(Spanning<Fragment<'a, S>>),
Expand Down Expand Up @@ -194,44 +242,6 @@ pub trait ToInputValue<S = DefaultScalarValue>: Sized {
fn to_input_value(&self) -> InputValue<S>;
}

impl Type<'_> {
/// Get the name of a named type.
///
/// Only applies to named types; lists will return `None`.
pub fn name(&self) -> Option<&str> {
match *self {
Type::Named(ref n) | Type::NonNullNamed(ref n) => Some(n),
_ => None,
}
}

/// Get the innermost name by unpacking lists
///
/// All type literals contain exactly one named type.
pub fn innermost_name(&self) -> &str {
match *self {
Type::Named(ref n) | Type::NonNullNamed(ref n) => n,
Type::List(ref l, _) | Type::NonNullList(ref l, _) => l.innermost_name(),
}
}

/// Determines if a type only can represent non-null values.
pub fn is_non_null(&self) -> bool {
matches!(*self, Type::NonNullNamed(_) | Type::NonNullList(..))
}
}

impl fmt::Display for Type<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Named(n) => write!(f, "{n}"),
Self::NonNullNamed(n) => write!(f, "{n}!"),
Self::List(t, _) => write!(f, "[{t}]"),
Self::NonNullList(t, _) => write!(f, "[{t}]!"),
}
}
}

impl<S> InputValue<S> {
/// Construct a `null` value.
pub fn null() -> Self {
Expand Down Expand Up @@ -574,7 +584,7 @@ impl<'a, S> Arguments<'a, S> {
}

impl<'a, S> VariableDefinitions<'a, S> {
pub fn iter(&self) -> slice::Iter<(Spanning<&'a str>, VariableDefinition<S>)> {
pub fn iter(&self) -> slice::Iter<(Spanning<&'a str>, VariableDefinition<'a, S>)> {
self.items.iter()
}
}
Expand Down
Loading