Skip to content

Commit

Permalink
Introduce a abstraction for scalar values
Browse files Browse the repository at this point in the history
Before this possible scalar values where hard coded to be representable
in one of the following types: `i32`, `f64`, `String` or `bool`. This
restricts what types of custom scalar values could be defined. (For
example it was not possible to define a scalar value that represents a
`i64` without mapping it to a string (that would be not efficient))

One solution to fix that example above would simply be to change the
internal representation to allow it represent a `i64`, but this would
only fix the problem for one type (till someone want's to support
`i128` for example…). Also this would make juniper not following the
graphql standard closely.

This commit tries to explore another approach, by making the exact "internal"
representation of scalar values swappable (in such a way that a down
stream crate could provide it's own representation tailored to their
needs). This allows juniper to provide a default type that only
contains the types described in the standard whereas other crates
could define custom scalars following their needs.

To that we need to change several things in the current implementation
* Add some traits that abstract the behaviour of such a scalar value representation
* Change `Value` and `InputValue` to have a scalar variant (with a
  generic type) instead of hard coded variants for the standard
  types. This implies adding a generic parameter to both enums that
  needs to be added in the whole crate.
* Change the parser to allow deciding between different types of
  scalar values. The problem is basically that the original parser
  implementation had no way to know whether a parsed integer number is
  a `i32` or a `i64` for example. To fix this we added some knowlege
  from the existing schema to the parser.
* Fix some macros and derives to follow the new behaviour

This also contains a unrelated change about the way `juniper_codegen`
resolves items from juniper. In detail the `_internal` flag is removed
the the resolution is replaced by an macro. This is a drive by change
because I was annoyed of adding new items to that list.
  • Loading branch information
weiznich committed Sep 14, 2018
1 parent ec59766 commit 805db63
Show file tree
Hide file tree
Showing 87 changed files with 6,003 additions and 3,308 deletions.
246 changes: 136 additions & 110 deletions juniper/src/ast.rs

Large diffs are not rendered by default.

139 changes: 74 additions & 65 deletions juniper/src/executor/look_ahead.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use ast::{Directive, Fragment, InputValue, Selection};
use parser::Spanning;
use value::ScalarValue;

use std::collections::HashMap;

