prax_schema/loader/
merge.rs1use smol_str::SmolStr;
4
5use super::source::{SourceId, SourceLoc};
6use crate::ast::Span;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum MergeConflict {
14 DuplicateModel {
15 name: SmolStr,
16 existing: SourceLoc,
17 incoming: SourceLoc,
18 },
19 DuplicateEnum {
20 name: SmolStr,
21 existing: SourceLoc,
22 incoming: SourceLoc,
23 },
24 DuplicateType {
25 name: SmolStr,
26 existing: SourceLoc,
27 incoming: SourceLoc,
28 },
29 DuplicateView {
30 name: SmolStr,
31 existing: SourceLoc,
32 incoming: SourceLoc,
33 },
34 DuplicateServerGroup {
35 name: SmolStr,
36 existing: SourceLoc,
37 incoming: SourceLoc,
38 },
39 DuplicatePolicy {
40 name: SmolStr,
41 existing: SourceLoc,
42 incoming: SourceLoc,
43 },
44 DuplicateGenerator {
45 name: SmolStr,
46 existing: SourceLoc,
47 incoming: SourceLoc,
48 },
49 DuplicateRawSql {
50 name: SmolStr,
51 existing: SourceLoc,
52 incoming: SourceLoc,
53 },
54 MultipleDatasource {
55 existing: SourceLoc,
56 incoming: SourceLoc,
57 },
58}
59
60pub(crate) fn loc(source_id: Option<SourceId>, span: Span) -> SourceLoc {
65 SourceLoc::new(source_id.unwrap_or(SourceId(u32::MAX)), span)
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::ast::Schema;
72 use crate::loader::{SourceId, stamp_source};
73 use crate::parser::parse_schema;
74
75 fn stamped(input: &str, sid: u32) -> Schema {
76 let mut s = parse_schema(input).unwrap();
77 stamp_source(&mut s, SourceId(sid));
78 s
79 }
80
81 #[test]
82 fn merge_distinct_models_succeeds() {
83 let mut a = stamped("model A { id Int @id @auto }", 0);
84 let b = stamped("model B { id Int @id @auto }", 1);
85 assert!(a.try_merge(b).is_ok());
86 assert!(a.get_model("A").is_some());
87 assert!(a.get_model("B").is_some());
88 assert_eq!(a.get_model("B").unwrap().source_id, Some(SourceId(1)));
89 }
90
91 #[test]
92 fn merge_duplicate_models_reports_both_locations() {
93 let mut a = stamped("model User { id Int @id @auto }", 0);
94 let b = stamped("model User { id Int @id @auto }", 1);
95 let err = a.try_merge(b).unwrap_err();
96 assert_eq!(err.len(), 1);
97 match &err[0] {
98 MergeConflict::DuplicateModel {
99 name,
100 existing,
101 incoming,
102 } => {
103 assert_eq!(name.as_str(), "User");
104 assert_eq!(existing.source, SourceId(0));
105 assert_eq!(incoming.source, SourceId(1));
106 }
107 other => panic!("unexpected conflict: {other:?}"),
108 }
109 }
110
111 #[test]
112 fn merge_collects_all_conflicts_without_short_circuit() {
113 let mut a = stamped(
114 "model A { id Int @id @auto } model B { id Int @id @auto }",
115 0,
116 );
117 let b = stamped(
118 "model A { id Int @id @auto } model B { id Int @id @auto }",
119 1,
120 );
121 let err = a.try_merge(b).unwrap_err();
122 assert_eq!(err.len(), 2);
123 }
124
125 #[test]
126 fn merge_two_datasources_errors() {
127 let mut a = stamped(r#"datasource db { provider = "postgresql" url = "x" }"#, 0);
128 let b = stamped(r#"datasource db { provider = "postgresql" url = "y" }"#, 1);
129 let err = a.try_merge(b).unwrap_err();
130 assert!(matches!(err[0], MergeConflict::MultipleDatasource { .. }));
131 }
132}