Merge branch 'master' into rework-core-traits

This commit is contained in:
tyranron 2022-05-10 17:45:27 +03:00
commit 72cd4be715
No known key found for this signature in database
GPG key ID: 762E144FB230A4F0
7 changed files with 270 additions and 0 deletions

View file

@ -97,12 +97,14 @@ jobs:
matrix:
include:
- { feature: <none>, crate: juniper }
- { feature: bigdecimal, crate: juniper }
- { feature: bson, crate: juniper }
- { feature: chrono, crate: juniper }
- { feature: chrono-clock, crate: juniper }
- { feature: chrono-tz, crate: juniper }
- { feature: expose-test-schema, crate: juniper }
- { feature: graphql-parser, crate: juniper }
- { feature: rust_decimal, crate: juniper }
- { feature: schema-language, crate: juniper }
- { feature: serde_json, crate: juniper }
- { feature: time, crate: juniper }

View file

@ -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])
- [`time` crate] integration behind `time` [Cargo feature]. ([#1006])
- `#[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
@ -95,6 +97,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
[#1051]: /../../issues/1051
[#1054]: /../../pull/1054
[#1057]: /../../pull/1057
[#1060]: /../../pull/1060
[ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083

View file

@ -37,6 +37,7 @@ schema-language = ["graphql-parser"]
[dependencies]
anyhow = { version = "1.0.32", default-features = false, optional = true }
async-trait = "0.1.39"
bigdecimal = { version = "0.3", optional = true }
bson = { version = "2.0", features = ["chrono-0_4"], optional = true }
chrono = { version = "0.4", features = ["alloc"], 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 }
indexmap = { version = "1.0", features = ["serde-1"] }
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_json = { version = "1.0.2", default-features = false, optional = true }
smartstring = "1.0"

View file

@ -44,9 +44,11 @@ As an exception to other [GraphQL] libraries for other languages, [Juniper] buil
### 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:
- [`bigdecimal`] (feature gated)
- [`bson`]
- [`chrono`] (feature gated)
- [`chrono-tz`] (feature gated)
- [`rust_decimal`] (feature gated)
- [`time`] (feature gated)
- [`url`]
- [`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
[`bigdecimal`]: https://docs.rs/bigdecimal
[`bson`]: https://docs.rs/bson
[`chrono`]: https://docs.rs/chrono
[`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
[`iron`]: https://docs.rs/iron
[`rocket`]: https://docs.rs/rocket
[`rust_decimal`]: https://docs.rs/rust_decimal
[`time`]: https://docs.rs/time
[`url`]: https://docs.rs/url
[`uuid`]: https://docs.rs/uuid

View 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);
}
}
}

View file

@ -1,11 +1,15 @@
//! Provides GraphQLType implementations for some external types
#[cfg(feature = "bigdecimal")]
pub mod bigdecimal;
#[cfg(feature = "bson")]
pub mod bson;
#[cfg(feature = "chrono")]
pub mod chrono;
#[cfg(feature = "chrono-tz")]
pub mod chrono_tz;
#[cfg(feature = "rust_decimal")]
pub mod rust_decimal;
#[doc(hidden)]
pub mod serde;
#[cfg(feature = "time")]

View 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);
}
}
}