Expand All @@ -21,25 +22,22 @@ pub enum Applies<'a> {
/// meaning that variables are already resolved.
#[derive(Debug, Clone, PartialEq)]
#[allow(missing_docs)]
pub enum LookAheadValue<'a> {
pub enum LookAheadValue<'a, S: 'a> {
Null,
Int(i32),
Float(f64),
String(&'a str),
Boolean(bool),
Scalar(&'a S),
Enum(&'a str),
List(Vec<LookAheadValue<'a>>),
Object(Vec<(&'a str, LookAheadValue<'a>)>),
List(Vec<LookAheadValue<'a, S>>),
Object(Vec<(&'a str, LookAheadValue<'a, S>)>),
}

impl<'a> LookAheadValue<'a> {
fn from_input_value(input_value: &'a InputValue, vars: &'a Variables) -> Self {
impl<'a, S> LookAheadValue<'a, S>
where
S: ScalarValue,
{
fn from_input_value(input_value: &'a InputValue<S>, vars: &'a Variables<S>) -> Self {
match *input_value {
InputValue::Null => LookAheadValue::Null,
InputValue::Int(i) => LookAheadValue::Int(i),
InputValue::Float(f) => LookAheadValue::Float(f),
InputValue::String(ref s) => LookAheadValue::String(s),
InputValue::Boolean(b) => LookAheadValue::Boolean(b),
InputValue::Scalar(ref s) => LookAheadValue::Scalar(s),
InputValue::Enum(ref e) => LookAheadValue::Enum(e),
InputValue::Variable(ref v) => Self::from_input_value(vars.get(v).unwrap(), vars),
InputValue::List(ref l) => LookAheadValue::List(
Expand All @@ -54,24 +52,26 @@ impl<'a> LookAheadValue<'a> {
&n.item as &str,
LookAheadValue::from_input_value(&i.item, vars),
)
})
.collect(),
}).collect(),
),
}
}
}

/// An argument passed into the query
#[derive(Debug, Clone, PartialEq)]
pub struct LookAheadArgument<'a> {
pub struct LookAheadArgument<'a, S: 'a> {
name: &'a str,
value: LookAheadValue<'a>,
value: LookAheadValue<'a, S>,
}

impl<'a> LookAheadArgument<'a> {
impl<'a, S> LookAheadArgument<'a, S>
where
S: ScalarValue,
{
pub(super) fn new(
&(ref name, ref value): &'a (Spanning<&'a str>, Spanning<InputValue>),
vars: &'a Variables,
&(ref name, ref value): &'a (Spanning<&'a str>, Spanning<InputValue<S>>),
vars: &'a Variables<S>,
) -> Self {
LookAheadArgument {
name: name.item,
Expand All @@ -80,86 +80,96 @@ impl<'a> LookAheadArgument<'a> {
}

/// The value of the argument
pub fn value(&'a self) -> &LookAheadValue<'a> {
pub fn value(&'a self) -> &LookAheadValue<'a, S> {
&self.value
}
}

#[derive(Debug, Clone, PartialEq)]
pub struct ChildSelection<'a> {
pub(super) inner: LookAheadSelection<'a>,
pub struct ChildSelection<'a, S: 'a> {
pub(super) inner: LookAheadSelection<'a, S>,
pub(super) applies_for: Applies<'a>,
}

/// A selection performed by a query
#[derive(Debug, Clone, PartialEq)]
pub struct LookAheadSelection<'a> {
pub struct LookAheadSelection<'a, S: 'a> {
pub(super) name: &'a str,
pub(super) alias: Option<&'a str>,
pub(super) arguments: Vec<LookAheadArgument<'a>>,
pub(super) children: Vec<ChildSelection<'a>>,
pub(super) arguments: Vec<LookAheadArgument<'a, S>>,
pub(super) children: Vec<ChildSelection<'a, S>>,
}

impl<'a> LookAheadSelection<'a> {
fn should_include(directives: Option<&Vec<Spanning<Directive>>>, vars: &Variables) -> bool {
impl<'a, S> LookAheadSelection<'a, S>
where
S: ScalarValue,
&'a S: Into<Option<bool>>,
{
fn should_include<'b, 'c>(
directives: Option<&'b Vec<Spanning<Directive<S>>>>,
vars: &'c Variables<S>,
) -> bool
where
'b: 'a,
'c: 'a,
{
directives
.map(|d| {
d.iter().all(|d| {
let d = &d.item;
let arguments = &d.arguments;
match (d.name.item, arguments) {
("include", &Some(ref a)) => a.item
("include", &Some(ref a)) => a
.item
.items
.iter()
.find(|item| item.0.item == "if")
.map(|&(_, ref v)| {
if let LookAheadValue::Boolean(b) =
if let LookAheadValue::Scalar(s) =
LookAheadValue::from_input_value(&v.item, vars)
{
b
s.into().unwrap_or(false)
} else {
false
}
})
.unwrap_or(false),
("skip", &Some(ref a)) => a.item
}).unwrap_or(false),
("skip", &Some(ref a)) => a
.item
.items
.iter()
.find(|item| item.0.item == "if")
.map(|&(_, ref v)| {
if let LookAheadValue::Boolean(b) =
if let LookAheadValue::Scalar(b) =
LookAheadValue::from_input_value(&v.item, vars)
{
!b
b.into().map(::std::ops::Not::not).unwrap_or(false)
} else {
false
}
})
.unwrap_or(false),
}).unwrap_or(false),
("skip", &None) => false,
("include", &None) => true,
(_, _) => unreachable!(),
}
})
})
.unwrap_or(true)
}).unwrap_or(true)
}

pub(super) fn build_from_selection(
s: &'a Selection<'a>,
vars: &'a Variables,
fragments: &'a HashMap<&'a str, &'a Fragment<'a>>,
) -> LookAheadSelection<'a> {
s: &'a Selection<'a, S>,
vars: &'a Variables<S>,
fragments: &'a HashMap<&'a str, &'a Fragment<'a, S>>,
) -> LookAheadSelection<'a, S> {
Self::build_from_selection_with_parent(s, None, vars, fragments).unwrap()
}

fn build_from_selection_with_parent(
s: &'a Selection<'a>,
s: &'a Selection<'a, S>,
parent: Option<&mut Self>,
vars: &'a Variables,
fragments: &'a HashMap<&'a str, &'a Fragment<'a>>,
) -> Option<LookAheadSelection<'a>> {
let empty: &[Selection] = &[];
vars: &'a Variables<S>,
fragments: &'a HashMap<&'a str, &'a Fragment<'a, S>>,
) -> Option<LookAheadSelection<'a, S>> {
let empty: &[Selection<S>] = &[];
match *s {
Selection::Field(ref field) => {
let field = &field.item;
Expand All @@ -178,8 +188,7 @@ impl<'a> LookAheadSelection<'a> {
.iter()
.map(|p| LookAheadArgument::new(p, vars))
.collect()
})
.unwrap_or_else(Vec::new);
}).unwrap_or_else(Vec::new);
let mut ret = LookAheadSelection {
name,
alias,
Expand Down Expand Up @@ -256,18 +265,18 @@ impl<'a> LookAheadSelection<'a> {
}

/// Convert a eventually type independent selection into one for a concrete type
pub fn for_explicit_type(&self, type_name: &str) -> ConcreteLookAheadSelection<'a> {
pub fn for_explicit_type(&self, type_name: &str) -> ConcreteLookAheadSelection<'a, S> {
ConcreteLookAheadSelection {
children: self.children
children: self
.children
.iter()
.filter_map(|c| match c.applies_for {
Applies::OnlyType(ref t) if *t == type_name => {
Some(c.inner.for_explicit_type(type_name))
}
Applies::All => Some(c.inner.for_explicit_type(type_name)),
Applies::OnlyType(_) => None,
})
.collect(),
}).collect(),
name: self.name,
alias: self.alias,
arguments: self.arguments.clone(),
Expand All @@ -277,15 +286,15 @@ impl<'a> LookAheadSelection<'a> {

/// A selection performed by a query on a concrete type
#[derive(Debug, PartialEq)]
pub struct ConcreteLookAheadSelection<'a> {
pub struct ConcreteLookAheadSelection<'a, S: 'a> {
name: &'a str,
alias: Option<&'a str>,
arguments: Vec<LookAheadArgument<'a>>,
children: Vec<ConcreteLookAheadSelection<'a>>,
arguments: Vec<LookAheadArgument<'a, S>>,
children: Vec<ConcreteLookAheadSelection<'a, S>>,
}

/// A set of common methods for `ConcreteLookAheadSelection` and `LookAheadSelection`
pub trait LookAheadMethods {
pub trait LookAheadMethods<S> {
/// Get the name of the field represented by the current selection
fn field_name(&self) -> &str;

Expand All @@ -298,15 +307,15 @@ pub trait LookAheadMethods {
}

/// Get the top level arguments for the current selection
fn arguments(&self) -> &[LookAheadArgument];
fn arguments(&self) -> &[LookAheadArgument<S>];

/// Get the top level argument with a given name from the current selection
fn argument(&self, name: &str) -> Option<&LookAheadArgument> {
fn argument(&self, name: &str) -> Option<&LookAheadArgument<S>> {
self.arguments().iter().find(|a| a.name == name)
}
}

impl<'a> LookAheadMethods for ConcreteLookAheadSelection<'a> {
impl<'a, S> LookAheadMethods<S> for ConcreteLookAheadSelection<'a, S> {
fn field_name(&self) -> &str {
self.alias.unwrap_or(self.name)
}
Expand All @@ -315,12 +324,12 @@ impl<'a> LookAheadMethods for ConcreteLookAheadSelection<'a> {
self.children.iter().find(|c| c.name == name)
}

fn arguments(&self) -> &[LookAheadArgument] {
fn arguments(&self) -> &[LookAheadArgument<S>] {
&self.arguments
}
}

impl<'a> LookAheadMethods for LookAheadSelection<'a> {
impl<'a, S> LookAheadMethods<S> for LookAheadSelection<'a, S> {
fn field_name(&self) -> &str {
self.alias.unwrap_or(self.name)
}
Expand All @@ -332,7 +341,7 @@ impl<'a> LookAheadMethods for LookAheadSelection<'a> {
.map(|s| &s.inner)
}

fn arguments(&self) -> &[LookAheadArgument] {
fn arguments(&self) -> &[LookAheadArgument<S>] {
&self.arguments
}
}
Expand Down
Loading

0 comments on commit 805db63

Please sign in to comment.