genotype_project 0.8.1

Genotype language project crate
Documentation
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
use error::GTProjectError;
use genotype_config::GTConfig;
use glob::glob;
use miette::Result;
use rayon::Scope;
use std::{
    collections::HashSet,
    path::PathBuf,
    sync::{Arc, Mutex},
};

use crate::*;

#[derive(Debug, PartialEq, Clone)]
pub struct GTProject {
    pub root: Arc<PathBuf>,
    pub modules: Vec<GTProjectModule>,
}

impl GTProject {
    pub fn load(config: &GTConfig) -> Result<Self> {
        let src = config
            .root()
            .join(config.src())
            .canonicalize()
            .map_err(|_| {
                GTProjectError::Canonicalize(format!("root directory {:?}", config.root))
            })?;
        let src = Arc::new(src);
        let pattern = config.entry_pattern()?;

        let entry_paths = glob(&pattern).map_err(|_| GTProjectError::Unknown)?;
        let entries: Vec<GTPModulePath> = entry_paths
            .collect::<Result<Vec<_>, _>>()
            .map_err(|_| GTProjectError::Unknown)?
            .iter()
            .map(|entry| GTPModulePath::try_new(Arc::clone(&src), entry))
            .collect::<Result<Vec<_>, _>>()
            .map_err(|_| GTProjectError::Unknown)?;

        if entries.is_empty() {
            return Err(GTProjectError::NoEntries(pattern).into());
        }

        let processed_paths = Arc::new(Mutex::new(HashSet::new()));
        let modules: Arc<Mutex<Vec<Result<GTProjectModuleParse>>>> =
            Arc::new(Mutex::new(Vec::new()));

        rayon::scope(|scope| {
            for entry in entries {
                let processed_paths = Arc::clone(&processed_paths);
                let modules = Arc::clone(&modules);

                scope.spawn(|scope| Self::load_module(entry, scope, processed_paths, modules));
            }
        });

        // [TODO] Simplify and turn into errors
        let modules_parse = Arc::try_unwrap(modules)
            .expect("Mutex cannot be unwrapped")
            .into_inner()
            .expect("Mutex cannot be locked")
            .into_iter()
            .collect::<Result<Vec<_>>>()?;

        let resolve: GTPResolve = (&modules_parse).try_into()?;

        let mut modules = modules_parse
            .iter()
            .map(|parse| GTProjectModule::try_new(&resolve, &modules_parse, parse.clone()))
            .collect::<Result<Vec<_>, _>>()?;

        // [TODO] It's needed for tests, hide behind cfg(test), keep or replace with something like
        // set? Using HashSet will require Eq which will consequently break tests.
        modules.sort_by(|a, b| a.path.as_path().cmp(&b.path.as_path()));

        Ok(GTProject {
            root: src.clone(),
            modules,
        })
    }

    fn load_module(
        module_path: GTPModulePath,
        scope: &Scope<'_>,
        processed_paths: Arc<Mutex<HashSet<GTPModulePath>>>,
        modules: Arc<Mutex<Vec<Result<GTProjectModuleParse>>>>,
    ) {
        // Check if the module is already processed to avoid infinite recursion.
        {
            let mut processed = processed_paths.lock().expect("Failed to lock modules");
            if processed.contains(&module_path) {
                return;
            }
            processed.insert(module_path.clone());
        }

        let module_id = module_path.as_id().source_str().to_owned().into();
        let result = GTProjectModuleParse::try_new(module_id, module_path).and_then(|parse| {
            parse.deps().and_then(|deps| {
                // Iterate each module dependency and load it in a thread.
                for dep in deps {
                    let processed_paths = Arc::clone(&processed_paths);
                    let modules = Arc::clone(&modules);

                    scope.spawn(|scope| {
                        Self::load_module(dep, scope, processed_paths, modules);
                    });
                }

                // Push the module parse result to the modules vector.
                {
                    let mut modules = modules.lock().expect("Failed to lock modules");
                    modules.push(Ok(parse));
                }

                Ok(())
            })
        });

        // If parsing failed, push the error to the modules vector.
        if let Err(err) = result {
            let mut modules = modules.lock().expect("Failed to lock modules");
            modules.push(Err(err));
        }
    }
}

