Merge branch 'master' into rework-core-traits
This commit is contained in:
commit
72cd4be715
7 changed files with 270 additions and 0 deletions
.github/workflows
juniper
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -97,12 +97,14 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- { feature: <none>, crate: juniper }
|
- { feature: <none>, crate: juniper }
|
||||||
|
- { feature: bigdecimal, crate: juniper }
|
||||||
- { feature: bson, crate: juniper }
|
- { feature: bson, crate: juniper }
|
||||||
- { feature: chrono, crate: juniper }
|
- { feature: chrono, crate: juniper }
|
||||||
- { feature: chrono-clock, crate: juniper }
|
- { feature: chrono-clock, crate: juniper }
|
||||||
- { feature: chrono-tz, crate: juniper }
|
- { feature: chrono-tz, crate: juniper }
|
||||||
- { feature: expose-test-schema, crate: juniper }
|
- { feature: expose-test-schema, crate: juniper }
|
||||||
- { feature: graphql-parser, crate: juniper }
|
- { feature: graphql-parser, crate: juniper }
|
||||||
|
- { feature: rust_decimal, crate: juniper }
|
||||||
- { feature: schema-language, crate: juniper }
|
- { feature: schema-language, crate: juniper }
|
||||||
- { feature: serde_json, crate: juniper }
|
- { feature: serde_json, crate: juniper }
|
||||||
- { feature: time, crate: juniper }
|
- { feature: time, crate: juniper }
|
||||||
|
|
|
@ -56,6 +56,8 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
|
||||||
- `graphql_input_value!` and `graphql_vars!` macros. ([#996])
|
- `graphql_input_value!` and `graphql_vars!` macros. ([#996])
|
||||||
- [`time` crate] integration behind `time` [Cargo feature]. ([#1006])
|
- [`time` crate] integration behind `time` [Cargo feature]. ([#1006])
|
||||||
- `#[derive(GraphQLInterface)]` macro allowing using structs as GraphQL interfaces. ([#1026])
|
- `#[derive(GraphQLInterface)]` macro allowing using structs as GraphQL interfaces. ([#1026])
|
||||||
|
- [`bigdecimal` crate] integration behind `bigdecimal` [Cargo feature]. ([#1060])
|
||||||
|
- [`rust_decimal` crate] integration behind `rust_decimal` [Cargo feature]. ([#1060])
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -95,6 +97,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
|
||||||
[#1051]: /../../issues/1051
|
[#1051]: /../../issues/1051
|
||||||
[#1054]: /../../pull/1054
|
[#1054]: /../../pull/1054
|
||||||
[#1057]: /../../pull/1057
|
[#1057]: /../../pull/1057
|
||||||
|
[#1060]: /../../pull/1060
|
||||||
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083
|
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ schema-language = ["graphql-parser"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.32", default-features = false, optional = true }
|
anyhow = { version = "1.0.32", default-features = false, optional = true }
|
||||||
async-trait = "0.1.39"
|
async-trait = "0.1.39"
|
||||||
|
bigdecimal = { version = "0.3", optional = true }
|
||||||
bson = { version = "2.0", features = ["chrono-0_4"], optional = true }
|
bson = { version = "2.0", features = ["chrono-0_4"], optional = true }
|
||||||
chrono = { version = "0.4", features = ["alloc"], default-features = false, optional = true }
|
chrono = { version = "0.4", features = ["alloc"], default-features = false, optional = true }
|
||||||
chrono-tz = { version = "0.6", default-features = false, optional = true }
|
chrono-tz = { version = "0.6", default-features = false, optional = true }
|
||||||
|
@ -46,6 +47,7 @@ futures-enum = { version = "0.1.12", default-features = false }
|
||||||
graphql-parser = { version = "0.4", optional = true }
|
graphql-parser = { version = "0.4", optional = true }
|
||||||
indexmap = { version = "1.0", features = ["serde-1"] }
|
indexmap = { version = "1.0", features = ["serde-1"] }
|
||||||
juniper_codegen = { version = "0.16.0-dev", path = "../juniper_codegen" }
|
juniper_codegen = { version = "0.16.0-dev", path = "../juniper_codegen" }
|
||||||
|
rust_decimal = { version = "1.0", default-features = false, optional = true }
|
||||||
serde = { version = "1.0.8", features = ["derive"], default-features = false }
|
serde = { version = "1.0.8", features = ["derive"], default-features = false }
|
||||||
serde_json = { version = "1.0.2", default-features = false, optional = true }
|
serde_json = { version = "1.0.2", default-features = false, optional = true }
|
||||||
smartstring = "1.0"
|
smartstring = "1.0"
|
||||||
|
|
|
@ -44,9 +44,11 @@ As an exception to other [GraphQL] libraries for other languages, [Juniper] buil
|
||||||
### Data types
|
### Data types
|
||||||
|
|
||||||
[Juniper] has automatic integration with some very common [Rust] crates to make building schemas a breeze. The types from these crates will be usable in your schemas automatically:
|
[Juniper] has automatic integration with some very common [Rust] crates to make building schemas a breeze. The types from these crates will be usable in your schemas automatically:
|
||||||
|
- [`bigdecimal`] (feature gated)
|
||||||
- [`bson`]
|
- [`bson`]
|
||||||
- [`chrono`] (feature gated)
|
- [`chrono`] (feature gated)
|
||||||
- [`chrono-tz`] (feature gated)
|
- [`chrono-tz`] (feature gated)
|
||||||
|
- [`rust_decimal`] (feature gated)
|
||||||
- [`time`] (feature gated)
|
- [`time`] (feature gated)
|
||||||
- [`url`]
|
- [`url`]
|
||||||
- [`uuid`]
|
- [`uuid`]
|
||||||
|
@ -78,6 +80,7 @@ This project is licensed under [BSD 2-Clause License](https://github.com/graphql
|
||||||
|
|
||||||
|
|
||||||
[`actix-web`]: https://docs.rs/actix-web
|
[`actix-web`]: https://docs.rs/actix-web
|
||||||
|
[`bigdecimal`]: https://docs.rs/bigdecimal
|
||||||
[`bson`]: https://docs.rs/bson
|
[`bson`]: https://docs.rs/bson
|
||||||
[`chrono`]: https://docs.rs/chrono
|
[`chrono`]: https://docs.rs/chrono
|
||||||
[`chrono-tz`]: https://docs.rs/chrono-tz
|
[`chrono-tz`]: https://docs.rs/chrono-tz
|
||||||
|
@ -89,6 +92,7 @@ This project is licensed under [BSD 2-Clause License](https://github.com/graphql
|
||||||
[`hyper`]: https://docs.rs/hyper
|
[`hyper`]: https://docs.rs/hyper
|
||||||
[`iron`]: https://docs.rs/iron
|
[`iron`]: https://docs.rs/iron
|
||||||
[`rocket`]: https://docs.rs/rocket
|
[`rocket`]: https://docs.rs/rocket
|
||||||
|
[`rust_decimal`]: https://docs.rs/rust_decimal
|
||||||
[`time`]: https://docs.rs/time
|
[`time`]: https://docs.rs/time
|
||||||
[`url`]: https://docs.rs/url
|
[`url`]: https://docs.rs/url
|
||||||
[`uuid`]: https://docs.rs/uuid
|
[`uuid`]: https://docs.rs/uuid
|
||||||
|
|
132
juniper/src/integrations/bigdecimal.rs
Normal file
132
juniper/src/integrations/bigdecimal.rs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
//! GraphQL support for [`bigdecimal`] crate types.
|
||||||
|
//!
|
||||||
|
//! # Supported types
|
||||||
|
//!
|
||||||
|
//! | Rust type | GraphQL scalar |
|
||||||
|
//! |----------------|----------------|
|
||||||
|
//! | [`BigDecimal`] | `BigDecimal` |
|
||||||
|
//!
|
||||||
|
//! [`BigDecimal`]: bigdecimal::BigDecimal
|
||||||
|
|
||||||
|
use std::str::FromStr as _;
|
||||||
|
|
||||||
|
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||||
|
|
||||||
|
/// Big decimal type.
|
||||||
|
///
|
||||||
|
/// Allows storing any real number to arbitrary precision; which avoids common
|
||||||
|
/// floating point errors (such as 0.1 + 0.2 ≠ 0.3) at the cost of complexity.
|
||||||
|
///
|
||||||
|
/// Always serializes as `String`. But may be deserialized from `Int` and
|
||||||
|
/// `Float` values too. It's not recommended to deserialize from a `Float`
|
||||||
|
/// directly, as the floating point representation may be unexpected.
|
||||||
|
///
|
||||||
|
/// See also [`bigdecimal`] crate for details.
|
||||||
|
///
|
||||||
|
/// [`bigdecimal`]: https://docs.rs/bigdecimal
|
||||||
|
#[graphql_scalar(
|
||||||
|
with = bigdecimal_scalar,
|
||||||
|
parse_token(i32, f64, String),
|
||||||
|
specified_by_url = "https://docs.rs/bigdecimal",
|
||||||
|
)]
|
||||||
|
type BigDecimal = bigdecimal::BigDecimal;
|
||||||
|
|
||||||
|
mod bigdecimal_scalar {
|
||||||
|
use std::convert::TryFrom as _;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn to_output<S: ScalarValue>(v: &BigDecimal) -> Value<S> {
|
||||||
|
Value::scalar(v.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<BigDecimal, String> {
|
||||||
|
if let Some(i) = v.as_int_value() {
|
||||||
|
Ok(BigDecimal::from(i))
|
||||||
|
} else if let Some(f) = v.as_float_value() {
|
||||||
|
BigDecimal::try_from(f)
|
||||||
|
.map_err(|e| format!("Failed to parse `BigDecimal` from `Float`: {}", e))
|
||||||
|
} else {
|
||||||
|
v.as_string_value()
|
||||||
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
|
.and_then(|s| {
|
||||||
|
BigDecimal::from_str(s)
|
||||||
|
.map_err(|e| format!("Failed to parse `BigDecimal` from `String`: {}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::str::FromStr as _;
|
||||||
|
|
||||||
|
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||||
|
|
||||||
|
use super::BigDecimal;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_correct_input() {
|
||||||
|
for (input, expected) in [
|
||||||
|
(graphql_input_value!("4.20"), "4.20"),
|
||||||
|
(graphql_input_value!("0"), "0"),
|
||||||
|
(
|
||||||
|
graphql_input_value!("999999999999.999999999"),
|
||||||
|
"999999999999.999999999",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
graphql_input_value!("87553378877997984345"),
|
||||||
|
"87553378877997984345",
|
||||||
|
),
|
||||||
|
(graphql_input_value!(123), "123"),
|
||||||
|
(graphql_input_value!(0), "0"),
|
||||||
|
(graphql_input_value!(43.44), "43.44"),
|
||||||
|
] {
|
||||||
|
let input: InputValue = input;
|
||||||
|
let parsed = BigDecimal::from_input_value(&input);
|
||||||
|
let expected = BigDecimal::from_str(expected).unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
parsed.is_ok(),
|
||||||
|
"failed to parse `{:?}`: {:?}",
|
||||||
|
input,
|
||||||
|
parsed.unwrap_err(),
|
||||||
|
);
|
||||||
|
assert_eq!(parsed.unwrap(), expected, "input: {:?}", input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_on_invalid_input() {
|
||||||
|
for input in [
|
||||||
|
graphql_input_value!(""),
|
||||||
|
graphql_input_value!("0,0"),
|
||||||
|
graphql_input_value!("12,"),
|
||||||
|
graphql_input_value!("1996-12-19T14:23:43"),
|
||||||
|
graphql_input_value!("i'm not even a number"),
|
||||||
|
graphql_input_value!(null),
|
||||||
|
graphql_input_value!(false),
|
||||||
|
] {
|
||||||
|
let input: InputValue = input;
|
||||||
|
let parsed = BigDecimal::from_input_value(&input);
|
||||||
|
|
||||||
|
assert!(parsed.is_err(), "allows input: {:?}", input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn formats_correctly() {
|
||||||
|
for raw in [
|
||||||
|
"4.20",
|
||||||
|
"0",
|
||||||
|
"999999999999.999999999",
|
||||||
|
"87553378877997984345",
|
||||||
|
"123",
|
||||||
|
"43.44",
|
||||||
|
] {
|
||||||
|
let actual: InputValue = BigDecimal::from_str(raw).unwrap().to_input_value();
|
||||||
|
|
||||||
|
assert_eq!(actual, graphql_input_value!((raw)), "on value: {}", raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
//! Provides GraphQLType implementations for some external types
|
//! Provides GraphQLType implementations for some external types
|
||||||
|
|
||||||
|
#[cfg(feature = "bigdecimal")]
|
||||||
|
pub mod bigdecimal;
|
||||||
#[cfg(feature = "bson")]
|
#[cfg(feature = "bson")]
|
||||||
pub mod bson;
|
pub mod bson;
|
||||||
#[cfg(feature = "chrono")]
|
#[cfg(feature = "chrono")]
|
||||||
pub mod chrono;
|
pub mod chrono;
|
||||||
#[cfg(feature = "chrono-tz")]
|
#[cfg(feature = "chrono-tz")]
|
||||||
pub mod chrono_tz;
|
pub mod chrono_tz;
|
||||||
|
#[cfg(feature = "rust_decimal")]
|
||||||
|
pub mod rust_decimal;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
|
|
123
juniper/src/integrations/rust_decimal.rs
Normal file
123
juniper/src/integrations/rust_decimal.rs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
//! GraphQL support for [`rust_decimal`] crate types.
|
||||||
|
//!
|
||||||
|
//! # Supported types
|
||||||
|
//!
|
||||||
|
//! | Rust type | GraphQL scalar |
|
||||||
|
//! |-------------|----------------|
|
||||||
|
//! | [`Decimal`] | `Decimal` |
|
||||||
|
//!
|
||||||
|
//! [`Decimal`]: rust_decimal::Decimal
|
||||||
|
|
||||||
|
use std::str::FromStr as _;
|
||||||
|
|
||||||
|
use crate::{graphql_scalar, InputValue, ScalarValue, Value};
|
||||||
|
|
||||||
|
/// 128 bit representation of a fixed-precision decimal number.
|
||||||
|
///
|
||||||
|
/// The finite set of values of `Decimal` scalar are of the form
|
||||||
|
/// m / 10<sup>e</sup>, where m is an integer such that
|
||||||
|
/// -2<sup>96</sup> < m < 2<sup>96</sup>, and e is an integer between 0 and 28
|
||||||
|
/// inclusive.
|
||||||
|
///
|
||||||
|
/// Always serializes as `String`. But may be deserialized from `Int` and
|
||||||
|
/// `Float` values too. It's not recommended to deserialize from a `Float`
|
||||||
|
/// directly, as the floating point representation may be unexpected.
|
||||||
|
///
|
||||||
|
/// See also [`rust_decimal`] crate for details.
|
||||||
|
///
|
||||||
|
/// [`rust_decimal`]: https://docs.rs/rust_decimal
|
||||||
|
#[graphql_scalar(
|
||||||
|
with = rust_decimal_scalar,
|
||||||
|
parse_token(i32, f64, String),
|
||||||
|
specified_by_url = "https://docs.rs/rust_decimal",
|
||||||
|
)]
|
||||||
|
type Decimal = rust_decimal::Decimal;
|
||||||
|
|
||||||
|
mod rust_decimal_scalar {
|
||||||
|
use std::convert::TryFrom as _;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn to_output<S: ScalarValue>(v: &Decimal) -> Value<S> {
|
||||||
|
Value::scalar(v.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_input<S: ScalarValue>(v: &InputValue<S>) -> Result<Decimal, String> {
|
||||||
|
if let Some(i) = v.as_int_value() {
|
||||||
|
Ok(Decimal::from(i))
|
||||||
|
} else if let Some(f) = v.as_float_value() {
|
||||||
|
Decimal::try_from(f)
|
||||||
|
.map_err(|e| format!("Failed to parse `Decimal` from `Float`: {}", e))
|
||||||
|
} else {
|
||||||
|
v.as_string_value()
|
||||||
|
.ok_or_else(|| format!("Expected `String`, found: {}", v))
|
||||||
|
.and_then(|s| {
|
||||||
|
Decimal::from_str(s)
|
||||||
|
.map_err(|e| format!("Failed to parse `Decimal` from `String`: {}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::str::FromStr as _;
|
||||||
|
|
||||||
|
use crate::{graphql_input_value, FromInputValue as _, InputValue, ToInputValue as _};
|
||||||
|
|
||||||
|
use super::Decimal;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_correct_input() {
|
||||||
|
for (input, expected) in [
|
||||||
|
(graphql_input_value!("4.20"), "4.20"),
|
||||||
|
(graphql_input_value!("0"), "0"),
|
||||||
|
(graphql_input_value!("999.999999999"), "999.999999999"),
|
||||||
|
(graphql_input_value!("875533788"), "875533788"),
|
||||||
|
(graphql_input_value!(123), "123"),
|
||||||
|
(graphql_input_value!(0), "0"),
|
||||||
|
(graphql_input_value!(43.44), "43.44"),
|
||||||
|
] {
|
||||||
|
let input: InputValue = input;
|
||||||
|
let parsed = Decimal::from_input_value(&input);
|
||||||
|
let expected = Decimal::from_str(expected).unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
parsed.is_ok(),
|
||||||
|
"failed to parse `{:?}`: {:?}",
|
||||||
|
input,
|
||||||
|
parsed.unwrap_err(),
|
||||||
|
);
|
||||||
|
assert_eq!(parsed.unwrap(), expected, "input: {:?}", input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_on_invalid_input() {
|
||||||
|
for input in [
|
||||||
|
graphql_input_value!(""),
|
||||||
|
graphql_input_value!("0,0"),
|
||||||
|
graphql_input_value!("12,"),
|
||||||
|
graphql_input_value!("1996-12-19T14:23:43"),
|
||||||
|
graphql_input_value!("99999999999999999999999999999999999999"),
|
||||||
|
graphql_input_value!("99999999999999999999999999999999999999.99"),
|
||||||
|
graphql_input_value!("i'm not even a number"),
|
||||||
|
graphql_input_value!(null),
|
||||||
|
graphql_input_value!(false),
|
||||||
|
] {
|
||||||
|
let input: InputValue = input;
|
||||||
|
let parsed = Decimal::from_input_value(&input);
|
||||||
|
|
||||||
|
assert!(parsed.is_err(), "allows input: {:?}", input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn formats_correctly() {
|
||||||
|
for raw in ["4.20", "0", "999.999999999", "875533788", "123", "43.44"] {
|
||||||
|
let actual: InputValue = Decimal::from_str(raw).unwrap().to_input_value();
|
||||||
|
|
||||||
|
assert_eq!(actual, graphql_input_value!((raw)), "on value: {}", raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue