Skip to main content

mongo_graphql/schema/
definition.rs

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