juniper 0.17.0

GraphQL server library.
Documentation
mod object;
mod scalar;

use std::{any::TypeId, borrow::Cow, fmt, mem};

use arcstr::ArcStr;
use compact_str::CompactString;

pub use self::{
    object::Object,
    scalar::{
        AnyExt, DefaultScalarValue, FromScalarValue, ParseScalarResult, ParseScalarValue, Scalar,
        ScalarValue, ToScalarValue, TryToPrimitive, WrongInputScalarTypeError,
    },
};
use crate::{
    ast::{InputValue, ToInputValue},
    parser::Spanning,
};

/// Serializable value returned from query and field execution.
///
/// Used by the execution engine and resolvers to build up the response
/// structure. Similar to the `Json` type found in the serialize crate.
///
/// It is also similar to the `InputValue` type, but can not contain enum
/// values or variables. Also, lists and objects do not contain any location
/// information since they are generated by resolving fields and values rather
/// than parsing a source query.
#[expect(missing_docs, reason = "self-explanatory")]
#[derive(Clone, Debug, PartialEq)]
pub enum Value<S = DefaultScalarValue> {
    Null,
    Scalar(S),
    List(Vec<Value<S>>),
    Object(Object<S>),
}

impl<S> Value<S> {
    // CONSTRUCTORS

    /// Construct a null value.
    pub fn null() -> Self {
        Self::Null
    }

    /// Construct a list value.
    pub fn list(l: Vec<Self>) -> Self {
        Self::List(l)
    }

    /// Construct an object value.
    pub fn object(o: Object<S>) -> Self {
        Self::Object(o)
    }

    /// Construct a scalar value.
    pub fn scalar<T: Into<S>>(s: T) -> Self {
        Self::Scalar(s.into())
    }

    // DISCRIMINATORS

    /// Does this value represent null?
    pub fn is_null(&self) -> bool {
        matches!(*self, Self::Null)
    }

    /// View the underlying object value, if present.
    pub fn as_object_value(&self) -> Option<&Object<S>> {
        match self {
            Self::Object(o) => Some(o),
            _ => None,
        }
    }

    /// Convert this value into an Object.
    ///
    /// Returns None if value is not an Object.
    pub fn into_object(self) -> Option<Object<S>> {
        match self {
            Self::Object(o) => Some(o),
            _ => None,
        }
    }

    /// Mutable view into the underlying object value, if present.
    pub fn as_mut_object_value(&mut self) -> Option<&mut Object<S>> {
        match self {
            Self::Object(o) => Some(o),
            _ => None,
        }
    }

    /// View the underlying list value, if present.
    pub fn as_list_value(&self) -> Option<&Vec<Self>> {
        match self {
            Self::List(l) => Some(l),
            _ => None,
        }
    }

    /// View the underlying scalar value, if present
    pub fn as_scalar(&self) -> Option<&S> {
        match self {
            Self::Scalar(s) => Some(s),
            _ => None,
        }
    }

    /// Maps the [`ScalarValue`] type of this [`Value`] into the specified one.
    pub fn map_scalar_value<T>(self) -> Value<T>
    where
        S: ScalarValue,
        T: ScalarValue,
    {
        if TypeId::of::<T>() == TypeId::of::<S>() {
            // SAFETY: This is safe, because we're transmuting the value into itself, so no
            //         invariants may change, and we're just satisfying the type checker.
            //         As `mem::transmute_copy` creates a copy of the data, we need the
            //         `mem::ManuallyDrop` here to omit double-free when `S: Drop`.
            let val = mem::ManuallyDrop::new(self);
            unsafe { mem::transmute_copy(&*val) }
        } else {
            match self {
                Self::Null => Value::Null,
                Self::Scalar(s) => Value::Scalar(s.into_another()),
                Self::List(l) => Value::List(l.into_iter().map(Value::map_scalar_value).collect()),
                Self::Object(o) => Value::Object(
                    o.into_iter()
                        .map(|(k, v)| (k, v.map_scalar_value()))
                        .collect(),
                ),
            }
        }
    }
}

impl<S: Clone> ToInputValue<S> for Value<S> {
    fn to_input_value(&self) -> InputValue<S> {
        match self {
            Self::Null => InputValue::Null,
            Self::Scalar(s) => InputValue::Scalar(s.clone()),
            Self::List(l) => InputValue::List(
                l.iter()
                    .map(|x| Spanning::unlocated(x.to_input_value()))
                    .collect(),
            ),
            Self::Object(o) => InputValue::Object(
                o.iter()
                    .map(|(k, v)| {
                        (
                            Spanning::unlocated(k.clone()),
                            Spanning::unlocated(v.to_input_value()),
                        )
                    })
                    .collect(),
            ),
        }
    }
}

impl<S: ScalarValue> fmt::Display for Value<S> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Null => write!(f, "null"),
            Self::Scalar(s) => fmt::Display::fmt(<&Scalar<_>>::from(s), f),
            Self::List(list) => {
                write!(f, "[")?;
                for (idx, item) in list.iter().enumerate() {
                    write!(f, "{item}")?;
                    if idx < list.len() - 1 {
                        write!(f, ", ")?;
                    }
                }
                write!(f, "]")?;

                Ok(())
            }
            Self::Object(obj) => {
                write!(f, "{{")?;
                for (idx, (key, value)) in obj.iter().enumerate() {
                    write!(f, "\"{key}\": {value}")?;

                    if idx < obj.field_count() - 1 {
                        write!(f, ", ")?;
                    }
                }
                write!(f, "}}")?;

                Ok(())
            }
        }
    }
}

