From b78aef715d7b6a8ff646e9031b4cdacbda89094b Mon Sep 17 00:00:00 2001 From: Michael Macias Date: Fri, 6 Jan 2017 21:34:44 -0600 Subject: [PATCH] Make rustc-serialize optional This extracts rustc-serialize JSON serialization into its own module, decoupling all core structures from its use. rustc-serialize can now be completely disabled, removing it as a dependency if serde (or none) is used instead. It is, however, still the default serializer. In Cargo, the `default-features` field must be set to `false` to disable it, e.g., [dependencies.juniper] version = "0.6" default-features = false features = ["serde"] The Iron handlers still require rustc-serialize. The default values for attributes changed from being formatted as JSON to how it is defined in [the reference implementation][printer.js]. [printer.js]: https://github.com/graphql/graphql-js/blob/v0.8.2/src/language/printer.js --- Cargo.toml | 6 +- src/ast.rs | 104 +++++++++++++++++-------- src/integrations/mod.rs | 1 + src/integrations/rustc_serialize.rs | 114 ++++++++++++++++++++++++++++ src/lib.rs | 63 +-------------- src/schema/schema.rs | 4 +- src/value.rs | 16 ---- 7 files changed, 193 insertions(+), 115 deletions(-) create mode 100644 src/integrations/rustc_serialize.rs diff --git a/Cargo.toml b/Cargo.toml index a8750d4e..b7388e89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,13 +15,13 @@ harness = false path = "benches/bench.rs" [features] -default = [] +default = ["rustc-serialize"] nightly = [] -iron-handlers = ["iron"] +iron-handlers = ["iron", "rustc-serialize"] expose-test-schema = [] [dependencies] -rustc-serialize = "^0.3.19" +rustc-serialize = { version = "^0.3.19", optional = true } iron = { version = "^0.4.0", optional = true } serde = { version = "^0.8.21", optional = true } diff --git a/src/ast.rs b/src/ast.rs index 8d96b9ba..31023d3c 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -4,8 +4,6 @@ use std::hash::Hash; use std::vec; use std::slice; -use rustc_serialize::json::{ToJson, Json}; - use parser::Spanning; /// A type literal in the syntax tree @@ -266,26 +264,6 @@ impl InputValue { InputValue::Object(o) } - /// Convert a `Json` structure into an `InputValue`. - /// - /// This consumes the JSON instance. - /// - /// Notes: - /// * No enums or variables will be produced by this method. - /// * All lists and objects will be unlocated - pub fn from_json(json: Json) -> InputValue { - match json { - Json::I64(i) => InputValue::int(i), - Json::U64(u) => InputValue::float(u as f64), - Json::F64(f) => InputValue::float(f), - Json::String(s) => InputValue::string(s), - Json::Boolean(b) => InputValue::boolean(b), - Json::Array(a) => InputValue::list(a.into_iter().map(InputValue::from_json).collect()), - Json::Object(o) => InputValue::object(o.into_iter().map(|(k,v)| (k, InputValue::from_json(v))).collect()), - Json::Null => InputValue::null(), - } - } - /// Resolve all variables to their values. pub fn into_const(self, vars: &HashMap) -> InputValue { match self { @@ -403,17 +381,38 @@ impl InputValue { } } -impl ToJson for InputValue { - fn to_json(&self) -> Json { +impl fmt::Display for InputValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - InputValue::Null | InputValue::Variable(_) => Json::Null, - InputValue::Int(i) => Json::I64(i), - InputValue::Float(f) => Json::F64(f), - InputValue::String(ref s) | InputValue::Enum(ref s) => Json::String(s.clone()), - InputValue::Boolean(b) => Json::Boolean(b), - InputValue::List(ref l) => Json::Array(l.iter().map(|x| x.item.to_json()).collect()), - InputValue::Object(ref o) => Json::Object(o.iter().map(|&(ref k, ref v)| (k.item.clone(), v.item.to_json())).collect()), - } + InputValue::Null => write!(f, "null"), + InputValue::Int(v) => write!(f, "{}", v), + InputValue::Float(v) => write!(f, "{}", v), + InputValue::String(ref v) => write!(f, "\"{}\"", v), + InputValue::Boolean(v) => write!(f, "{}", v), + InputValue::Enum(ref v) => write!(f, "{}", v), + InputValue::Variable(ref v) => write!(f, "${}", v), + InputValue::List(ref v) => { + try!(write!(f, "[")); + + for (i, spanning) in v.iter().enumerate() { + try!(spanning.item.fmt(f)); + if i < v.len() - 1 { try!(write!(f, ", ")); } + } + + write!(f, "]") + }, + InputValue::Object(ref o) => { + try!(write!(f, "{{")); + + for (i, &(ref k, ref v)) in o.iter().enumerate() { + try!(write!(f, "{}: ", k.item)); + try!(v.item.fmt(f)); + if i < o.len() - 1 { try!(write!(f, ", ")); } + } + + write!(f, "}}") + } + } } } @@ -448,3 +447,44 @@ impl<'a> VariableDefinitions<'a> { self.items.iter() } } + +#[cfg(test)] +mod tests { + use super::InputValue; + use parser::Spanning; + + #[test] + fn test_input_value_fmt() { + let value = InputValue::null(); + assert_eq!(format!("{}", value), "null"); + + let value = InputValue::int(123); + assert_eq!(format!("{}", value), "123"); + + let value = InputValue::float(12.3); + assert_eq!(format!("{}", value), "12.3"); + + let value = InputValue::string("FOO".to_owned()); + assert_eq!(format!("{}", value), "\"FOO\""); + + let value = InputValue::boolean(true); + assert_eq!(format!("{}", value), "true"); + + let value = InputValue::enum_value("BAR".to_owned()); + assert_eq!(format!("{}", value), "BAR"); + + let value = InputValue::variable("baz".to_owned()); + assert_eq!(format!("{}", value), "$baz"); + + let list = vec![InputValue::int(1), InputValue::int(2)]; + let value = InputValue::list(list); + assert_eq!(format!("{}", value), "[1, 2]"); + + let object = vec![ + (Spanning::unlocated("foo".to_owned()), Spanning::unlocated(InputValue::int(1))), + (Spanning::unlocated("bar".to_owned()), Spanning::unlocated(InputValue::int(2))), + ]; + let value = InputValue::parsed_object(object); + assert_eq!(format!("{}", value), "{foo: 1, bar: 2}"); + } +} diff --git a/src/integrations/mod.rs b/src/integrations/mod.rs index 19a36c61..f597709d 100644 --- a/src/integrations/mod.rs +++ b/src/integrations/mod.rs @@ -1,2 +1,3 @@ #[cfg(feature="iron-handlers")] pub mod iron_handlers; +#[cfg(feature="rustc-serialize")] pub mod rustc_serialize; #[cfg(feature="serde")] pub mod serde; diff --git a/src/integrations/rustc_serialize.rs b/src/integrations/rustc_serialize.rs new file mode 100644 index 00000000..23bfdcc8 --- /dev/null +++ b/src/integrations/rustc_serialize.rs @@ -0,0 +1,114 @@ +use rustc_serialize::json::{ToJson, Json}; + +use ::{GraphQLError, Value}; +use ast::InputValue; +use executor::ExecutionError; +use parser::{ParseError, Spanning, SourcePosition}; +use validation::RuleError; + +fn parse_error_to_json(err: &Spanning) -> Json { + Json::Array(vec![ + Json::Object(vec![ + ("message".to_owned(), format!("{}", err.item).to_json()), + ("locations".to_owned(), vec![ + Json::Object(vec![ + ("line".to_owned(), (err.start.line() + 1).to_json()), + ("column".to_owned(), (err.start.column() + 1).to_json()) + ].into_iter().collect()), + ].to_json()), + ].into_iter().collect()), + ]) +} + +impl ToJson for ExecutionError { + fn to_json(&self) -> Json { + Json::Object(vec![ + ("message".to_owned(), self.message().to_json()), + ("locations".to_owned(), vec![self.location().clone()].to_json()), + ("path".to_owned(), self.path().to_json()), + ].into_iter().collect()) + } +} + +impl<'a> ToJson for GraphQLError<'a> { + fn to_json(&self) -> Json { + match *self { + GraphQLError::ParseError(ref err) => parse_error_to_json(err), + GraphQLError::ValidationError(ref errs) => errs.to_json(), + GraphQLError::MultipleOperationsProvided => Json::String( + "Must provide operation name if query contains multiple operations".to_owned()), + GraphQLError::NoOperationProvided => Json::String( + "Must provide an operation".to_owned()), + GraphQLError::UnknownOperationName => Json::String( + "Unknown operation".to_owned()), + } + } +} + +impl ToJson for InputValue { + fn to_json(&self) -> Json { + match *self { + InputValue::Null | InputValue::Variable(_) => Json::Null, + InputValue::Int(i) => Json::I64(i), + InputValue::Float(f) => Json::F64(f), + InputValue::String(ref s) | InputValue::Enum(ref s) => Json::String(s.clone()), + InputValue::Boolean(b) => Json::Boolean(b), + InputValue::List(ref l) => Json::Array(l.iter().map(|x| x.item.to_json()).collect()), + InputValue::Object(ref o) => Json::Object(o.iter().map(|&(ref k, ref v)| (k.item.clone(), v.item.to_json())).collect()), + } + } +} + +impl InputValue { + /// Convert a `Json` structure into an `InputValue`. + /// + /// This consumes the JSON instance. + /// + /// Notes: + /// * No enums or variables will be produced by this method. + /// * All lists and objects will be unlocated + pub fn from_json(json: Json) -> InputValue { + match json { + Json::I64(i) => InputValue::int(i), + Json::U64(u) => InputValue::float(u as f64), + Json::F64(f) => InputValue::float(f), + Json::String(s) => InputValue::string(s), + Json::Boolean(b) => InputValue::boolean(b), + Json::Array(a) => InputValue::list(a.into_iter().map(InputValue::from_json).collect()), + Json::Object(o) => InputValue::object(o.into_iter().map(|(k, v)| (k, InputValue::from_json(v))).collect()), + Json::Null => InputValue::null(), + } + } +} + +impl ToJson for RuleError { + fn to_json(&self) -> Json { + Json::Object(vec![ + ("message".to_owned(), self.message().to_json()), + ("locations".to_owned(), self.locations().to_json()), + ].into_iter().collect()) + } +} + +impl ToJson for SourcePosition { + fn to_json(&self) -> Json { + Json::Object(vec![ + ("line".to_owned(), (self.line() + 1).to_json()), + ("column".to_owned(), (self.column() + 1).to_json()), + ].into_iter().collect()) + } +} + +impl ToJson for Value { + fn to_json(&self) -> Json { + match *self { + Value::Null => Json::Null, + Value::Int(i) => Json::I64(i), + Value::Float(f) => Json::F64(f), + Value::String(ref s) => Json::String(s.clone()), + Value::Boolean(b) => Json::Boolean(b), + Value::List(ref l) => Json::Array(l.iter().map(|x| x.to_json()).collect()), + Value::Object(ref o) => Json::Object(o.iter().map(|(k,v)| (k.clone(), v.to_json())).collect()), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 12540b92..eb23bcb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,7 +186,7 @@ built-in [GraphiQL][6] handler included. #![cfg_attr(feature="nightly", feature(test))] #![warn(missing_docs)] -extern crate rustc_serialize; +#[cfg(feature="rustc-serialize")] extern crate rustc_serialize; #[cfg(feature="serde")] extern crate serde; #[cfg(feature="nightly")] extern crate test; @@ -211,9 +211,7 @@ mod integrations; use std::collections::HashMap; -use rustc_serialize::json::{ToJson, Json}; - -use parser::{parse_document_source, ParseError, Spanning, SourcePosition}; +use parser::{parse_document_source, ParseError, Spanning}; use validation::{ValidatorContext, visit_all_rules, validate_input_values}; use executor::execute_validated_query; @@ -285,63 +283,6 @@ impl<'a> From>> for GraphQLError<'a> { } } -impl<'a> ToJson for GraphQLError<'a> { - fn to_json(&self) -> Json { - match *self { - GraphQLError::ParseError(ref err) => parse_error_to_json(err), - GraphQLError::ValidationError(ref errs) => errs.to_json(), - GraphQLError::MultipleOperationsProvided => Json::String( - "Must provide operation name if query contains multiple operations".to_owned()), - GraphQLError::NoOperationProvided => Json::String( - "Must provide an operation".to_owned()), - GraphQLError::UnknownOperationName => Json::String( - "Unknown operation".to_owned()), - } - } -} - -fn parse_error_to_json(err: &Spanning) -> Json { - Json::Array(vec![ - Json::Object(vec![ - ("message".to_owned(), format!("{}", err.item).to_json()), - ("locations".to_owned(), vec![ - Json::Object(vec![ - ("line".to_owned(), (err.start.line() + 1).to_json()), - ("column".to_owned(), (err.start.column() + 1).to_json()) - ].into_iter().collect()), - ].to_json()), - ].into_iter().collect()), - ]) -} - -impl ToJson for RuleError { - fn to_json(&self) -> Json { - Json::Object(vec![ - ("message".to_owned(), self.message().to_json()), - ("locations".to_owned(), self.locations().to_json()), - ].into_iter().collect()) - } -} - -impl ToJson for SourcePosition { - fn to_json(&self) -> Json { - Json::Object(vec![ - ("line".to_owned(), (self.line() + 1).to_json()), - ("column".to_owned(), (self.column() + 1).to_json()), - ].into_iter().collect()) - } -} - -impl ToJson for ExecutionError { - fn to_json(&self) -> Json { - Json::Object(vec![ - ("message".to_owned(), self.message().to_json()), - ("locations".to_owned(), vec![self.location().clone()].to_json()), - ("path".to_owned(), self.path().to_json()), - ].into_iter().collect()) - } -} - #[doc(hidden)] pub fn to_camel_case(s: &str) -> String { let mut dest = String::new(); diff --git a/src/schema/schema.rs b/src/schema/schema.rs index 9cfd737e..31a70bc4 100644 --- a/src/schema/schema.rs +++ b/src/schema/schema.rs @@ -1,5 +1,3 @@ -use rustc_serialize::json::ToJson; - use types::base::{GraphQLType, Arguments, TypeKind}; use executor::{Executor, Registry, ExecutionResult}; @@ -191,7 +189,7 @@ graphql_object!(<'a> Argument<'a>: SchemaType<'a> as "__InputValue" |&self| { } field default_value() -> Option { - self.default_value.as_ref().map(|v| v.to_json().to_string()) + self.default_value.as_ref().map(|v| format!("{}", v)) } }); diff --git a/src/value.rs b/src/value.rs index 67e5d01b..10f7de96 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,8 +1,6 @@ use std::collections::HashMap; use std::hash::Hash; -use rustc_serialize::json::{ToJson, Json}; - use parser::Spanning; use ast::{InputValue, ToInputValue}; @@ -100,20 +98,6 @@ impl Value { } } -impl ToJson for Value { - fn to_json(&self) -> Json { - match *self { - Value::Null => Json::Null, - Value::Int(i) => Json::I64(i), - Value::Float(f) => Json::F64(f), - Value::String(ref s) => Json::String(s.clone()), - Value::Boolean(b) => Json::Boolean(b), - Value::List(ref l) => Json::Array(l.iter().map(|x| x.to_json()).collect()), - Value::Object(ref o) => Json::Object(o.iter().map(|(k,v)| (k.clone(), v.to_json())).collect()), - } - } -} - impl ToInputValue for Value { fn to(&self) -> InputValue { match *self {