Skip to main content

fraiseql_core/schema/
graphql_value.rs

1//! Typed GraphQL literal values.
2//!
3//! [`GraphQLValue`] represents any valid GraphQL default value per the spec (§2.9).
4//! It replaces `serde_json::Value` at default-value sites so that invalid shapes
5//! (e.g. `{"__type": "unresolvable"}`) can be detected at schema compile time
6//! rather than at query execution time.
7//!
8//! # Wire format
9//!
10//! Uses `#[serde(untagged)]` so the JSON representation is identical to a plain
11//! `serde_json::Value`: `10` → `Int(10)`, `true` → `Boolean(true)`, etc.
12//! Existing compiled schemas require no migration.
13
14use std::fmt;
15
16use indexmap::IndexMap;
17use serde::{Deserialize, Serialize};
18
19use crate::error::{FraiseQLError, Result};
20
21/// A typed GraphQL literal value (spec §2.9).
22///
23/// Used for `default_value` in argument and input-field definitions.  The enum
24/// covers every kind that the GraphQL spec permits as a default: null, boolean,
25/// integer, float, string, enum value (stored as `String`), list, and object.
26///
27/// # Serialization
28///
29/// Serializes to / deserializes from plain JSON (no wrapper object), so
30/// `GraphQLValue::Int(42)` round-trips through JSON as `42` and
31/// `GraphQLValue::Boolean(true)` as `true`.
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33#[serde(untagged)]
34#[non_exhaustive]
35pub enum GraphQLValue {
36    /// `null`
37    Null,
38    /// `true` / `false`
39    Boolean(bool),
40    /// Integer literals (e.g. `42`, `-1`).
41    Int(i64),
42    /// Float literals (e.g. `3.14`). Only reached when the JSON number cannot
43    /// be losslessly represented as i64.
44    Float(f64),
45    /// String or enum-variant literals.  Both are JSON strings; callers that
46    /// need to distinguish enum values from string values must check the
47    /// argument's declared type.
48    String(String),
49    /// List literals (`[1, 2, 3]`).
50    List(Vec<GraphQLValue>),
51    /// Input-object literals (`{key: value}`).
52    Object(IndexMap<String, GraphQLValue>),
53}
54
55impl GraphQLValue {
56    /// Convert to an equivalent `serde_json::Value` for wire serialization.
57    #[must_use]
58    pub fn to_json(&self) -> serde_json::Value {
59        match self {
60            Self::Null => serde_json::Value::Null,
61            Self::Boolean(b) => serde_json::Value::Bool(*b),
62            Self::Int(i) => serde_json::json!(*i),
63            Self::Float(f) => serde_json::json!(*f),
64            Self::String(s) => serde_json::Value::String(s.clone()),
65            Self::List(v) => serde_json::Value::Array(v.iter().map(Self::to_json).collect()),
66            Self::Object(m) => {
67                serde_json::Value::Object(m.iter().map(|(k, v)| (k.clone(), v.to_json())).collect())
68            },
69        }
70    }
71
72    /// Parse from a `serde_json::Value`.
73    ///
74    /// Returns `Err` if the shape is not a valid GraphQL literal.  Currently
75    /// all JSON shapes are valid (`Object` maps to an input-object literal),
76    /// but this is the validation boundary where future restrictions can be
77    /// added.
78    ///
79    /// # Errors
80    ///
81    /// Returns [`FraiseQLError::Validation`] if a nested value cannot be
82    /// converted (e.g. a number that overflows i64 and f64).
83    pub fn from_json(v: &serde_json::Value) -> Result<Self> {
84        match v {
85            serde_json::Value::Null => Ok(Self::Null),
86            serde_json::Value::Bool(b) => Ok(Self::Boolean(*b)),
87            serde_json::Value::Number(n) => {
88                if let Some(i) = n.as_i64() {
89                    Ok(Self::Int(i))
90                } else if let Some(f) = n.as_f64() {
91                    Ok(Self::Float(f))
92                } else {
93                    Err(FraiseQLError::Validation {
94                        message: format!("default value number out of range: {n}"),
95                        path:    None,
96                    })
97                }
98            },
99            serde_json::Value::String(s) => Ok(Self::String(s.clone())),
100            serde_json::Value::Array(arr) => {
101                let items = arr.iter().map(Self::from_json).collect::<Result<Vec<_>>>()?;
102                Ok(Self::List(items))
103            },
104            serde_json::Value::Object(obj) => {
105                let map = obj
106                    .iter()
107                    .map(|(k, v)| Self::from_json(v).map(|gv| (k.clone(), gv)))
108                    .collect::<Result<IndexMap<_, _>>>()?;
109                Ok(Self::Object(map))
110            },
111        }
112    }
113}
114
115impl fmt::Display for GraphQLValue {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        write!(f, "{}", self.to_json())
118    }
119}