Skip to main content

mongo_graphql/schema/
definition.rs

1use serde::de;
2use serde::{Deserialize, Serialize};
3
4use super::inflect::{to_pascal_singular, to_plural};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(deny_unknown_fields)]
8pub struct SchemaDefinition {
9    pub collections: Vec<CollectionDef>,
10}
11
12impl SchemaDefinition {
13    pub fn collection_by_name(&self, name: &str) -> Option<&CollectionDef> {
14        self.collections.iter().find(|coll_def| coll_def.collection == name)
15    }
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct CollectionDef {
21    pub collection: String,
22
23    #[serde(default)]
24    pub graphql_name: Option<String>,
25
26    #[serde(default)]
27    pub description: Option<String>,
28
29    pub fields: Vec<FieldDef>,
30}
31
32impl CollectionDef {
33    pub fn type_name(&self) -> String {
34        self.graphql_name
35            .clone()
36            .unwrap_or_else(|| to_pascal_singular(&self.collection))
37    }
38
39    pub fn plural_name(&self) -> String {
40        let base = self
41            .graphql_name
42            .as_deref()
43            .unwrap_or(&self.collection);
44        to_plural(&base.to_lowercase())
45    }
46
47    pub fn singular_name(&self) -> String {
48        let base = self.graphql_name.as_deref().unwrap_or(&self.collection);
49        let mut chars = base.chars();
50        match chars.next() {
51            None => base.to_string(),
52            Some(first_char) => {
53                first_char.to_lowercase().collect::<String>() + chars.as_str()
54            }
55        }
56    }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60#[serde(deny_unknown_fields)]
61pub struct FieldDef {
62    pub name: String,
63
64    #[serde(default)]
65    pub graphql_name: Option<String>,
66
67    #[serde(rename = "type")]
68    pub field_type: FieldType,
69
70    #[serde(default)]
71    pub required: bool,
72
73    #[serde(default)]
74    pub unique: bool,
75
76    #[serde(default)]
77    pub r#enum: Option<EnumDef>,
78
79    #[serde(default)]
80    pub description: Option<String>,
81}
82
83impl FieldDef {
84    pub fn graphql_name(&self) -> String {
85        self.graphql_name
86            .clone()
87            .unwrap_or_else(|| self.name.clone())
88    }
89}
90
91#[derive(Debug, Clone, Serialize)]
92#[serde(rename_all = "lowercase")]
93pub enum FieldType {
94    ID,
95    String,
96    Int,
97    Float,
98    Boolean,
99    DateTime,
100    Json,
101    /// e.g. ["String"] → [String!] in GraphQL.
102    List(Box<FieldType>),
103    Relation(RelationFieldDef),
104}
105
106impl<'de> Deserialize<'de> for FieldType {
107    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108    where
109        D: serde::Deserializer<'de>,
110    {
111        use serde_json::Value;
112
113        let value = Value::deserialize(deserializer)?;
114
115        match value {
116            Value::String(s) => match s.to_lowercase().as_str() {
117                "id" => Ok(FieldType::ID),
118                "string" => Ok(FieldType::String),
119                "int" => Ok(FieldType::Int),
120                "float" => Ok(FieldType::Float),
121                "boolean" => Ok(FieldType::Boolean),
122                "datetime" => Ok(FieldType::DateTime),
123                "json" => Ok(FieldType::Json),
124                _ => Err(de::Error::custom(format!("unknown type: {}", s))),
125            },
126            Value::Array(arr) if arr.len() == 1 => {
127                let first = arr.into_iter().next().ok_or_else(|| {
128                    de::Error::custom("expected exactly one list element")
129                })?;
130                let inner: FieldType =
131                    serde_json::from_value(first).map_err(de::Error::custom)?;
132                Ok(FieldType::List(Box::new(inner)))
133            }
134            Value::Object(ref obj) => {
135                let rel_value = if let Some(inner) = obj.get("relation") {
136                    inner.clone()
137                } else {
138                    value
139                };
140                let rel: RelationFieldDef =
141                    serde_json::from_value(rel_value).map_err(de::Error::custom)?;
142                Ok(FieldType::Relation(rel))
143            }
144            Value::Array(ref arr) if arr.len() != 1 => Err(de::Error::custom(format!(
145                "list type must contain exactly one element, got {}",
146                arr.len()
147            ))),
148            _ => Err(de::Error::custom(
149                "expected a type string (e.g. \"String\"), a list type (e.g. [\"String\"]), or a relation object",
150            )),
151        }
152    }
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
156#[serde(deny_unknown_fields)]
157pub struct RelationFieldDef {
158    pub kind: RelationKind,
159    pub collection: String,
160    pub reference_field: String,
161
162    /// Custom name for the auto-generated reverse field on the target collection.
163    #[serde(default)]
164    pub reverse_name: Option<String>,
165
166    /// Only for ManyToMany.
167    #[serde(default)]
168    pub junction: Option<JunctionDef>,
169}
170
171#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
172#[serde(rename_all = "snake_case")]
173pub enum RelationKind {
174    /// FK on this collection. e.g. hero.team_id → team.
175    OneToMany,
176    /// Unique FK on one side. e.g. hero.secret_lair_id ↔ secret_lair.
177    OneToOne,
178    /// Both sides must be declared.
179    ManyToMany,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
183#[serde(deny_unknown_fields)]
184pub struct EnumDef {
185    pub name: String,
186    pub values: Vec<String>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(deny_unknown_fields)]
191pub struct JunctionDef {
192    pub collection: String,
193    pub local_field: String,
194    pub foreign_field: String,
195    pub foreign_reference: String,
196
197    #[serde(default)]
198    pub metadata_fields: Vec<String>,
199}