/// Conversion into a [`Value`].
///
/// This trait exists to work around [orphan rules] and allow to specify custom efficient
/// conversions whenever some custom [`ScalarValue`] is involved
/// (`impl IntoValue<CustomScalarValue> for ForeignType` would work, while
/// `impl From<ForeignType> for Value<CustomScalarValue>` wound not).
///
/// This trait is used inside [`graphql_value!`] macro expansion and implementing it allows to
/// put values of the implementor type there.
///
/// [`graphql_value!`]: crate::graphql_value
/// [orphan rules]: https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
pub trait IntoValue<S> {
    /// Converts this value into a [`Value`].
    #[must_use]
    fn into_value(self) -> Value<S>;
}

impl<S> IntoValue<S> for Value<S> {
    fn into_value(self) -> Self {
        self
    }
}

impl<T, S> IntoValue<S> for Option<T>
where
    T: IntoValue<S>,
{
    fn into_value(self) -> Value<S> {
        match self {
            Some(v) => v.into_value(),
            None => Value::Null,
        }
    }
}

impl<T, S> IntoValue<S> for &T
where
    T: ToScalarValue<S> + ?Sized,
{
    fn into_value(self) -> Value<S> {
        Value::Scalar(self.to_scalar_value())
    }
}

impl<S> IntoValue<S> for String
where
    String: Into<S>,
{
    fn into_value(self) -> Value<S> {
        Value::Scalar(self.into())
    }
}

impl<S> IntoValue<S> for Cow<'_, str>
where
    for<'a> &'a str: IntoValue<S>,
    String: IntoValue<S>,
{
    fn into_value(self) -> Value<S> {
        match self {
            Cow::Borrowed(s) => s.into_value(),
            Cow::Owned(s) => s.into_value(),
        }
    }
}

impl<S: ScalarValue> IntoValue<S> for ArcStr
where
    ArcStr: ToScalarValue<S>,
{
    fn into_value(self) -> Value<S> {
        Value::Scalar(self.to_scalar_value())
    }
}

impl<S: ScalarValue> IntoValue<S> for CompactString
where
    CompactString: ToScalarValue<S>,
{
    fn into_value(self) -> Value<S> {
        Value::Scalar(self.to_scalar_value())
    }
}

impl<S> IntoValue<S> for i32
where
    i32: ToScalarValue<S>,
{
    fn into_value(self) -> Value<S> {
        Value::Scalar(self.to_scalar_value())
    }
}

impl<S> IntoValue<S> for f64
where
    f64: ToScalarValue<S>,
{
    fn into_value(self) -> Value<S> {
        Value::Scalar(self.to_scalar_value())
    }
}

impl<S> IntoValue<S> for bool
where
    bool: ToScalarValue<S>,
{
    fn into_value(self) -> Value<S> {
        Value::Scalar(self.to_scalar_value())
    }
}

#[cfg(test)]
mod tests {
    use crate::graphql_value;

    use super::Value;

    #[test]
    fn display_null() {
        let s: Value = graphql_value!(null);
        assert_eq!(s.to_string(), "null");
    }

    #[test]
    fn display_int() {
        let s: Value = graphql_value!(123);
        assert_eq!(s.to_string(), "123");
    }

    #[test]
    fn display_float() {
        let s: Value = graphql_value!(123.456);
        assert_eq!(s.to_string(), "123.456");
    }

    #[test]
    fn display_string() {
        let s: Value = graphql_value!("foo");
        assert_eq!(s.to_string(), "\"foo\"");
    }

    #[test]
    fn display_bool() {
        let s: Value = graphql_value!(false);
        assert_eq!(s.to_string(), "false");

        let s: Value = graphql_value!(true);
        assert_eq!(s.to_string(), "true");
    }

    #[test]
    fn display_list() {
        let s: Value = graphql_value!([1, null, "foo"]);
        assert_eq!(s.to_string(), "[1, null, \"foo\"]");
    }

    #[test]
    fn display_list_one_element() {
        let s: Value = graphql_value!([1]);
        assert_eq!(s.to_string(), "[1]");
    }

    #[test]
    fn display_list_empty() {
        let s: Value = graphql_value!([]);
        assert_eq!(s.to_string(), "[]");
    }

    #[test]
    fn display_object() {
        let s: Value = graphql_value!({
            "int": 1,
            "null": null,
            "string": "foo",
        });
        assert_eq!(
            s.to_string(),
            r#"{"int": 1, "null": null, "string": "foo"}"#,
        );
    }

    #[test]
    fn display_object_one_field() {
        let s: Value = graphql_value!({
            "int": 1,
        });
        assert_eq!(s.to_string(), r#"{"int": 1}"#);
    }

    #[test]
    fn display_object_empty() {
        let s: Value = graphql_value!({});
        assert_eq!(s.to_string(), r#"{}"#);
    }
}