|
| 1 | +//! This module is needed to support the cloud-side of the project. |
| 2 | +//! It enables GraphQL support for core structures used on the client and server sides. |
| 3 | +
|
| 4 | +use juniper::{ |
| 5 | + graphql_scalar, |
| 6 | + parser::{ParseError, ScalarToken, Token}, |
| 7 | + serde::{de, Deserialize, Deserializer, Serialize}, |
| 8 | + InputValue, ParseScalarResult, ScalarValue, Value, |
| 9 | +}; |
| 10 | +use std::{convert::TryInto as _, fmt}; |
| 11 | + |
| 12 | +/// An extension to the standard GraphQL set of types to include Rust scalar values. |
| 13 | +/// Only the types used in this project are added to the list. |
| 14 | +/// ### About GraphQL scalars |
| 15 | +/// * https://graphql.org/learn/schema/#scalar-types |
| 16 | +/// * https://www.graphql-tools.com/docs/scalars#custom-scalars |
| 17 | +/// ### About extending the GraphQL scalars in Juniper |
| 18 | +/// * https://graphql-rust.github.io/juniper/master/types/scalars.html#custom-scalars |
| 19 | +/// * https://github.com/graphql-rust/juniper/issues/862 |
| 20 | +#[derive(Clone, Debug, PartialEq, ScalarValue, Serialize)] |
| 21 | +#[serde(untagged)] |
| 22 | +pub enum RustScalarValue { |
| 23 | + /// A GraphQL scalar for i32 |
| 24 | + #[value(as_float, as_int)] |
| 25 | + Int(i32), |
| 26 | + /// A custom scalar for u64. The value is serialized into JSON number and should not be more than 53 bits to fit into JS Number type: |
| 27 | + /// * Number.MAX_SAFE_INTEGER = 2^53 - 1 = 9_007_199_254_740_991 |
| 28 | + /// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number |
| 29 | + /// JSON spec does not constrain integer values unless specified in the schema. 53 bits is sufficient for our purposes. |
| 30 | + U64(u64), |
| 31 | + /// A custom scalar for i64 used in EPOCH timestamps. Theoretically, the value should never be negative because all STM dates are post 1970. |
| 32 | + /// The value is serialized into JSON number and should not be more than 53 bits to fit into JS Number type: |
| 33 | + /// * Number.MIN_SAFE_INTEGER = -(2^53 - 1) = -9,007,199,254,740,991 |
| 34 | + I64(i64), |
| 35 | + /// A GraphQL scalar for f64 |
| 36 | + #[value(as_float)] |
| 37 | + Float(f64), |
| 38 | + /// A GraphQL scalar for String |
| 39 | + #[value(as_str, as_string, into_string)] |
| 40 | + String(String), |
| 41 | + /// A GraphQL scalar for bool |
| 42 | + #[value(as_bool)] |
| 43 | + Boolean(bool), |
| 44 | +} |
| 45 | + |
| 46 | +impl<'de> Deserialize<'de> for RustScalarValue { |
| 47 | + fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> { |
| 48 | + struct Visitor; |
| 49 | + |
| 50 | + impl<'de> de::Visitor<'de> for Visitor { |
| 51 | + type Value = RustScalarValue; |
| 52 | + |
| 53 | + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 54 | + f.write_str("a valid input value") |
| 55 | + } |
| 56 | + |
| 57 | + fn visit_bool<E: de::Error>(self, b: bool) -> Result<Self::Value, E> { |
| 58 | + Ok(RustScalarValue::Boolean(b)) |
| 59 | + } |
| 60 | + |
| 61 | + fn visit_i32<E: de::Error>(self, n: i32) -> Result<Self::Value, E> { |
| 62 | + Ok(RustScalarValue::Int(n)) |
| 63 | + } |
| 64 | + |
| 65 | + fn visit_u64<E: de::Error>(self, b: u64) -> Result<Self::Value, E> { |
| 66 | + if b <= u64::from(i32::MAX as u32) { |
| 67 | + self.visit_i32(b.try_into().unwrap()) |
| 68 | + } else { |
| 69 | + Ok(RustScalarValue::U64(b)) |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + fn visit_u32<E: de::Error>(self, n: u32) -> Result<Self::Value, E> { |
| 74 | + if n <= i32::MAX as u32 { |
| 75 | + self.visit_i32(n.try_into().unwrap()) |
| 76 | + } else { |
| 77 | + self.visit_u64(n.into()) |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + fn visit_i64<E: de::Error>(self, n: i64) -> Result<Self::Value, E> { |
| 82 | + if n <= i64::MAX as i64 { |
| 83 | + self.visit_i64(n.try_into().unwrap()) |
| 84 | + } else { |
| 85 | + // Browser's `JSON.stringify()` serializes all numbers |
| 86 | + // having no fractional part as integers (no decimal point), |
| 87 | + // so we must parse large integers as floating point, |
| 88 | + // otherwise we would error on transferring large floating |
| 89 | + // point numbers. |
| 90 | + // TODO: Use `FloatToInt` conversion once stabilized: |
| 91 | + // https://github.com/rust-lang/rust/issues/67057 |
| 92 | + Ok(RustScalarValue::Float(n as f64)) |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + fn visit_f64<E: de::Error>(self, f: f64) -> Result<Self::Value, E> { |
| 97 | + Ok(RustScalarValue::Float(f)) |
| 98 | + } |
| 99 | + |
| 100 | + fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> { |
| 101 | + self.visit_string(s.into()) |
| 102 | + } |
| 103 | + |
| 104 | + fn visit_string<E: de::Error>(self, s: String) -> Result<Self::Value, E> { |
| 105 | + Ok(RustScalarValue::String(s)) |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + de.deserialize_any(Visitor) |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +#[graphql_scalar(with = u64_scalar, scalar = RustScalarValue)] |
| 114 | +type U64 = u64; |
| 115 | + |
| 116 | +mod u64_scalar { |
| 117 | + use super::*; |
| 118 | + |
| 119 | + pub(super) fn to_output(v: &U64) -> Value<RustScalarValue> { |
| 120 | + Value::scalar(*v) |
| 121 | + } |
| 122 | + |
| 123 | + pub(super) fn from_input(v: &InputValue<RustScalarValue>) -> Result<U64, String> { |
| 124 | + v.as_scalar_value::<u64>() |
| 125 | + .copied() |
| 126 | + .ok_or_else(|| format!("Expected `RustScalarValue::U64`, found: {}", v)) |
| 127 | + } |
| 128 | + |
| 129 | + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, RustScalarValue> { |
| 130 | + if let ScalarToken::Int(v) = value { |
| 131 | + v.parse() |
| 132 | + .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) |
| 133 | + .map(|s: u64| s.into()) |
| 134 | + } else { |
| 135 | + Err(ParseError::UnexpectedToken(Token::Scalar(value))) |
| 136 | + } |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +#[graphql_scalar(with = i64_scalar, scalar = RustScalarValue)] |
| 141 | +type I64 = i64; |
| 142 | + |
| 143 | +mod i64_scalar { |
| 144 | + use super::*; |
| 145 | + |
| 146 | + pub(super) fn to_output(v: &I64) -> Value<RustScalarValue> { |
| 147 | + Value::scalar(*v) |
| 148 | + } |
| 149 | + |
| 150 | + pub(super) fn from_input(v: &InputValue<RustScalarValue>) -> Result<I64, String> { |
| 151 | + v.as_scalar_value::<i64>() |
| 152 | + .copied() |
| 153 | + .ok_or_else(|| format!("Expected `RustScalarValue::I64`, found: {}", v)) |
| 154 | + } |
| 155 | + |
| 156 | + pub(super) fn parse_token(value: ScalarToken<'_>) -> ParseScalarResult<'_, RustScalarValue> { |
| 157 | + if let ScalarToken::Int(v) = value { |
| 158 | + v.parse() |
| 159 | + .map_err(|_| ParseError::UnexpectedToken(Token::Scalar(value))) |
| 160 | + .map(|s: i64| s.into()) |
| 161 | + } else { |
| 162 | + Err(ParseError::UnexpectedToken(Token::Scalar(value))) |
| 163 | + } |
| 164 | + } |
| 165 | +} |
0 commit comments