1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use super::*;
use crate::Directive;

#[derive(Debug, Default)]
pub struct MongoDbSchemaRenderer {}

impl MongoDbSchemaRenderer {
    pub fn new() -> Self {
        Self::default()
    }
}

impl DatamodelRenderer for MongoDbSchemaRenderer {
    fn render_id(&self, mut id: IdFragment) -> String {
        // Mongo IDs require an `_id` mapping.
        id.upsert_directive("map", |existing| match existing {
            Some(dir) => {
                dir.args = vec!["\"_id\"".to_owned()];
                None
            }
            None => Some(Directive::new("map", vec!["\"_id\""])),
        });

        id.to_string()
    }

    // Currently just an accepted hack for MongoDB
    fn render_m2m(&self, m2m: M2mFragment) -> String {
        let M2mFragment {
            field_name,
            field_type,
            opposing_name,
            opposing_type,
            relation_name,
        } = m2m;

        // Add an array field for mongo, name: "<rel_field_name>_ids <Opposing type>[]"
        let fk_field_name = format!("{field_name}_ids");
        let additional_fk_field = format!("{fk_field_name} {opposing_type}[]");

        // Add @relation directive that specifies the local array to hold the FKs.
        let relation_directive = match relation_name {
            Some(name) => {
                format!(r#"@relation(name: "{name}", fields: [{fk_field_name}], references: [{opposing_name}])"#,)
            }
            None => format!(r#"@relation(fields: [{fk_field_name}], references: [{opposing_name}])"#),
        };

        format!("{additional_fk_field}\n{field_name} {field_type} {relation_directive}")
    }
}

#[cfg(test)]
mod mongo_render_tests {
    use super::*;
    use crate::IdFragment;

    #[test]
    fn add_id_mapping() {
        let fragment = IdFragment {
            field_name: "someIdField".to_owned(),
            field_type: "SomeType".to_owned(),
            directives: vec![Directive::new("id", vec![])],
        };

        let renderer = MongoDbSchemaRenderer::new();
        let rendered = renderer.render_id(fragment);

        assert_eq!(rendered, r#"someIdField SomeType @id @map("_id")"#)
    }

    #[test]
    fn update_id_mapping() {
        let fragment = IdFragment {
            field_name: "someIdField".to_owned(),
            field_type: "SomeType".to_owned(),
            directives: vec![Directive::new("id", vec![]), Directive::new("map", vec!["\"not_id\""])],
        };

        let renderer = MongoDbSchemaRenderer::new();
        let rendered = renderer.render_id(fragment);

        assert_eq!(rendered, r#"someIdField SomeType @id @map("_id")"#)
    }

    #[test]
    fn add_m2m_mapping() {
        let fragment = M2mFragment {
            field_name: "posts".to_owned(),
            field_type: "Post[]".to_owned(),
            opposing_name: "id".to_owned(),
            opposing_type: "String".to_owned(),
            relation_name: Some("test".to_owned()),
        };

        let renderer = MongoDbSchemaRenderer::new();
        let rendered = renderer.render_m2m(fragment);

        assert_eq!(
            rendered.trim(),
            "posts_ids String[]\nposts Post[] @relation(name: \"test\", fields: [posts_ids], references: [id])"
        )
    }
}