1#![allow(missing_docs)]
19
20use brain_db_sdk::wire::types::{
21 SchemaGetRequest, SchemaGetResponse, SchemaReplaceRequest, SchemaReplaceResponse,
22 SchemaUploadRequest, SchemaUploadResponse, SchemaValidateRequest, SchemaValidateResponse,
23 SchemaValidationErrorWire,
24};
25use serde::{Deserialize, Serialize};
26
27#[derive(Debug, Serialize)]
29pub struct SchemaErrorDto {
30 pub code: String,
31 pub message: String,
32 pub line: u32,
34 pub column: u32,
35 pub length: u32,
36 pub severity: u8,
38}
39
40impl From<&SchemaValidationErrorWire> for SchemaErrorDto {
41 fn from(e: &SchemaValidationErrorWire) -> Self {
42 Self {
43 code: e.code.clone(),
44 message: e.message.clone(),
45 line: e.line,
46 column: e.column,
47 length: e.length,
48 severity: e.severity,
49 }
50 }
51}
52
53fn errors(src: &[SchemaValidationErrorWire]) -> Vec<SchemaErrorDto> {
54 src.iter().map(SchemaErrorDto::from).collect()
55}
56
57#[derive(Debug, Serialize)]
59pub struct SchemaDto {
60 pub namespace: String,
61 pub schema_version: u32,
62 pub schema_document: String,
65 pub uploaded_at_unix_nanos: u64,
66 pub validator_version: u32,
67}
68
69impl From<SchemaGetResponse> for SchemaDto {
70 fn from(r: SchemaGetResponse) -> Self {
71 Self {
72 namespace: r.namespace,
73 schema_version: r.schema_version,
74 schema_document: r.schema_document,
75 uploaded_at_unix_nanos: r.uploaded_at_unix_nanos,
76 validator_version: r.validator_version,
77 }
78 }
79}
80
81#[derive(Debug, Deserialize)]
83pub struct SchemaUploadBody {
84 pub schema_document: String,
85 #[serde(default)]
87 pub dry_run: bool,
88 #[serde(default)]
90 pub allow_breaking: bool,
91}
92
93#[derive(Debug, Serialize)]
95pub struct SchemaUploadDto {
96 pub namespace: String,
97 pub schema_version: u32,
99 pub backward_compatible: bool,
100 pub validation_errors: Vec<SchemaErrorDto>,
101}
102
103impl From<SchemaUploadResponse> for SchemaUploadDto {
104 fn from(r: SchemaUploadResponse) -> Self {
105 Self {
106 namespace: r.namespace,
107 schema_version: r.schema_version,
108 backward_compatible: r.backward_compatible,
109 validation_errors: errors(&r.validation_errors),
110 }
111 }
112}
113
114#[derive(Debug, Deserialize)]
116pub struct SchemaValidateBody {
117 pub schema_document: String,
118}
119
120#[derive(Debug, Serialize)]
122pub struct SchemaValidateDto {
123 pub namespace: String,
126 pub would_be_version: u32,
128 pub validation_errors: Vec<SchemaErrorDto>,
129}
130
131impl From<SchemaValidateResponse> for SchemaValidateDto {
132 fn from(r: SchemaValidateResponse) -> Self {
133 Self {
134 namespace: r.namespace,
135 would_be_version: r.would_be_version,
136 validation_errors: errors(&r.validation_errors),
137 }
138 }
139}
140
141#[derive(Debug, Deserialize)]
147pub struct SchemaReplaceBody {
148 pub schema_document: String,
149 pub force_drop_existing: bool,
150}
151
152#[derive(Debug, Serialize)]
154pub struct SchemaReplaceDto {
155 pub namespace: String,
156 pub schema_version: u32,
157 pub dropped_count: u32,
160 pub validation_errors: Vec<SchemaErrorDto>,
161}
162
163impl From<SchemaReplaceResponse> for SchemaReplaceDto {
164 fn from(r: SchemaReplaceResponse) -> Self {
165 Self {
166 namespace: r.namespace,
167 schema_version: r.schema_version,
168 dropped_count: r.dropped_count,
169 validation_errors: errors(&r.validation_errors),
170 }
171 }
172}
173
174#[derive(Debug, Deserialize)]
179pub struct SchemaGetQuery {
180 #[serde(default)]
181 pub namespace: String,
182 #[serde(default)]
183 pub version: u32,
184}
185
186impl SchemaGetQuery {
187 #[must_use]
188 pub fn to_request(&self) -> SchemaGetRequest {
189 SchemaGetRequest {
190 namespace: self.namespace.clone(),
191 version: self.version,
192 act_as: None,
193 }
194 }
195}
196
197impl SchemaUploadBody {
198 #[must_use]
200 pub fn to_request(self) -> SchemaUploadRequest {
201 SchemaUploadRequest {
202 schema_document: self.schema_document,
203 dry_run: self.dry_run,
204 allow_breaking: self.allow_breaking,
205 request_id: brain_db_sdk::new_id(),
206 act_as: None,
207 }
208 }
209}
210
211impl SchemaValidateBody {
212 #[must_use]
213 pub fn to_request(self) -> SchemaValidateRequest {
214 SchemaValidateRequest {
215 schema_document: self.schema_document,
216 act_as: None,
217 }
218 }
219}
220
221impl SchemaReplaceBody {
222 pub fn to_request(self) -> Result<SchemaReplaceRequest, String> {
228 if !self.force_drop_existing {
229 return Err(
230 "force_drop_existing must be true — PUT /v1/schema drops every declared row in \
231 the namespace before the new document lands"
232 .into(),
233 );
234 }
235 Ok(SchemaReplaceRequest {
236 schema_document: self.schema_document,
237 force_drop_existing: true,
238 request_id: brain_db_sdk::new_id(),
239 act_as: None,
240 })
241 }
242}
243
244#[cfg(test)]
245mod schema_tests {
246 use super::*;
247 use brain_db_sdk::wire::types::SchemaUploadResponse;
248
249 fn sample_error() -> SchemaValidationErrorWire {
250 SchemaValidationErrorWire {
251 code: "E001".into(),
252 message: "undeclared type".into(),
253 line: 3,
254 column: 5,
255 length: 7,
256 severity: 2,
257 }
258 }
259
260 #[test]
261 fn get_query_forwards_namespace_and_version() {
262 let req = SchemaGetQuery {
263 namespace: "people".into(),
264 version: 4,
265 }
266 .to_request();
267 assert_eq!(req.namespace, "people");
268 assert_eq!(req.version, 4);
269 }
270
271 #[test]
272 fn upload_and_validate_bodies_build_requests() {
273 let up = SchemaUploadBody {
274 schema_document: "entity P {}".into(),
275 dry_run: true,
276 allow_breaking: true,
277 }
278 .to_request();
279 assert_eq!(up.schema_document, "entity P {}");
280 assert!(up.dry_run);
281 assert!(up.allow_breaking);
282 assert_ne!(up.request_id, [0u8; 16]); let val = SchemaValidateBody {
285 schema_document: "entity Q {}".into(),
286 }
287 .to_request();
288 assert_eq!(val.schema_document, "entity Q {}");
289 }
290
291 #[test]
292 fn replace_body_requires_force_drop_existing() {
293 assert!(
294 SchemaReplaceBody {
295 schema_document: "entity P {}".into(),
296 force_drop_existing: false,
297 }
298 .to_request()
299 .is_err()
300 );
301 let req = SchemaReplaceBody {
302 schema_document: "entity P {}".into(),
303 force_drop_existing: true,
304 }
305 .to_request()
306 .unwrap();
307 assert!(req.force_drop_existing);
308 assert_eq!(req.schema_document, "entity P {}");
309 }
310
311 #[test]
312 fn error_dto_maps_position_and_severity() {
313 let dto = SchemaErrorDto::from(&sample_error());
314 assert_eq!(dto.code, "E001");
315 assert_eq!(dto.message, "undeclared type");
316 assert_eq!((dto.line, dto.column, dto.length), (3, 5, 7));
317 assert_eq!(dto.severity, 2);
318 }
319
320 #[test]
321 fn get_response_maps_and_drops_source_blob() {
322 let dto = SchemaDto::from(SchemaGetResponse {
323 namespace: "people".into(),
324 schema_version: 2,
325 schema_document: "entity Person {}".into(),
326 source_blob: vec![1, 2, 3], uploaded_at_unix_nanos: 99,
328 validator_version: 1,
329 });
330 assert_eq!(dto.namespace, "people");
331 assert_eq!(dto.schema_version, 2);
332 assert_eq!(dto.schema_document, "entity Person {}");
333 assert_eq!(dto.uploaded_at_unix_nanos, 99);
334 assert_eq!(dto.validator_version, 1);
335 }
336
337 #[test]
338 fn upload_response_maps_errors_and_compat() {
339 let dto = SchemaUploadDto::from(SchemaUploadResponse {
340 namespace: "people".into(),
341 schema_version: 0, validation_errors: vec![sample_error()],
343 backward_compatible: false,
344 migration_summary_blob: vec![],
345 });
346 assert_eq!(dto.namespace, "people");
347 assert_eq!(dto.schema_version, 0);
348 assert!(!dto.backward_compatible);
349 assert_eq!(dto.validation_errors.len(), 1);
350 assert_eq!(dto.validation_errors[0].code, "E001");
351 }
352
353 #[test]
354 fn validate_response_maps_would_be_version_and_errors() {
355 let dto = SchemaValidateDto::from(SchemaValidateResponse {
356 namespace: "people".into(),
357 would_be_version: 3,
358 validation_errors: vec![],
359 });
360 assert_eq!(dto.namespace, "people");
361 assert_eq!(dto.would_be_version, 3);
362 assert!(dto.validation_errors.is_empty());
363 }
364
365 #[test]
366 fn replace_response_surfaces_dropped_count() {
367 let dto = SchemaReplaceDto::from(SchemaReplaceResponse {
368 namespace: "people".into(),
369 schema_version: 5,
370 dropped_count: 12,
371 validation_errors: vec![sample_error()],
372 });
373 assert_eq!(dto.namespace, "people");
374 assert_eq!(dto.schema_version, 5);
375 assert_eq!(dto.dropped_count, 12);
376 assert_eq!(dto.validation_errors.len(), 1);
377 }
378}