#[cfg(test)]
mod tests {
    use std::{collections::HashMap, fs::read_to_string};

    use crate::{GTPModuleIdentifierSource, GTPModuleResolve};

    use super::*;
    use genotype_parser::*;
    use miette::NamedSource;
    use pretty_assertions::assert_eq;

    #[test]
    fn test_glob() {
        let project = GTProject::load(&GTConfig::from_root("module", "./examples/basic"));
        assert_eq!(project.unwrap(), basic_project());
    }

    #[test]
    fn test_entry() {
        let project = GTProject::load(&GTConfig::from_entry(
            "module",
            "./examples/basic",
            "order.type",
        ));
        assert_eq!(project.unwrap(), basic_project());
    }

    #[test]
    fn test_process_anonymous() {
        let root = Arc::new(PathBuf::from("./examples/process").canonicalize().unwrap());
        let module_path = GTPModulePath::try_new(
            root.clone(),
            &PathBuf::from("./examples/process/anonymous.type"),
        )
        .unwrap();
        let project = GTProject::load(&GTConfig::from_entry(
            "module",
            "./examples/process",
            "anonymous.type",
        ));
        assert_eq!(
            project.unwrap(),
            GTProject {
                root: root.clone(),
                modules: vec![GTProjectModule {
                    path: module_path.clone(),
                    module: GTModule {
                        id: "anonymous".into(),
                        doc: None,
                        imports: vec![],
                        aliases: vec![
                            GTAlias {
                                id: GTDefinitionId("anonymous".into(), "Order".into()),
                                span: (0, 91).into(),
                                doc: None,
                                attributes: vec![],
                                name: GTIdentifier::new((0, 5).into(), "Order".into()),
                                descriptor: GTObject {
                                    span: (8, 91).into(),
                                    name: GTIdentifier::new((0, 5).into(), "Order".into()).into(),
                                    extensions: vec![],
                                    properties: vec![GTProperty {
                                        span: (12, 89).into(),
                                        doc: None,
                                        attributes: vec![],
                                        name: GTKey::new((12, 20).into(), "delivery".into()),
                                        descriptor: GTObject {
                                            span: (22, 89).into(),
                                            name: GTObjectName::Alias(
                                                GTIdentifier::new(
                                                    (22, 89).into(),
                                                    "OrderDelivery".into()
                                                ),
                                                GTObjectNameParent::Property(
                                                    GTIdentifier::new(
                                                        (0, 5).into(),
                                                        "Order".into()
                                                    ),
                                                    vec![GTKey::new(
                                                        (12, 20).into(),
                                                        "delivery".into()
                                                    )]
                                                ),
                                            ),
                                            extensions: vec![],
                                            properties: vec![GTProperty {
                                                span: (28, 85).into(),
                                                doc: None,
                                                attributes: vec![],
                                                name: GTKey::new((28, 35).into(), "address".into()),
                                                descriptor: GTObject {
                                                    span: (37, 85).into(),
                                                    name: GTObjectName::Alias(
                                                        GTIdentifier::new(
                                                            (37, 85).into(),
                                                            "OrderDeliveryAddress".into()
                                                        ),
                                                        GTObjectNameParent::Property(
                                                            GTIdentifier::new(
                                                                (0, 5).into(),
                                                                "Order".into()
                                                            ),
                                                            vec![
                                                                GTKey::new(
                                                                    (12, 20).into(),
                                                                    "delivery".into()
                                                                ),
                                                                GTKey::new(
                                                                    (28, 35).into(),
                                                                    "address".into()
                                                                )
                                                            ]
                                                        ),
                                                    ),
                                                    extensions: vec![],
                                                    properties: vec![
                                                        GTProperty {
                                                            span: (45, 59).into(),
                                                            doc: None,
                                                            attributes: vec![],
                                                            name: GTKey::new(
                                                                (45, 51).into(),
                                                                "street".into()
                                                            ),
                                                            descriptor: GTPrimitive::String(
                                                                (53, 59).into()
                                                            )
                                                            .into(),
                                                            required: true,
                                                        },
                                                        GTProperty {
                                                            span: (67, 79).into(),
                                                            doc: None,
                                                            attributes: vec![],
                                                            name: GTKey::new(
                                                                (67, 71).into(),
                                                                "city".into()
                                                            ),
                                                            descriptor: GTPrimitive::String(
                                                                (73, 79).into()
                                                            )
                                                            .into(),
                                                            required: true,
                                                        }
                                                    ],
                                                }
                                                .into(),
                                                required: true,
                                            }],
                                        }
                                        .into(),
                                        required: true,
                                    }],
                                }
                                .into(),
                            },
                            GTAlias {
                                id: GTDefinitionId("anonymous".into(), "Email".into()),
                                span: (93, 146).into(),
                                doc: None,
                                attributes: vec![],
                                name: GTIdentifier::new((93, 98).into(), "Email".into()),
                                descriptor: GTUnion {
                                    span: (101, 146).into(),
                                    descriptors: vec![
                                        GTPrimitive::String((101, 107).into()).into(),
                                        GTObject {
                                            span: (110, 146).into(),
                                            name: GTObjectName::Alias(
                                                GTIdentifier::new(
                                                    (110, 146).into(),
                                                    "EmailObj".into()
                                                ),
                                                GTObjectNameParent::Alias(GTIdentifier::new(
                                                    (93, 98).into(),
                                                    "Email".into()
                                                ),),
                                            ),
                                            extensions: vec![],
                                            properties: vec![
                                                GTProperty {
                                                    span: (114, 126).into(),
                                                    doc: None,
                                                    attributes: vec![],
                                                    name: GTKey::new(
                                                        (114, 118).into(),
                                                        "name".into()
                                                    ),
                                                    descriptor: GTPrimitive::String(
                                                        (120, 126).into()
                                                    )
                                                    .into(),
                                                    required: true,
                                                },
                                                GTProperty {
                                                    span: (130, 143).into(),
                                                    doc: None,
                                                    attributes: vec![],
                                                    name: GTKey::new(
                                                        (130, 135).into(),
                                                        "email".into()
                                                    ),
                                                    descriptor: GTPrimitive::String(
                                                        (137, 143).into()
                                                    )
                                                    .into(),
                                                    required: true,
                                                }
                                            ],
                                        }
                                        .into(),
                                    ]
                                }
                                .into()
                            }
                        ],
                    },
                    resolve: GTPModuleResolve {
                        paths: Default::default(),
                        identifiers: Default::default(),
                        definitions: Default::default(),
                    },
                    source_code: NamedSource::new(
                        "anonymous.type",
                        read_to_string(&module_path).unwrap(),
                    ),
                },],
            }
        );
    }

