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 DuplicateProcedure {
55 name: SmolStr,
56 existing: SourceLoc,
57 incoming: SourceLoc,
58 },
59 MultipleDatasource {
60 existing: SourceLoc,
61 incoming: SourceLoc,
62 },
63}
64
65pub(crate) fn loc(source_id: Option<SourceId>, span: Span) -> SourceLoc {
70 SourceLoc::new(source_id.unwrap_or(SourceId(u32::MAX)), span)
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::ast::Schema;
77 use crate::loader::{SourceId, stamp_source};
78 use crate::parser::parse_schema;
79
80 fn stamped(input: &str, sid: u32) -> Schema {
81 let mut s = parse_schema(input).unwrap();
82 stamp_source(&mut s, SourceId(sid));
83 s
84 }
85
86 #[test]
87 fn merge_distinct_models_succeeds() {
88 let mut a = stamped("model A { id Int @id @auto }", 0);
89 let b = stamped("model B { id Int @id @auto }", 1);
90 assert!(a.try_merge(b).is_ok());
91 assert!(a.get_model("A").is_some());
92 assert!(a.get_model("B").is_some());
93 assert_eq!(a.get_model("B").unwrap().source_id, Some(SourceId(1)));
94 }
95
96 #[test]
97 fn merge_duplicate_models_reports_both_locations() {
98 let mut a = stamped("model User { id Int @id @auto }", 0);
99 let b = stamped("model User { id Int @id @auto }", 1);
100 let err = a.try_merge(b).unwrap_err();
101 assert_eq!(err.len(), 1);
102 match &err[0] {
103 MergeConflict::DuplicateModel {
104 name,
105 existing,
106 incoming,
107 } => {
108 assert_eq!(name.as_str(), "User");
109 assert_eq!(existing.source, SourceId(0));
110 assert_eq!(incoming.source, SourceId(1));
111 }
112 other => panic!("unexpected conflict: {other:?}"),
113 }
114 }
115
116 #[test]
117 fn merge_collects_all_conflicts_without_short_circuit() {
118 let mut a = stamped(
119 "model A { id Int @id @auto } model B { id Int @id @auto }",
120 0,
121 );
122 let b = stamped(
123 "model A { id Int @id @auto } model B { id Int @id @auto }",
124 1,
125 );
126 let err = a.try_merge(b).unwrap_err();
127 assert_eq!(err.len(), 2);
128 }
129
130 #[test]
131 fn merge_two_datasources_errors() {
132 let mut a = stamped(r#"datasource db { provider = "postgresql" url = "x" }"#, 0);
133 let b = stamped(r#"datasource db { provider = "postgresql" url = "y" }"#, 1);
134 let err = a.try_merge(b).unwrap_err();
135 assert!(matches!(err[0], MergeConflict::MultipleDatasource { .. }));
136 }
137}