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,
};
#[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> {
pub fn null() -> Self {
Self::Null
}
pub fn list(l: Vec<Self>) -> Self {
Self::List(l)
}
pub fn object(o: Object<S>) -> Self {
Self::Object(o)
}
pub fn scalar<T: Into<S>>(s: T) -> Self {
Self::Scalar(s.into())
}
pub fn is_null(&self) -> bool {
matches!(*self, Self::Null)
}
pub fn as_object_value(&self) -> Option<&Object<S>> {
match self {
Self::Object(o) => Some(o),
_ => None,
}
}
pub fn into_object(self) -> Option<Object<S>> {
match self {
Self::Object(o) => Some(o),
_ => None,
}
}
pub fn as_mut_object_value(&mut self) -> Option<&mut Object<S>> {
match self {
Self::Object(o) => Some(o),
_ => None,
}
}
pub fn as_list_value(&self) -> Option<&Vec<Self>> {
match self {
Self::List(l) => Some(l),
_ => None,
}
}
pub fn as_scalar(&self) -> Option<&S> {
match self {
Self::Scalar(s) => Some(s),
_ => None,
}
}
pub fn map_scalar_value<T>(self) -> Value<T>
where
S: ScalarValue,
T: ScalarValue,
{
if TypeId::of::<T>() == TypeId::of::<S>() {
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(())
}
}
}
}
pub trait IntoValue<S> {
#[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#"{}"#);
}
}