    fn basic_project() -> GTProject {
        let root = Arc::new(PathBuf::from("./examples/basic").canonicalize().unwrap());
        let author_path =
            GTPModulePath::try_new(root.clone(), &PathBuf::from("./examples/basic/author.type"))
                .unwrap();
        let book_path =
            GTPModulePath::try_new(root.clone(), &PathBuf::from("./examples/basic/book.type"))
                .unwrap();
        let order_path =
            GTPModulePath::try_new(root.clone(), &PathBuf::from("./examples/basic/order.type"))
                .unwrap();
        let user_path =
            GTPModulePath::try_new(root.clone(), &PathBuf::from("./examples/basic/user.type"))
                .unwrap();

        GTProject {
            root: root.clone(),
            modules: vec![
                GTProjectModule {
                    path: author_path.clone(),
                    module: GTModule {
                        id: "author".into(),
                        doc: None,
                        imports: vec![],
                        aliases: vec![GTAlias {
                            id: GTDefinitionId("author".into(), "Author".into()),
                            span: (0, 27).into(),
                            doc: None,
                            attributes: vec![],
                            name: GTIdentifier::new((0, 6).into(), "Author".into()),
                            descriptor: GTDescriptor::Object(GTObject {
                                span: (9, 27).into(),
                                name: GTIdentifier::new((0, 6).into(), "Author".into()).into(),
                                extensions: vec![],
                                properties: vec![GTProperty {
                                    span: (13, 25).into(),
                                    doc: None,
                                    attributes: vec![],
                                    name: GTKey::new((13, 17).into(), "name".into()),
                                    descriptor: GTPrimitive::String((19, 25).into()).into(),
                                    required: true,
                                }],
                            }),
                        }],
                    },
                    resolve: GTPModuleResolve {
                        paths: Default::default(),
                        identifiers: Default::default(),
                        definitions: HashMap::from_iter([]),
                    },
                    source_code: NamedSource::new(
                        "author.type",
                        read_to_string(&author_path).unwrap(),
                    ),
                },
                GTProjectModule {
                    path: book_path.clone(),
                    module: GTModule {
                        id: "book".into(),
                        doc: None,
                        imports: vec![GTImport {
                            span: (0, 19).into(),
                            path: GTPath::new(
                                (4, 12).into(),
                                GTPathModuleId::Resolved(GTModuleId("author".into())),
                                "./author".into(),
                            ),
                            reference: GTImportReference::Name(
                                (13, 19).into(),
                                GTIdentifier::new((13, 19).into(), "Author".into()),
                            ),
                        }],
                        aliases: vec![GTAlias {
                            id: GTDefinitionId("book".into(), "Book".into()),
                            span: (21, 64).into(),
                            doc: None,
                            attributes: vec![],
                            name: GTIdentifier::new((21, 25).into(), "Book".into()),
                            descriptor: GTDescriptor::Object(GTObject {
                                span: (28, 64).into(),
                                name: GTIdentifier::new((21, 25).into(), "Book".into()).into(),
                                extensions: vec![],
                                properties: vec![
                                    GTProperty {
                                        span: (32, 45).into(),
                                        doc: None,
                                        attributes: vec![],
                                        name: GTKey::new((32, 37).into(), "title".into()),
                                        descriptor: GTPrimitive::String((39, 45).into()).into(),
                                        required: true,
                                    },
                                    GTProperty {
                                        span: (48, 62).into(),
                                        doc: None,
                                        attributes: vec![],
                                        name: GTKey::new((48, 54).into(), "author".into()),
                                        descriptor: GTDescriptor::Reference(GTReference {
                                            span: (56, 62).into(),
                                            id: GTReferenceId("book".into(), (56, 62).into()),
                                            definition_id: GTReferenceDefinitionId::Resolved(
                                                GTDefinitionId("author".into(), "Author".into()),
                                            ),
                                            identifier: GTIdentifier::new(
                                                (56, 62).into(),
                                                "Author".into(),
                                            ),
                                        }),
                                        required: true,
                                    },
                                ],
                            }),
                        }],
                    },
                    resolve: GTPModuleResolve {
                        paths: HashMap::from_iter([(
                            GTPath::parse((4, 12).into(), "./author").unwrap(),
                            GTPModulePathResolve {
                                module_path: author_path.clone(),
                            },
                        )]),
                        identifiers: HashMap::from_iter([(
                            GTIdentifier::new((56, 62).into(), "Author".into()),
                            GTPModuleIdentifierResolve {
                                source: GTPModuleIdentifierSource::External(
                                    GTPath::parse((4, 12).into(), "./author").unwrap(),
                                ),
                            },
                        )]),
                        definitions: HashMap::from_iter([(
                            GTDefinitionId("author".into(), "Author".into()),
                            GTPModuleDefinitionResolve {
                                references: HashSet::from_iter([GTReferenceId(
                                    "book".into(),
                                    (56, 62).into(),
                                )]),
                                deps: Default::default(),
                            },
                        )]),
                    },
                    source_code: NamedSource::new("book.type", read_to_string(&book_path).unwrap()),
                },
                GTProjectModule {
                    path: order_path.clone(),
                    module: GTModule {
                        id: "order".into(),
                        doc: None,
                        imports: vec![GTImport {
                            span: (0, 15).into(),
                            path: GTPath::new(
                                (4, 10).into(),
                                GTPathModuleId::Resolved("book".into()),
                                "./book".into(),
                            ),
                            reference: GTImportReference::Name(
                                (11, 15).into(),
                                GTIdentifier::new((11, 15).into(), "Book".into()),
                            ),
                        }],
                        aliases: vec![GTAlias {
                            id: GTDefinitionId("order".into(), "Order".into()),
                            span: (17, 64).into(),
                            doc: None,
                            attributes: vec![],
                            name: GTIdentifier::new((17, 22).into(), "Order".into()),
                            descriptor: GTDescriptor::Object(GTObject {
                                span: (25, 64).into(),
                                name: GTIdentifier::new((17, 22).into(), "Order".into()).into(),
                                extensions: vec![],
                                properties: vec![
                                    GTProperty {
                                        span: (29, 46).into(),
                                        doc: None,
                                        attributes: vec![],
                                        name: GTKey::new((29, 33).into(), "user".into()),
                                        descriptor: GTDescriptor::InlineImport(GTInlineImport {
                                            span: (35, 46).into(),
                                            path: GTPath::parse((35, 41).into(), "./user").unwrap(),
                                            name: GTIdentifier::new((42, 46).into(), "User".into()),
                                        }),
                                        required: true,
                                    },
                                    GTProperty {
                                        span: (49, 62).into(),
                                        doc: None,
                                        attributes: vec![],
                                        name: GTKey::new((49, 54).into(), "books".into()),
                                        descriptor: GTDescriptor::Array(Box::new(GTArray {
                                            span: (56, 62).into(),
                                            descriptor: GTDescriptor::Reference(GTReference {
                                                span: (57, 61).into(),
                                                id: GTReferenceId("order".into(), (57, 61).into()),
                                                definition_id: GTReferenceDefinitionId::Resolved(
                                                    GTDefinitionId("book".into(), "Book".into()),
                                                ),
                                                identifier: GTIdentifier::new(
                                                    (57, 61).into(),
                                                    "Book".into(),
                                                ),
                                            }),
                                        })),
                                        required: true,
                                    },
                                ],
                            }),
                        }],
                    },
                    resolve: GTPModuleResolve {
                        paths: HashMap::from_iter([
                            (
                                GTPath::parse((4, 10).into(), "./book").unwrap(),
                                GTPModulePathResolve {
                                    module_path: book_path.clone(),
                                },
                            ),
                            (
                                GTPath::parse((35, 41).into(), "./user").unwrap(),
                                GTPModulePathResolve {
                                    module_path: user_path.clone(),
                                },
                            ),
                        ]),
                        identifiers: HashMap::from_iter([(
                            GTIdentifier::new((57, 61).into(), "Book".into()),
                            GTPModuleIdentifierResolve {
                                source: GTPModuleIdentifierSource::External(
                                    GTPath::parse((4, 10).into(), "./book").unwrap(),
                                ),
                            },
                        )]),
                        definitions: HashMap::from_iter([(
                            GTDefinitionId("book".into(), "Book".into()),
                            GTPModuleDefinitionResolve {
                                references: HashSet::from_iter([GTReferenceId(
                                    "order".into(),
                                    (57, 61).into(),
                                )]),
                                deps: Default::default(),
                            },
                        )]),
                    },
                    source_code: NamedSource::new(
                        "order.type",
                        read_to_string(&order_path).unwrap(),
                    ),
                },
                GTProjectModule {
                    path: user_path.clone(),
                    module: GTModule {
                        id: "user".into(),
                        doc: None,
                        imports: vec![],
                        aliases: vec![GTAlias {
                            id: GTDefinitionId("user".into(), "User".into()),
                            span: (0, 41).into(),
                            doc: None,
                            attributes: vec![],
                            name: GTIdentifier::new((0, 4).into(), "User".into()),
                            descriptor: GTDescriptor::Object(GTObject {
                                span: (7, 41).into(),
                                name: GTIdentifier::new((0, 4).into(), "User".into()).into(),
                                extensions: vec![],
                                properties: vec![
                                    GTProperty {
                                        span: (11, 24).into(),
                                        doc: None,
                                        attributes: vec![],
                                        name: GTKey::new((11, 16).into(), "email".into()),
                                        descriptor: GTPrimitive::String((18, 24).into()).into(),
                                        required: true,
                                    },
                                    GTProperty {
                                        span: (27, 39).into(),
                                        doc: None,
                                        attributes: vec![],
                                        name: GTKey::new((27, 31).into(), "name".into()),
                                        descriptor: GTPrimitive::String((33, 39).into()).into(),
                                        required: true,
                                    },
                                ],
                            }),
                        }],
                    },
                    resolve: GTPModuleResolve {
                        paths: Default::default(),
                        identifiers: Default::default(),
                        definitions: Default::default(),
                    },
                    source_code: NamedSource::new("user.type", read_to_string(&user_path).unwrap()),
                },
            ],
        }
    }
}