1use super::error::ViewError;
9use reinhardt_auth::{Permission, PermissionContext};
10use reinhardt_db::orm::model::filter_value_from_field_type;
11use reinhardt_db::orm::{
12 CustomManager, Filter, FilterCondition, FilterOperator, FilterValue, Model, QuerySet,
13 query_types::DbBackend,
14};
15use reinhardt_http::{AuthState, Request, Response};
16use reinhardt_rest::filters::FilterBackend;
17use reinhardt_rest::serializers::{ModelSerializer, Serializer};
18use serde::Serialize;
19use serde::de::DeserializeOwned;
20use std::marker::PhantomData;
21use std::sync::Arc;
22
23type QuerysetFn =
24 dyn Fn(&Request) -> std::result::Result<FilterCondition, ViewError> + Send + Sync + 'static;
25
26fn map_scope_field<T: Model>(field_name: &mut String) {
27 if let Some((prefix, name)) = field_name.rsplit_once('.') {
28 let mut mapped_name = name.to_owned();
29 map_scope_field::<T>(&mut mapped_name);
30 *field_name = format!("{prefix}.{mapped_name}");
31 return;
32 }
33
34 if let Some(field) = T::field_metadata()
35 .into_iter()
36 .find(|field| field.name == *field_name)
37 {
38 *field_name = field.db_column_name().to_owned();
39 }
40}
41
42fn map_scope_subquery_field<T: Model>(field_name: &mut String) {
43 map_scope_field::<T>(field_name);
44}
45
46fn map_scope_expression_sql<T: Model>(sql: &str) -> String {
47 let fields = T::field_metadata();
48 let bytes = sql.as_bytes();
49 let mut mapped = String::with_capacity(sql.len());
50 let mut index = 0;
51 while index < bytes.len() {
52 let quote = bytes[index] as char;
53 if matches!(quote, '"' | '`') {
54 let identifier_start = index + 1;
55 let mut cursor = identifier_start;
56 while cursor < bytes.len() {
57 if bytes[cursor] as char == quote {
58 if bytes.get(cursor + 1).map(|byte| *byte as char) == Some(quote) {
59 cursor += 2;
60 continue;
61 }
62 break;
63 }
64 cursor += 1;
65 }
66 if cursor < bytes.len() {
67 let identifier = &sql[identifier_start..cursor];
68 let replacement = fields
69 .iter()
70 .find(|field| field.name == identifier)
71 .map(|field| field.db_column_name())
72 .unwrap_or(identifier);
73 mapped.push(quote);
74 mapped.push_str(replacement);
75 mapped.push(quote);
76 index = cursor + 1;
77 continue;
78 }
79 }
80
81 let character = sql[index..]
82 .chars()
83 .next()
84 .expect("index is within the expression");
85 mapped.push(character);
86 index += character.len_utf8();
87 }
88 mapped
89}
90
91fn map_scope_query_condition<T: Model>(condition: &mut reinhardt_db::orm::expressions::Q) {
92 use reinhardt_db::orm::expressions::Q;
93
94 match condition {
95 Q::Condition { field, .. } => map_scope_field::<T>(field),
96 Q::Combined { conditions, .. } => {
97 for condition in conditions {
98 map_scope_query_condition::<T>(condition);
99 }
100 }
101 }
102}
103
104fn map_scope_annotation_value<T: Model>(
105 value: &mut reinhardt_db::orm::annotation::AnnotationValue,
106) {
107 use reinhardt_db::orm::annotation::AnnotationValue;
108
109 match value {
110 AnnotationValue::Field(field) => map_scope_field::<T>(&mut field.field),
111 AnnotationValue::Aggregate(aggregate) => {
112 if let Some(field) = &mut aggregate.field {
113 map_scope_field::<T>(field);
114 }
115 }
116 AnnotationValue::Expression(expression) => map_scope_annotation_expression::<T>(expression),
117 AnnotationValue::ArrayAgg(value) => value.map_fields(map_scope_order_by_field::<T>),
118 AnnotationValue::StringAgg(value) => value.map_fields(map_scope_order_by_field::<T>),
119 AnnotationValue::JsonbAgg(value) => value.map_fields(map_scope_order_by_field::<T>),
120 AnnotationValue::JsonbBuildObject(value) => {
121 value.map_fields(map_scope_field::<T>);
122 }
123 AnnotationValue::TsRank(value) => value.map_fields(map_scope_field::<T>),
124 AnnotationValue::Value(_) | AnnotationValue::Subquery(_) => {}
125 }
126}
127
128fn map_scope_annotation_expression<T: Model>(
129 expression: &mut reinhardt_db::orm::annotation::Expression,
130) {
131 use reinhardt_db::orm::annotation::Expression;
132
133 match expression {
134 Expression::Add(left, right)
135 | Expression::Subtract(left, right)
136 | Expression::Multiply(left, right)
137 | Expression::Divide(left, right) => {
138 map_scope_annotation_value::<T>(left);
139 map_scope_annotation_value::<T>(right);
140 }
141 Expression::Case { whens, default } => {
142 for when in whens {
143 map_scope_query_condition::<T>(&mut when.condition);
144 map_scope_annotation_value::<T>(&mut when.then);
145 }
146 if let Some(default) = default {
147 map_scope_annotation_value::<T>(default);
148 }
149 }
150 Expression::Coalesce(values) => {
151 for value in values {
152 map_scope_annotation_value::<T>(value);
153 }
154 }
155 }
156}
157
158fn map_scope_filter_value<T: Model>(value: &mut FilterValue) {
159 match value {
160 FilterValue::FieldRef(field) => map_scope_field::<T>(&mut field.field),
161 FilterValue::OuterRef(field) => map_scope_field::<T>(&mut field.field),
162 FilterValue::Expression(expression) => map_scope_annotation_expression::<T>(expression),
163 FilterValue::List(values) => {
164 for value in values {
165 map_scope_filter_value::<T>(value);
166 }
167 }
168 FilterValue::Range(start, end) => {
169 map_scope_filter_value::<T>(start);
170 map_scope_filter_value::<T>(end);
171 }
172 FilterValue::String(_)
173 | FilterValue::Timestamp(_)
174 | FilterValue::Date(_)
175 | FilterValue::Time(_)
176 | FilterValue::NaiveDateTime(_)
177 | FilterValue::Decimal(_)
178 | FilterValue::Uuid(_)
179 | FilterValue::Integer(_)
180 | FilterValue::Int(_)
181 | FilterValue::Float(_)
182 | FilterValue::Boolean(_)
183 | FilterValue::Bool(_)
184 | FilterValue::Null
185 | FilterValue::Array(_) => {}
186 }
187}
188
189fn map_scope_filter_column<T: Model>(filter: &mut Filter) {
190 filter.map_expression_source(map_scope_expression_sql::<T>);
191 map_scope_field::<T>(&mut filter.field);
192 map_scope_filter_value::<T>(&mut filter.value);
193}
194
195fn map_scope_filter_columns<T: Model>(condition: &mut FilterCondition) {
196 match condition {
197 FilterCondition::Single(filter) => map_scope_filter_column::<T>(filter),
198 FilterCondition::And(conditions) | FilterCondition::Or(conditions) => {
199 for condition in conditions {
200 map_scope_filter_columns::<T>(condition);
201 }
202 }
203 FilterCondition::Not(condition) => map_scope_filter_columns::<T>(condition),
204 }
205}
206
207fn scope_annotation_value_contains_opaque_subquery(
208 value: &reinhardt_db::orm::annotation::AnnotationValue,
209) -> bool {
210 use reinhardt_db::orm::annotation::AnnotationValue;
211
212 match value {
213 AnnotationValue::Subquery(_) => true,
214 AnnotationValue::Expression(expression) => {
215 scope_annotation_expression_contains_opaque_subquery(expression)
216 }
217 AnnotationValue::Value(_)
218 | AnnotationValue::Field(_)
219 | AnnotationValue::Aggregate(_)
220 | AnnotationValue::ArrayAgg(_)
221 | AnnotationValue::StringAgg(_)
222 | AnnotationValue::JsonbAgg(_)
223 | AnnotationValue::JsonbBuildObject(_)
224 | AnnotationValue::TsRank(_) => false,
225 }
226}
227
228fn scope_annotation_expression_contains_opaque_subquery(
229 expression: &reinhardt_db::orm::annotation::Expression,
230) -> bool {
231 use reinhardt_db::orm::annotation::Expression;
232
233 match expression {
234 Expression::Add(left, right)
235 | Expression::Subtract(left, right)
236 | Expression::Multiply(left, right)
237 | Expression::Divide(left, right) => {
238 scope_annotation_value_contains_opaque_subquery(left)
239 || scope_annotation_value_contains_opaque_subquery(right)
240 }
241 Expression::Case { whens, default } => {
242 whens
243 .iter()
244 .any(|when| scope_annotation_value_contains_opaque_subquery(&when.then))
245 || default
246 .as_deref()
247 .is_some_and(scope_annotation_value_contains_opaque_subquery)
248 }
249 Expression::Coalesce(values) => values
250 .iter()
251 .any(scope_annotation_value_contains_opaque_subquery),
252 }
253}
254
255fn scope_filter_value_contains_opaque_subquery(value: &FilterValue) -> bool {
256 match value {
257 FilterValue::Expression(expression) => {
258 scope_annotation_expression_contains_opaque_subquery(expression)
259 }
260 FilterValue::List(values) => values
261 .iter()
262 .any(scope_filter_value_contains_opaque_subquery),
263 FilterValue::Range(start, end) => {
264 scope_filter_value_contains_opaque_subquery(start)
265 || scope_filter_value_contains_opaque_subquery(end)
266 }
267 _ => false,
268 }
269}
270
271fn scope_filter_condition_contains_opaque_subquery(condition: &FilterCondition) -> bool {
272 match condition {
273 FilterCondition::Single(filter) => {
274 scope_filter_value_contains_opaque_subquery(&filter.value)
275 }
276 FilterCondition::And(conditions) | FilterCondition::Or(conditions) => conditions
277 .iter()
278 .any(scope_filter_condition_contains_opaque_subquery),
279 FilterCondition::Not(condition) => {
280 scope_filter_condition_contains_opaque_subquery(condition)
281 }
282 }
283}
284
285fn collect_scope_annotation_value(
286 value: &reinhardt_db::orm::annotation::AnnotationValue,
287 fields: &mut Vec<String>,
288) {
289 use reinhardt_db::orm::annotation::{AnnotationValue, Expression};
290
291 match value {
292 AnnotationValue::Field(field) => fields.push(field.field.clone()),
293 AnnotationValue::Aggregate(aggregate) => {
294 if let Some(field) = &aggregate.field {
295 fields.push(field.clone());
296 }
297 }
298 AnnotationValue::Expression(expression) => match expression {
299 Expression::Add(left, right)
300 | Expression::Subtract(left, right)
301 | Expression::Multiply(left, right)
302 | Expression::Divide(left, right) => {
303 collect_scope_annotation_value(left, fields);
304 collect_scope_annotation_value(right, fields);
305 }
306 Expression::Case { whens, default } => {
307 for when in whens {
308 collect_scope_query_condition(&when.condition, fields);
309 collect_scope_annotation_value(&when.then, fields);
310 }
311 if let Some(default) = default {
312 collect_scope_annotation_value(default, fields);
313 }
314 }
315 Expression::Coalesce(values) => {
316 for value in values {
317 collect_scope_annotation_value(value, fields);
318 }
319 }
320 },
321 AnnotationValue::ArrayAgg(value) => {
322 let mut value = value.clone();
323 value.map_fields(|field| collect_scope_order_by_field(field, fields));
324 }
325 AnnotationValue::StringAgg(value) => {
326 let mut value = value.clone();
327 value.map_fields(|field| collect_scope_order_by_field(field, fields));
328 }
329 AnnotationValue::JsonbAgg(value) => {
330 let mut value = value.clone();
331 value.map_fields(|field| collect_scope_order_by_field(field, fields));
332 }
333 AnnotationValue::JsonbBuildObject(value) => {
334 let mut value = value.clone();
335 value.map_fields(|field| fields.push(field.clone()));
336 }
337 AnnotationValue::TsRank(value) => {
338 let mut value = value.clone();
339 value.map_fields(|field| fields.push(field.clone()));
340 }
341 AnnotationValue::Value(_) | AnnotationValue::Subquery(_) => {}
342 }
343}
344
345fn collect_scope_query_condition(
346 condition: &reinhardt_db::orm::expressions::Q,
347 fields: &mut Vec<String>,
348) {
349 use reinhardt_db::orm::expressions::Q;
350
351 match condition {
352 Q::Condition { field, .. } => fields.push(field.clone()),
353 Q::Combined { conditions, .. } => {
354 for condition in conditions {
355 collect_scope_query_condition(condition, fields);
356 }
357 }
358 }
359}
360
361fn collect_scope_filter_value(value: &FilterValue, fields: &mut Vec<String>) {
362 match value {
363 FilterValue::FieldRef(field) => fields.push(field.field.clone()),
364 FilterValue::OuterRef(field) => fields.push(field.field.clone()),
365 FilterValue::Expression(expression) => {
366 collect_scope_annotation_expression(expression, fields);
367 }
368 FilterValue::List(values) => {
369 for value in values {
370 collect_scope_filter_value(value, fields);
371 }
372 }
373 FilterValue::Range(start, end) => {
374 collect_scope_filter_value(start, fields);
375 collect_scope_filter_value(end, fields);
376 }
377 FilterValue::String(_)
378 | FilterValue::Timestamp(_)
379 | FilterValue::Date(_)
380 | FilterValue::Time(_)
381 | FilterValue::NaiveDateTime(_)
382 | FilterValue::Decimal(_)
383 | FilterValue::Uuid(_)
384 | FilterValue::Integer(_)
385 | FilterValue::Int(_)
386 | FilterValue::Float(_)
387 | FilterValue::Boolean(_)
388 | FilterValue::Bool(_)
389 | FilterValue::Null
390 | FilterValue::Array(_) => {}
391 }
392}
393
394fn collect_scope_annotation_expression(
395 expression: &reinhardt_db::orm::annotation::Expression,
396 fields: &mut Vec<String>,
397) {
398 use reinhardt_db::orm::annotation::Expression;
399
400 match expression {
401 Expression::Add(left, right)
402 | Expression::Subtract(left, right)
403 | Expression::Multiply(left, right)
404 | Expression::Divide(left, right) => {
405 collect_scope_annotation_value(left, fields);
406 collect_scope_annotation_value(right, fields);
407 }
408 Expression::Case { whens, default } => {
409 for when in whens {
410 collect_scope_query_condition(&when.condition, fields);
411 collect_scope_annotation_value(&when.then, fields);
412 }
413 if let Some(default) = default {
414 collect_scope_annotation_value(default, fields);
415 }
416 }
417 Expression::Coalesce(values) => {
418 for value in values {
419 collect_scope_annotation_value(value, fields);
420 }
421 }
422 }
423}
424
425fn collect_scope_filter_condition(condition: &FilterCondition, fields: &mut Vec<String>) {
426 match condition {
427 FilterCondition::Single(filter) => {
428 fields.push(
429 filter
430 .source_field_name()
431 .unwrap_or(&filter.field)
432 .to_owned(),
433 );
434 collect_scope_filter_value(&filter.value, fields);
435 }
436 FilterCondition::And(conditions) | FilterCondition::Or(conditions) => {
437 for condition in conditions {
438 collect_scope_filter_condition(condition, fields);
439 }
440 }
441 FilterCondition::Not(condition) => collect_scope_filter_condition(condition, fields),
442 }
443}
444
445fn serialized_scope_field<'a>(
446 value: &'a serde_json::Value,
447 field: &reinhardt_db::orm::inspection::FieldInfo,
448) -> Option<&'a serde_json::Value> {
449 value
450 .get(&field.name)
451 .or_else(|| value.get(field.db_column_name()))
452}
453
454fn map_scope_order_by_field<T: Model>(field_name: &mut String) {
455 let descending = field_name.starts_with('-');
456 let order_field = field_name
457 .strip_prefix('-')
458 .unwrap_or(field_name)
459 .to_owned();
460 let Some(separator) = order_field.find(|character: char| character.is_whitespace()) else {
461 map_scope_order_by_name::<T>(
462 field_name,
463 if descending { "-" } else { "" },
464 &order_field,
465 "",
466 );
467 return;
468 };
469 let (logical_name, suffix) = order_field.split_at(separator);
470 map_scope_order_by_name::<T>(
471 field_name,
472 if descending { "-" } else { "" },
473 logical_name,
474 suffix,
475 );
476}
477
478fn map_scope_order_by_name<T: Model>(
479 field_name: &mut String,
480 prefix: &str,
481 qualified_name: &str,
482 suffix: &str,
483) {
484 let (qualifier, logical_name) = qualified_name
485 .rsplit_once('.')
486 .map_or(("", qualified_name), |(qualifier, name)| (qualifier, name));
487 let Some(field) = T::field_metadata()
488 .into_iter()
489 .find(|field| field.name == logical_name)
490 else {
491 return;
492 };
493 let physical_name = field.db_column_name();
494 let mapped_name = if qualifier.is_empty() {
495 physical_name.to_owned()
496 } else {
497 format!("{qualifier}.{physical_name}")
498 };
499 *field_name = format!("{prefix}{mapped_name}{suffix}");
500}
501
502fn collect_scope_order_by_field(field_name: &str, fields: &mut Vec<String>) {
503 let field_name = field_name.strip_prefix('-').unwrap_or(field_name);
504 let field_name = field_name
505 .split_once(|character: char| character.is_whitespace())
506 .map_or(field_name, |(field, _)| field);
507 fields.push(field_name.to_owned());
508}
509
510fn parse_length_prefixed_composite_parts<'a>(
511 inner: &'a str,
512 fields: &[String],
513) -> Option<Vec<&'a str>> {
514 if fields.is_empty() {
515 return None;
516 }
517
518 let mut cursor = inner;
519 let mut parts = Vec::with_capacity(fields.len());
520 for (index, field_name) in fields.iter().enumerate() {
521 let value_start = cursor.strip_prefix(&format!("{field_name}="))?;
522 let length_separator = value_start.find(':')?;
523 let length = value_start[..length_separator].parse::<usize>().ok()?;
524 let content_start = length_separator + 1;
525 let content_end = content_start.checked_add(length)?;
526 let value = value_start.get(content_start..content_end)?;
527 let remainder = value_start.get(content_end..)?;
528
529 if index + 1 == fields.len() {
530 if !remainder.is_empty() {
531 return None;
532 }
533 } else {
534 cursor = remainder.strip_prefix(", ")?;
535 }
536 parts.push(value);
537 }
538
539 Some(parts)
540}
541
542fn parse_legacy_composite_parts<'a, F>(
543 cursor: &'a str,
544 fields: &[String],
545 index: usize,
546 is_valid_part: &F,
547) -> Option<Vec<&'a str>>
548where
549 F: Fn(usize, &str) -> bool,
550{
551 let field_name = fields.get(index)?;
552 let value_start = cursor.strip_prefix(&format!("{field_name}="))?;
553 if index + 1 == fields.len() {
554 return is_valid_part(index, value_start).then(|| vec![value_start]);
555 }
556
557 let delimiter = format!(", {}=", fields[index + 1]);
558 for (position, _) in value_start.match_indices(&delimiter) {
559 let part = &value_start[..position];
560 if !is_valid_part(index, part) {
561 continue;
562 }
563 let next_cursor = &value_start[position + 2..];
564 if let Some(mut tail) =
565 parse_legacy_composite_parts(next_cursor, fields, index + 1, is_valid_part)
566 {
567 tail.insert(0, part);
568 return Some(tail);
569 }
570 }
571
572 None
573}
574
575fn primary_key_filter_for_model<T: Model>(
576 pk: &serde_json::Value,
577) -> std::result::Result<FilterCondition, ViewError> {
578 let pk_string = pk
579 .as_str()
580 .map(str::to_owned)
581 .unwrap_or_else(|| pk.to_string());
582 let pk_string = urlencoding::decode(&pk_string)
583 .map_err(|_| ViewError::NotFound(format!("Object with pk={} not found", pk_string)))?
584 .into_owned();
585 let Some(composite) = T::composite_primary_key() else {
586 let value = T::primary_key_filter_value_from_str(&pk_string)
587 .map_err(|_| ViewError::NotFound(format!("Object with pk={} not found", pk_string)))?;
588 let column = T::field_metadata()
589 .into_iter()
590 .find(|field| field.name == T::primary_key_field())
591 .map(|field| field.db_column_name().to_owned())
592 .unwrap_or_else(|| T::primary_key_field().to_owned());
593 return Ok(Filter::new(column, FilterOperator::Eq, value).into());
594 };
595
596 let inner = pk_string
597 .strip_prefix('(')
598 .and_then(|value| value.strip_suffix(')'))
599 .ok_or_else(|| ViewError::NotFound(format!("Object with pk={} not found", pk_string)))?;
600 let fields = composite.fields();
601 let metadata = T::field_metadata();
602 let is_valid_part = |index: usize, part: &str| {
603 let field_name = &fields[index];
604 match metadata.iter().find(|field| field.name == *field_name) {
605 Some(field) => filter_value_from_field_type(&field.field_type, part).is_ok(),
606 None => true,
607 }
608 };
609 let parts = parse_length_prefixed_composite_parts(inner, fields)
610 .or_else(|| parse_legacy_composite_parts(inner, fields, 0, &is_valid_part));
611 let parts = parts
612 .ok_or_else(|| ViewError::NotFound(format!("Object with pk={} not found", pk_string)))?;
613 let filters = fields
614 .iter()
615 .zip(parts)
616 .map(|(field_name, part)| {
617 let field = metadata.iter().find(|field| field.name == *field_name);
618 let filter_value = field
619 .map(|field| filter_value_from_field_type(&field.field_type, part))
620 .transpose()
621 .map_err(|_| {
622 ViewError::NotFound(format!("Object with pk={} not found", pk_string))
623 })?
624 .unwrap_or_else(|| FilterValue::String(part.to_owned()));
625 let column = field
626 .map(|field| field.db_column_name().to_owned())
627 .unwrap_or_else(|| field_name.clone());
628 Ok(Filter::new(column, FilterOperator::Eq, filter_value))
629 })
630 .collect::<std::result::Result<Vec<_>, _>>()?;
631
632 Ok(FilterCondition::and(
633 filters.into_iter().map(FilterCondition::from).collect(),
634 ))
635}
636
637fn lookup_value(value: &serde_json::Value) -> std::result::Result<String, ViewError> {
638 let value = value
639 .as_str()
640 .map(str::to_owned)
641 .unwrap_or_else(|| value.to_string());
642 urlencoding::decode(&value)
643 .map(|value| value.into_owned())
644 .map_err(|_| ViewError::NotFound(format!("Object with lookup={value} not found")))
645}
646
647fn lookup_filter_for_model<T: Model>(
648 lookup_field: Option<&str>,
649 value: &serde_json::Value,
650) -> std::result::Result<FilterCondition, ViewError> {
651 let Some(lookup_field) = lookup_field else {
652 return primary_key_filter_for_model::<T>(value);
653 };
654 if lookup_field == T::primary_key_field() {
655 return primary_key_filter_for_model::<T>(value);
656 }
657
658 let value = lookup_value(value)?;
659 let field = T::field_metadata()
660 .into_iter()
661 .find(|field| field.name == lookup_field);
662 let filter_value = field
663 .as_ref()
664 .map(|field| filter_value_from_field_type(&field.field_type, &value))
665 .transpose()
666 .map_err(|_| ViewError::NotFound(format!("Object with lookup={value} not found")))?
667 .unwrap_or(FilterValue::String(value));
668 let column = field
669 .as_ref()
670 .map(|field| field.db_column_name())
671 .unwrap_or(lookup_field);
672 Ok(Filter::new(column, FilterOperator::Eq, filter_value).into())
673}
674
675fn assigned_primary_key_filter<T: Model>(item: &T) -> Option<FilterCondition> {
676 let metadata = T::field_metadata();
677 if let Some(composite) = T::composite_primary_key() {
678 let values = item.get_composite_pk_values();
679 let filters = composite
680 .fields()
681 .iter()
682 .map(|field_name| {
683 let value = match values.get(field_name)? {
684 reinhardt_db::orm::composite_pk::PkValue::String(value) => {
685 FilterValue::String(value.clone())
686 }
687 reinhardt_db::orm::composite_pk::PkValue::Int(value) => {
688 FilterValue::Integer(*value)
689 }
690 reinhardt_db::orm::composite_pk::PkValue::Uint(value) => {
691 FilterValue::Integer(i64::try_from(*value).ok()?)
692 }
693 reinhardt_db::orm::composite_pk::PkValue::Bool(value) => {
694 FilterValue::Boolean(*value)
695 }
696 };
697 let column = metadata
698 .iter()
699 .find(|field| field.name == *field_name)
700 .map(|field| field.db_column_name().to_owned())
701 .unwrap_or_else(|| field_name.clone());
702 Some(Filter::new(column, FilterOperator::Eq, value).into())
703 })
704 .collect::<Option<Vec<FilterCondition>>>()?;
705 return Some(FilterCondition::and(filters));
706 }
707
708 let column = metadata
709 .iter()
710 .find(|field| field.name == T::primary_key_field())
711 .map(|field| field.db_column_name().to_owned())
712 .unwrap_or_else(|| T::primary_key_field().to_owned());
713 let serialized = serde_json::to_value(item).ok()?;
714 let primary_key_value = serialized
715 .get(T::primary_key_field())
716 .or_else(|| serialized.get(&column))?;
717 let filter = primary_key_filter_for_model::<T>(primary_key_value).ok()?;
718 let FilterCondition::Single(mut filter) = filter else {
719 return None;
720 };
721 filter.field = column;
722 Some(filter.into())
723}
724
725pub struct ModelViewSetHandler<T>
765where
766 T: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
767{
768 queryset: Option<Vec<T>>,
769 queryset_fn: Option<Arc<QuerysetFn>>,
770 serializer_class: Option<Arc<dyn Serializer<Input = T, Output = String> + Send + Sync>>,
771 permission_classes: Vec<Arc<dyn Permission>>,
772 filter_backends: Vec<Arc<dyn FilterBackend>>,
773 pagination_class: Option<reinhardt_core::pagination::PaginatorImpl>,
774 pool: Option<Arc<sqlx::AnyPool>>,
775 lookup_field: Option<String>,
776 db_backend: DbBackend,
778 _phantom: PhantomData<T>,
779}
780
781impl<T> ModelViewSetHandler<T>
782where
783 T: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
784{
785 pub fn new() -> Self {
819 Self {
820 queryset: None,
821 queryset_fn: None,
822 serializer_class: None,
823 permission_classes: Vec::new(),
824 filter_backends: Vec::new(),
825 pagination_class: None,
826 pool: None,
827 lookup_field: None,
828 db_backend: DbBackend::Postgres, _phantom: PhantomData,
830 }
831 }
832
833 pub fn with_queryset(mut self, queryset: Vec<T>) -> Self {
872 self.queryset = Some(queryset);
873 self
874 }
875
876 pub(crate) fn with_lookup_field(mut self, lookup_field: String) -> Self {
877 self.lookup_field = Some(lookup_field);
878 self
879 }
880
881 pub fn with_queryset_fn<F>(mut self, queryset_fn: F) -> Self
892 where
893 F: Fn(&Request) -> std::result::Result<FilterCondition, ViewError> + Send + Sync + 'static,
894 {
895 self.queryset_fn = Some(Arc::new(queryset_fn));
896 self
897 }
898
899 pub fn with_serializer(
937 mut self,
938 serializer: Arc<dyn Serializer<Input = T, Output = String> + Send + Sync>,
939 ) -> Self {
940 self.serializer_class = Some(serializer);
941 self
942 }
943
944 pub fn with_pool(mut self, pool: Arc<sqlx::AnyPool>) -> Self {
985 self.pool = Some(pool);
986 self
987 }
988
989 pub fn with_db_backend(mut self, db_backend: DbBackend) -> Self {
1024 self.db_backend = db_backend;
1025 self
1026 }
1027
1028 pub fn add_permission(mut self, permission: Arc<dyn Permission>) -> Self {
1065 self.permission_classes.push(permission);
1066 self
1067 }
1068
1069 pub fn add_filter_backend(mut self, backend: Arc<dyn FilterBackend>) -> Self {
1071 self.filter_backends.push(backend);
1072 self
1073 }
1074
1075 pub fn with_pagination(
1077 mut self,
1078 pagination: reinhardt_core::pagination::PaginatorImpl,
1079 ) -> Self {
1080 self.pagination_class = Some(pagination);
1081 self
1082 }
1083
1084 fn get_queryset(&self) -> &[T] {
1086 self.queryset.as_deref().unwrap_or(&[])
1087 }
1088
1089 fn scoped_queryset(&self, request: &Request) -> std::result::Result<QuerySet<T>, ViewError> {
1090 let mut queryset = T::objects().all().for_model_session();
1091 queryset.map_filter_columns(map_scope_filter_column::<T>);
1092 queryset.map_order_by_fields(map_scope_order_by_field::<T>);
1093 queryset.map_subquery_fields(map_scope_subquery_field::<T>);
1094 match &self.queryset_fn {
1095 Some(queryset_fn) => {
1096 let mut condition = queryset_fn(request)?;
1097 map_scope_filter_columns::<T>(&mut condition);
1098 Ok(queryset.filter(condition))
1099 }
1100 None => Ok(queryset),
1101 }
1102 }
1103
1104 fn ensure_scope_values_unchanged(
1105 &self,
1106 request: &Request,
1107 before: &serde_json::Value,
1108 after: &serde_json::Value,
1109 ) -> std::result::Result<(), ViewError> {
1110 let mut field_names = Vec::new();
1111 self.ensure_mutation_scope_supported()?;
1112 let manager_queryset = T::objects().all();
1113 let mut has_opaque_subquery = manager_queryset
1114 .filters()
1115 .iter()
1116 .any(|filter| scope_filter_value_contains_opaque_subquery(&filter.value));
1117 has_opaque_subquery |= manager_queryset
1118 .filter_conditions()
1119 .iter()
1120 .any(scope_filter_condition_contains_opaque_subquery);
1121 for filter in manager_queryset.filters() {
1122 field_names.push(
1123 filter
1124 .source_field_name()
1125 .unwrap_or(&filter.field)
1126 .to_owned(),
1127 );
1128 collect_scope_filter_value(&filter.value, &mut field_names);
1129 }
1130 for condition in manager_queryset.filter_conditions() {
1131 collect_scope_filter_condition(condition, &mut field_names);
1132 }
1133 field_names.extend(manager_queryset.subquery_fields().map(|field| {
1134 field
1135 .rsplit_once('.')
1136 .map_or_else(|| field.to_owned(), |(_, name)| name.to_owned())
1137 }));
1138 if let Some(queryset_fn) = &self.queryset_fn {
1139 let condition = queryset_fn(request)?;
1140 has_opaque_subquery |= scope_filter_condition_contains_opaque_subquery(&condition);
1141 collect_scope_filter_condition(&condition, &mut field_names);
1142 }
1143 if has_opaque_subquery {
1144 return Err(ViewError::Permission(
1145 "opaque scalar subquery scopes cannot be mutated".to_owned(),
1146 ));
1147 }
1148 if field_names.is_empty() {
1149 return Ok(());
1150 }
1151 field_names.sort_unstable();
1152 field_names.dedup();
1153
1154 for field_name in field_names {
1155 let field_name = field_name
1156 .rsplit_once('.')
1157 .map_or(field_name.as_str(), |(_, name)| name);
1158 let Some(field) = T::field_metadata()
1159 .into_iter()
1160 .find(|field| field.name == field_name || field.db_column_name() == field_name)
1161 else {
1162 return Err(ViewError::Permission(format!(
1163 "request scope field `{field_name}` is not a model field"
1164 )));
1165 };
1166 if serialized_scope_field(before, &field) != serialized_scope_field(after, &field) {
1167 return Err(ViewError::Permission(format!(
1168 "scope field `{}` cannot be changed",
1169 field.name
1170 )));
1171 }
1172 }
1173
1174 Ok(())
1175 }
1176
1177 fn ensure_mutation_scope_supported(&self) -> std::result::Result<(), ViewError> {
1178 if T::objects().all().has_joins() {
1179 return Err(ViewError::Permission(
1180 "join-backed scopes cannot be mutated".to_owned(),
1181 ));
1182 }
1183 Ok(())
1184 }
1185
1186 fn ensure_scope_fields_unchanged(
1187 &self,
1188 request: &Request,
1189 before: &T,
1190 after: &T,
1191 ) -> std::result::Result<(), ViewError> {
1192 let before = serde_json::to_value(before).map_err(|error| {
1193 ViewError::Serialization(format!("failed to serialize original scope state: {error}"))
1194 })?;
1195 let after = serde_json::to_value(after).map_err(|error| {
1196 ViewError::Serialization(format!("failed to serialize updated scope state: {error}"))
1197 })?;
1198 self.ensure_scope_values_unchanged(request, &before, &after)
1199 }
1200
1201 fn primary_key_filter(
1202 pk: &serde_json::Value,
1203 ) -> std::result::Result<FilterCondition, ViewError> {
1204 primary_key_filter_for_model::<T>(pk)
1205 }
1206
1207 fn lookup_filter(
1208 &self,
1209 value: &serde_json::Value,
1210 ) -> std::result::Result<FilterCondition, ViewError> {
1211 lookup_filter_for_model::<T>(self.lookup_field.as_deref(), value)
1212 }
1213
1214 fn matches_lookup(&self, item: &T, value: &serde_json::Value) -> bool {
1215 let Some(lookup_field) = self.lookup_field.as_deref() else {
1216 let value = value.to_string();
1217 return item
1218 .primary_key()
1219 .is_some_and(|primary_key| primary_key.to_string() == value.trim_matches('"'));
1220 };
1221 let Ok(value) = lookup_value(value) else {
1222 return false;
1223 };
1224 serde_json::to_value(item)
1225 .ok()
1226 .and_then(|item| item.get(lookup_field).cloned())
1227 .is_some_and(|field_value| match field_value {
1228 serde_json::Value::String(field_value) => field_value == value,
1229 serde_json::Value::Number(field_value) => field_value.to_string() == value,
1230 serde_json::Value::Bool(field_value) => field_value.to_string() == value,
1231 _ => false,
1232 })
1233 }
1234
1235 async fn get_object(
1236 &self,
1237 request: &Request,
1238 pk: &serde_json::Value,
1239 ) -> std::result::Result<T, ViewError> {
1240 let pool = self.pool.as_ref().ok_or_else(|| {
1241 ViewError::Internal("with_queryset_fn requires a database pool".to_owned())
1242 })?;
1243 let session = reinhardt_db::prelude::Session::new(pool.clone(), self.db_backend)
1244 .await
1245 .map_err(|error| {
1246 ViewError::DatabaseError(format!("Failed to create session: {error}"))
1247 })?;
1248 let queryset = self
1249 .scoped_queryset(request)?
1250 .filter(self.lookup_filter(pk)?)
1251 .without_slicing();
1252 session
1253 .list(&queryset)
1254 .await
1255 .map_err(|error| ViewError::DatabaseError(format!("Failed to query objects: {error}")))?
1256 .into_iter()
1257 .next()
1258 .ok_or_else(|| ViewError::NotFound(format!("Object with pk={pk} not found")))
1259 }
1260
1261 fn get_serializer(&self) -> Arc<dyn Serializer<Input = T, Output = String> + Send + Sync> {
1263 self.serializer_class
1264 .clone()
1265 .unwrap_or_else(|| Arc::new(ModelSerializer::<T>::new()))
1266 }
1267
1268 async fn check_permissions(&self, request: &Request) -> std::result::Result<(), ViewError> {
1270 let auth_state = AuthState::from_extensions(&request.extensions);
1284 let is_authenticated = auth_state
1285 .as_ref()
1286 .map(|state| state.is_authenticated())
1287 .unwrap_or(false);
1288 let is_admin = auth_state
1289 .as_ref()
1290 .map(|state| state.is_admin())
1291 .unwrap_or(false);
1292 let is_active = auth_state
1293 .as_ref()
1294 .map(|state| state.is_active())
1295 .unwrap_or(false);
1296 let user_obj = None;
1297
1298 let context = PermissionContext {
1299 request,
1300 is_authenticated,
1301 is_admin,
1302 is_active,
1303 user: user_obj,
1304 };
1305
1306 for permission in &self.permission_classes {
1308 if !permission.has_permission(&context).await {
1309 return Err(ViewError::Permission(format!(
1311 "Permission denied by {}",
1312 std::any::type_name_of_val(&**permission)
1313 )));
1314 }
1315 }
1316
1317 Ok(())
1318 }
1319
1320 pub async fn list(&self, request: &Request) -> std::result::Result<Response, ViewError> {
1369 self.check_permissions(request).await?;
1370
1371 let serializer = self.get_serializer();
1372
1373 let items = if let Some(pool) = &self.pool {
1374 let session = reinhardt_db::prelude::Session::new(pool.clone(), self.db_backend)
1375 .await
1376 .map_err(|error| {
1377 ViewError::DatabaseError(format!("Failed to create session: {error}"))
1378 })?;
1379 let queryset = self.scoped_queryset(request)?;
1380 session.list(&queryset).await.map_err(|error| {
1381 ViewError::DatabaseError(format!("Failed to list objects: {error}"))
1382 })?
1383 } else if self.queryset_fn.is_some() {
1384 return Err(ViewError::Internal(
1385 "with_queryset_fn requires a database pool".to_owned(),
1386 ));
1387 } else {
1388 self.get_queryset().to_vec()
1389 };
1390
1391 let mut serialized_items = Vec::new();
1393 for item in &items {
1394 let json = serializer
1395 .serialize(item)
1396 .map_err(|e| ViewError::Serialization(e.to_string()))?;
1397 serialized_items.push(json);
1398 }
1399
1400 let response_body = format!("[{}]", serialized_items.join(","));
1402
1403 Ok(Response::ok().with_body(response_body))
1404 }
1405
1406 pub async fn retrieve(
1457 &self,
1458 request: &Request,
1459 pk: serde_json::Value,
1460 ) -> std::result::Result<Response, ViewError> {
1461 self.check_permissions(request).await?;
1462
1463 let serializer = self.get_serializer();
1464
1465 let item = if self.pool.is_some() {
1466 self.get_object(request, &pk).await?
1467 } else if self.queryset_fn.is_some() {
1468 return Err(ViewError::Internal(
1469 "with_queryset_fn requires a database pool".to_owned(),
1470 ));
1471 } else {
1472 let queryset = self.get_queryset();
1473 queryset
1474 .iter()
1475 .find(|item| self.matches_lookup(item, &pk))
1476 .cloned()
1477 .ok_or_else(|| ViewError::NotFound(format!("Object with pk={} not found", pk)))?
1478 };
1479
1480 let json = serializer
1481 .serialize(&item)
1482 .map_err(|e| ViewError::Serialization(e.to_string()))?;
1483
1484 Ok(Response::ok().with_body(json))
1485 }
1486
1487 pub async fn create(&self, request: &Request) -> std::result::Result<Response, ViewError> {
1536 self.check_permissions(request).await?;
1537
1538 let serializer = self.get_serializer();
1539
1540 let body_str = String::from_utf8(request.body().to_vec())
1542 .map_err(|e| ViewError::BadRequest(format!("Invalid UTF-8: {}", e)))?;
1543
1544 let item = serializer
1546 .deserialize(&body_str)
1547 .map_err(|e| ViewError::Serialization(e.to_string()))?;
1548
1549 if let Some(pool) = &self.pool {
1551 let mut session = reinhardt_db::prelude::Session::new(pool.clone(), self.db_backend)
1553 .await
1554 .map_err(|e| {
1555 ViewError::DatabaseError(format!("Failed to create session: {}", e))
1556 })?;
1557
1558 session.begin().await.map_err(|e| {
1560 ViewError::DatabaseError(format!("Failed to begin transaction: {}", e))
1561 })?;
1562
1563 session
1565 .add_new(item.clone())
1566 .await
1567 .map_err(|e| ViewError::DatabaseError(format!("Failed to add object: {}", e)))?;
1568
1569 session
1571 .flush()
1572 .await
1573 .map_err(|e| ViewError::DatabaseError(format!("Failed to flush: {}", e)))?;
1574
1575 let generated_id = session.get_generated_ids().first().map(|(_, id)| *id);
1577
1578 session
1580 .commit()
1581 .await
1582 .map_err(|e| ViewError::DatabaseError(format!("Failed to commit: {}", e)))?;
1583
1584 let refresh_filter = if let Some(id) = generated_id {
1588 Some(Self::primary_key_filter(&serde_json::json!(id))?)
1589 } else {
1590 assigned_primary_key_filter(&item)
1591 };
1592 if let Some(refresh_filter) = refresh_filter {
1593 let fetch_session =
1594 reinhardt_db::prelude::Session::new(pool.clone(), self.db_backend)
1595 .await
1596 .map_err(|e| {
1597 ViewError::DatabaseError(format!("Failed to create session: {}", e))
1598 })?;
1599
1600 let queryset = QuerySet::<T>::new().filter(refresh_filter).limit(1);
1601 let created_item = fetch_session
1602 .list(&queryset)
1603 .await
1604 .map_err(|error| {
1605 ViewError::DatabaseError(format!(
1606 "Failed to refresh created object: {error}"
1607 ))
1608 })?
1609 .into_iter()
1610 .next()
1611 .ok_or_else(|| {
1612 ViewError::DatabaseError("Failed to find created object".to_owned())
1613 })?;
1614
1615 let response_body = serializer
1617 .serialize(&created_item)
1618 .map_err(|e| ViewError::Serialization(e.to_string()))?;
1619
1620 return Ok(Response::created().with_body(response_body));
1621 }
1622 }
1623
1624 let response_body = serializer
1626 .serialize(&item)
1627 .map_err(|e| ViewError::Serialization(e.to_string()))?;
1628
1629 Ok(Response::created().with_body(response_body))
1630 }
1631
1632 pub async fn update(
1683 &self,
1684 request: &Request,
1685 pk: serde_json::Value,
1686 ) -> std::result::Result<Response, ViewError> {
1687 self.check_permissions(request).await?;
1688 self.ensure_mutation_scope_supported()?;
1689
1690 let serializer = self.get_serializer();
1691
1692 let existing_obj = if self.pool.is_some() {
1693 self.get_object(request, &pk).await?
1694 } else if self.queryset_fn.is_some() {
1695 return Err(ViewError::Internal(
1696 "with_queryset_fn requires a database pool".to_owned(),
1697 ));
1698 } else {
1699 self.get_queryset()
1701 .iter()
1702 .find(|item| self.matches_lookup(item, &pk))
1703 .cloned()
1704 .ok_or_else(|| ViewError::NotFound(format!("Object with lookup={pk} not found")))?
1705 };
1706
1707 let body_str = String::from_utf8(request.body().to_vec())
1709 .map_err(|e| ViewError::BadRequest(format!("Invalid UTF-8: {}", e)))?;
1710
1711 let patch_data: serde_json::Value = serde_json::from_str(&body_str)
1713 .map_err(|e| ViewError::Serialization(format!("Invalid JSON: {}", e)))?;
1714
1715 let existing_json = serializer
1717 .serialize(&existing_obj)
1718 .map_err(|e| ViewError::Serialization(e.to_string()))?;
1719 let mut existing_value: serde_json::Value = serde_json::from_str(&existing_json)
1720 .map_err(|e| ViewError::Serialization(format!("Failed to parse existing: {}", e)))?;
1721 crate::generic::patch_utils::merge_patch_object_into(&mut existing_value, &patch_data)
1723 .map_err(ViewError::BadRequest)?;
1724
1725 let merged_json = serde_json::to_string(&existing_value)
1727 .map_err(|e| ViewError::Serialization(format!("Failed to serialize merged: {}", e)))?;
1728 let mut updated_item: T = serializer
1729 .deserialize(&merged_json)
1730 .map_err(|e| ViewError::Serialization(e.to_string()))?;
1731 self.ensure_scope_fields_unchanged(request, &existing_obj, &updated_item)?;
1732 let primary_key = existing_obj
1733 .primary_key()
1734 .ok_or_else(|| ViewError::Internal("Object has no primary key".to_owned()))?;
1735 updated_item.set_primary_key(primary_key);
1736 let response_json = serializer
1737 .serialize(&updated_item)
1738 .map_err(|e| ViewError::Serialization(e.to_string()))?;
1739
1740 if let Some(pool) = &self.pool {
1742 let mut session = reinhardt_db::prelude::Session::new(pool.clone(), self.db_backend)
1744 .await
1745 .map_err(|e| {
1746 ViewError::DatabaseError(format!("Failed to create session: {}", e))
1747 })?;
1748
1749 let mut transaction = pool.begin().await.map_err(|e| {
1751 ViewError::DatabaseError(format!("Failed to begin transaction: {}", e))
1752 })?;
1753
1754 let mutation_queryset = self
1756 .scoped_queryset(request)?
1757 .filter(self.lookup_filter(&pk)?)
1758 .without_slicing()
1759 .without_distinct();
1760 if session
1761 .list_with_connection_for_update(&mutation_queryset, &mut transaction)
1762 .await
1763 .map_err(|e| ViewError::DatabaseError(format!("Failed to recheck object: {}", e)))?
1764 .into_iter()
1765 .next()
1766 .is_none()
1767 {
1768 return Err(ViewError::NotFound(format!(
1769 "Object with pk={} not found",
1770 pk
1771 )));
1772 }
1773
1774 session
1776 .add(updated_item.clone())
1777 .await
1778 .map_err(|e| ViewError::DatabaseError(format!("Failed to add object: {}", e)))?;
1779
1780 session
1782 .flush_with_connection(&mut transaction)
1783 .await
1784 .map_err(|e| ViewError::DatabaseError(format!("Failed to flush: {}", e)))?;
1785
1786 transaction
1788 .commit()
1789 .await
1790 .map_err(|e| ViewError::DatabaseError(format!("Failed to commit: {}", e)))?;
1791 }
1792
1793 Ok(Response::ok().with_body(response_json))
1795 }
1796
1797 pub async fn destroy(
1848 &self,
1849 request: &Request,
1850 pk: serde_json::Value,
1851 ) -> std::result::Result<Response, ViewError> {
1852 self.check_permissions(request).await?;
1853 self.ensure_mutation_scope_supported()?;
1854
1855 if self.pool.is_none() {
1856 if self.queryset_fn.is_some() {
1857 return Err(ViewError::Internal(
1858 "with_queryset_fn requires a database pool".to_owned(),
1859 ));
1860 }
1861 self.get_queryset()
1862 .iter()
1863 .find(|item| self.matches_lookup(item, &pk))
1864 .cloned()
1865 .ok_or_else(|| ViewError::NotFound(format!("Object with lookup={pk} not found")))?;
1866 }
1867
1868 if let Some(pool) = &self.pool {
1870 let mut session = reinhardt_db::prelude::Session::new(pool.clone(), self.db_backend)
1872 .await
1873 .map_err(|e| {
1874 ViewError::DatabaseError(format!("Failed to create session: {}", e))
1875 })?;
1876
1877 let mut transaction = pool.begin().await.map_err(|e| {
1879 ViewError::DatabaseError(format!("Failed to begin transaction: {}", e))
1880 })?;
1881
1882 let mutation_queryset = self
1884 .scoped_queryset(request)?
1885 .filter(self.lookup_filter(&pk)?)
1886 .without_slicing()
1887 .without_distinct();
1888 let item = session
1889 .list_with_connection_for_update(&mutation_queryset, &mut transaction)
1890 .await
1891 .map_err(|e| ViewError::DatabaseError(format!("Failed to recheck object: {}", e)))?
1892 .into_iter()
1893 .next()
1894 .ok_or_else(|| ViewError::NotFound(format!("Object with pk={} not found", pk)))?;
1895
1896 session.delete(item).await.map_err(|e| {
1898 ViewError::DatabaseError(format!("Failed to mark object for deletion: {}", e))
1899 })?;
1900
1901 session
1903 .flush_with_connection(&mut transaction)
1904 .await
1905 .map_err(|e| ViewError::DatabaseError(format!("Failed to flush: {}", e)))?;
1906
1907 transaction
1909 .commit()
1910 .await
1911 .map_err(|e| ViewError::DatabaseError(format!("Failed to commit: {}", e)))?;
1912 }
1913
1914 Ok(Response::no_content())
1915 }
1916}
1917
1918impl<T> Default for ModelViewSetHandler<T>
1919where
1920 T: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
1921{
1922 fn default() -> Self {
1923 Self::new()
1924 }
1925}
1926
1927#[cfg(test)]
1928mod tests {
1929 use super::*;
1930 use bytes::Bytes;
1931 use hyper::{HeaderMap, Method, Version};
1932 use reinhardt_auth::{IsActiveUser, IsAuthenticated};
1933 use reinhardt_db::orm::fields::{CharField, Field};
1934 use reinhardt_db::orm::inspection::FieldInfo;
1935 use reinhardt_db::orm::{Filter, FilterOperator, FilterValue};
1936 use reinhardt_http::Request;
1937 use reinhardt_rest::serializers::SerializerError;
1938 use rstest::rstest;
1939 use std::sync::atomic::{AtomicUsize, Ordering};
1940
1941 fn build_request(uri: &str) -> Request {
1942 Request::builder()
1943 .method(Method::GET)
1944 .uri(uri)
1945 .version(Version::HTTP_11)
1946 .headers(HeaderMap::new())
1947 .body(Bytes::new())
1948 .build()
1949 .unwrap()
1950 }
1951
1952 #[rstest]
1953 fn composite_pk_parser_preserves_delimiters_in_length_prefixed_values() {
1954 let fields = vec!["namespace".to_owned(), "id".to_owned()];
1955 let parts =
1956 parse_length_prefixed_composite_parts("namespace=9:a, id=999, id=3:123", &fields)
1957 .expect("length-prefixed composite keys should parse");
1958
1959 assert_eq!(parts, vec!["a, id=999", "123"]);
1960 }
1961
1962 #[rstest]
1963 fn legacy_composite_pk_parser_uses_typed_boundaries() {
1964 let fields = vec!["namespace".to_owned(), "id".to_owned()];
1965 let is_valid = |index: usize, value: &str| index == 0 || value.parse::<i64>().is_ok();
1966 let parts =
1967 parse_legacy_composite_parts("namespace=a, id=999, id=1", &fields, 0, &is_valid)
1968 .expect("legacy composite keys should parse");
1969
1970 assert_eq!(parts, vec!["a, id=999", "1"]);
1971 }
1972
1973 #[derive(Debug, Clone, Serialize, serde::Deserialize, PartialEq)]
1978 struct TestItem {
1979 id: Option<i64>,
1980 name: String,
1981 organization: Option<String>,
1982 }
1983
1984 #[derive(Clone, Copy)]
1985 struct AliasTestItemSerializer;
1986
1987 impl Serializer for AliasTestItemSerializer {
1988 type Input = TestItem;
1989 type Output = String;
1990
1991 fn serialize(&self, input: &Self::Input) -> Result<Self::Output, SerializerError> {
1992 serde_json::to_string(&serde_json::json!({
1993 "id": input.id,
1994 "name": input.name,
1995 "tenant": input.organization,
1996 }))
1997 .map_err(|error| SerializerError::Serde {
1998 message: error.to_string(),
1999 })
2000 }
2001
2002 fn deserialize(&self, output: &Self::Output) -> Result<Self::Input, SerializerError> {
2003 #[derive(serde::Deserialize)]
2004 struct Payload {
2005 id: Option<i64>,
2006 name: String,
2007 tenant: Option<String>,
2008 }
2009
2010 let payload: Payload =
2011 serde_json::from_str(output).map_err(|error| SerializerError::Serde {
2012 message: error.to_string(),
2013 })?;
2014 Ok(TestItem {
2015 id: payload.id,
2016 name: payload.name,
2017 organization: payload.tenant,
2018 })
2019 }
2020 }
2021
2022 #[derive(Clone)]
2023 struct TestItemFields;
2024
2025 #[derive(Clone, Copy)]
2026 struct OrganizationId(i64);
2027
2028 #[derive(Default)]
2029 struct ScopedTestItemManager;
2030
2031 impl reinhardt_db::orm::CustomManager for ScopedTestItemManager {
2032 type Model = TestItem;
2033
2034 fn new() -> Self {
2035 Self
2036 }
2037
2038 fn all(&self) -> QuerySet<Self::Model> {
2039 QuerySet::new()
2040 .filter(Filter::new(
2041 "organization",
2042 FilterOperator::Eq,
2043 FilterValue::Integer(99),
2044 ))
2045 .filter(Filter::new(
2046 "organization",
2047 FilterOperator::Eq,
2048 FilterValue::FieldRef(reinhardt_db::orm::expressions::F::new("organization")),
2049 ))
2050 .filter(Filter::new(
2051 "organization",
2052 FilterOperator::Eq,
2053 FilterValue::Expression(reinhardt_db::orm::annotation::Expression::Add(
2054 Box::new(reinhardt_db::orm::annotation::AnnotationValue::Field(
2055 reinhardt_db::orm::expressions::F::new("organization"),
2056 )),
2057 Box::new(reinhardt_db::orm::annotation::AnnotationValue::Value(
2058 reinhardt_db::orm::annotation::Value::Int(1),
2059 )),
2060 )),
2061 ))
2062 .filter(
2063 reinhardt_db::orm::expressions::FieldRef::<TestItem, String>::new(
2064 "organization",
2065 )
2066 .year()
2067 .eq(2026),
2068 )
2069 .order_by(&["-organization"])
2070 }
2071 }
2072
2073 impl reinhardt_db::orm::FieldSelector for TestItemFields {
2074 fn with_alias(self, _alias: &str) -> Self {
2075 self
2076 }
2077 }
2078
2079 impl reinhardt_db::orm::Model for TestItem {
2080 type PrimaryKey = i64;
2081 type Fields = TestItemFields;
2082 type Objects = ScopedTestItemManager;
2083
2084 fn table_name() -> &'static str {
2085 "test_items"
2086 }
2087
2088 fn primary_key(&self) -> Option<Self::PrimaryKey> {
2089 self.id
2090 }
2091
2092 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
2093 self.id = Some(value);
2094 }
2095
2096 fn new_fields() -> Self::Fields {
2097 TestItemFields
2098 }
2099
2100 fn field_metadata() -> Vec<FieldInfo> {
2101 let mut organization = CharField::new(255);
2102 organization.set_attributes_from_name("organization");
2103 organization.base.db_column = Some("organization_id".to_owned());
2104 vec![FieldInfo::from_field(&organization)]
2105 }
2106 }
2107
2108 fn build_model_handler(items: Vec<TestItem>) -> ModelViewSetHandler<TestItem> {
2110 ModelViewSetHandler::<TestItem>::new().with_queryset(items)
2111 }
2112
2113 #[test]
2114 fn scoped_queryset_fn_reads_request_extensions() {
2115 let request = build_request("/items/");
2116 request.extensions.insert(OrganizationId(7));
2117 let handler = ModelViewSetHandler::<TestItem>::new().with_queryset_fn(|request| {
2118 let organization = request
2119 .extensions
2120 .get::<OrganizationId>()
2121 .ok_or_else(|| ViewError::Permission("organization scope is missing".to_owned()))?;
2122 Ok(Filter::new(
2123 "organization_id",
2124 FilterOperator::Eq,
2125 FilterValue::Integer(organization.0),
2126 )
2127 .into())
2128 });
2129
2130 let queryset = handler.scoped_queryset(&request).unwrap();
2131
2132 assert_eq!(queryset.filters().len(), 5);
2133 assert!(
2134 queryset
2135 .filters()
2136 .iter()
2137 .take(3)
2138 .all(|filter| filter.field == "organization_id")
2139 );
2140 assert_eq!(
2141 queryset.filters()[3].field,
2142 "EXTRACT(YEAR FROM \"organization_id\")"
2143 );
2144 assert_eq!(
2145 queryset.filters()[3].source_field_name(),
2146 Some("organization")
2147 );
2148 let FilterValue::FieldRef(field) = &queryset.filters()[1].value else {
2149 panic!("custom-manager field reference should be preserved");
2150 };
2151 assert_eq!(field.field, "organization_id");
2152 let FilterValue::Expression(expression) = &queryset.filters()[2].value else {
2153 panic!("custom-manager expression should be preserved");
2154 };
2155 assert_eq!(expression.to_sql(), "(\"organization_id\" + 1)");
2156 assert_eq!(
2157 queryset.to_sql(),
2158 "SELECT * FROM \"test_items\" WHERE (\"organization_id\" = 99 AND \"organization_id\" = \"organization_id\" AND \"organization_id\" = (\"organization_id\" + 1) AND EXTRACT(YEAR FROM \"organization_id\") = 2026 AND \"organization_id\" = 7) ORDER BY \"organization_id\" DESC"
2159 );
2160 }
2161
2162 #[test]
2163 fn postgres_annotation_scope_fields_are_mapped_and_collected() {
2164 use reinhardt_db::orm::annotation::{AnnotationValue, Expression};
2165
2166 let mut value = AnnotationValue::Expression(Expression::Coalesce(vec![
2167 AnnotationValue::ArrayAgg(
2168 reinhardt_db::orm::ArrayAgg::new("organization".to_owned())
2169 .order_by(vec!["organization DESC".to_owned()]),
2170 ),
2171 AnnotationValue::StringAgg(reinhardt_db::orm::StringAgg::new(
2172 "organization".to_owned(),
2173 ", ".to_owned(),
2174 )),
2175 AnnotationValue::JsonbAgg(reinhardt_db::orm::JsonbAgg::new("organization".to_owned())),
2176 AnnotationValue::JsonbBuildObject(
2177 reinhardt_db::orm::JsonbBuildObject::new().add("tenant", "organization"),
2178 ),
2179 AnnotationValue::TsRank(reinhardt_db::orm::TsRank::new(
2180 "organization".to_owned(),
2181 "tenant".to_owned(),
2182 )),
2183 ]));
2184
2185 let mut fields = Vec::new();
2186 collect_scope_annotation_value(&value, &mut fields);
2187 assert_eq!(fields, vec!["organization"; 6]);
2188
2189 map_scope_annotation_value::<TestItem>(&mut value);
2190 assert_eq!(
2191 value.to_sql(),
2192 "COALESCE(ARRAY_AGG(organization_id ORDER BY organization_id DESC), STRING_AGG(organization_id, ', '), JSONB_AGG(organization_id), jsonb_build_object('tenant', organization_id), ts_rank(organization_id, to_tsquery('english', 'tenant')))"
2193 );
2194 }
2195
2196 #[rstest]
2197 fn qualified_scope_fields_map_the_final_component() {
2198 let mut field = "items.organization".to_owned();
2199
2200 map_scope_field::<TestItem>(&mut field);
2201
2202 assert_eq!(field, "items.organization_id");
2203 }
2204
2205 #[rstest]
2206 fn qualified_scope_ordering_maps_the_final_component() {
2207 let mut field = "items.organization DESC NULLS LAST".to_owned();
2208
2209 map_scope_order_by_field::<TestItem>(&mut field);
2210
2211 assert_eq!(field, "items.organization_id DESC NULLS LAST");
2212 }
2213
2214 #[rstest]
2215 fn opaque_scalar_subquery_scope_is_rejected_before_mutation() {
2216 use reinhardt_db::orm::annotation::{AnnotationValue, Expression};
2217
2218 let request = build_request("/items/");
2219 let handler = ModelViewSetHandler::<TestItem>::new().with_queryset_fn(|_| {
2220 Ok(Filter::new(
2221 "organization",
2222 FilterOperator::Eq,
2223 FilterValue::Expression(Expression::Coalesce(vec![AnnotationValue::Subquery(
2224 "(SELECT organization_id FROM memberships WHERE memberships.item_id = items.id)"
2225 .to_owned(),
2226 )])),
2227 )
2228 .into())
2229 });
2230
2231 let error = handler
2232 .ensure_scope_values_unchanged(
2233 &request,
2234 &serde_json::json!({"organization": "tenant-a"}),
2235 &serde_json::json!({"organization": "tenant-a"}),
2236 )
2237 .unwrap_err();
2238
2239 assert!(matches!(
2240 error,
2241 ViewError::Permission(message)
2242 if message == "opaque scalar subquery scopes cannot be mutated"
2243 ));
2244 }
2245
2246 #[rstest]
2247 fn scope_field_changes_are_rejected_before_update() {
2248 let request = build_request("/items/");
2249 let handler = ModelViewSetHandler::<TestItem>::new().with_queryset_fn(|_| {
2250 Ok(Filter::new("organization", FilterOperator::Eq, FilterValue::Integer(7)).into())
2251 });
2252
2253 let error = handler
2254 .ensure_scope_values_unchanged(
2255 &request,
2256 &serde_json::json!({"organization": "tenant-a"}),
2257 &serde_json::json!({"organization": "tenant-b"}),
2258 )
2259 .unwrap_err();
2260
2261 assert!(matches!(
2262 error,
2263 ViewError::Permission(message) if message == "scope field `organization` cannot be changed"
2264 ));
2265 }
2266
2267 #[test]
2268 fn scope_field_changes_are_rejected_after_custom_serializer_alias_round_trip() {
2269 let request = build_request("/items/");
2270 let handler = ModelViewSetHandler::<TestItem>::new().with_queryset_fn(|_| {
2271 Ok(Filter::new(
2272 "organization",
2273 FilterOperator::Eq,
2274 FilterValue::String("tenant-a".to_owned()),
2275 )
2276 .into())
2277 });
2278 let serializer = AliasTestItemSerializer;
2279 let existing = TestItem {
2280 id: Some(1),
2281 name: "item".to_owned(),
2282 organization: Some("tenant-a".to_owned()),
2283 };
2284 let mut serialized: serde_json::Value =
2285 serde_json::from_str(&serializer.serialize(&existing).unwrap()).unwrap();
2286 serialized["tenant"] = serde_json::json!("tenant-b");
2287 let updated = serializer
2288 .deserialize(&serde_json::to_string(&serialized).unwrap())
2289 .unwrap();
2290
2291 let error = handler
2292 .ensure_scope_fields_unchanged(&request, &existing, &updated)
2293 .unwrap_err();
2294
2295 assert!(matches!(
2296 error,
2297 ViewError::Permission(message) if message == "scope field `organization` cannot be changed"
2298 ));
2299 }
2300
2301 #[rstest]
2302 fn custom_manager_scope_fields_are_rejected_before_update() {
2303 let request = build_request("/items/");
2304 let handler = ModelViewSetHandler::<TestItem>::new();
2305
2306 let error = handler
2307 .ensure_scope_values_unchanged(
2308 &request,
2309 &serde_json::json!({"organization": "99"}),
2310 &serde_json::json!({"organization": "100"}),
2311 )
2312 .unwrap_err();
2313
2314 assert!(matches!(
2315 error,
2316 ViewError::Permission(message) if message == "scope field `organization` cannot be changed"
2317 ));
2318 }
2319
2320 #[test]
2321 fn scoped_queryset_propagates_hook_errors() {
2322 let handler = ModelViewSetHandler::<TestItem>::new().with_queryset_fn(|_| {
2323 Err(ViewError::Permission(
2324 "organization scope is missing".to_owned(),
2325 ))
2326 });
2327
2328 let error = match handler.scoped_queryset(&build_request("/items/")) {
2329 Ok(_) => panic!("queryset hook error must propagate"),
2330 Err(error) => error,
2331 };
2332
2333 assert!(
2334 matches!(error, ViewError::Permission(message) if message == "organization scope is missing")
2335 );
2336 }
2337
2338 #[test]
2339 fn get_object_primary_key_filter_preserves_integer_type() {
2340 let filter =
2341 ModelViewSetHandler::<TestItem>::primary_key_filter(&serde_json::json!(42)).unwrap();
2342 let FilterCondition::Single(filter) = filter else {
2343 panic!("a scalar primary key should produce one filter");
2344 };
2345
2346 assert_eq!(filter.field, "id");
2347 assert!(matches!(filter.value, FilterValue::Integer(42)));
2348 }
2349
2350 #[test]
2351 fn custom_lookup_filter_maps_declared_database_column() {
2352 let handler =
2353 ModelViewSetHandler::<TestItem>::new().with_lookup_field("organization".to_owned());
2354
2355 let FilterCondition::Single(filter) = handler
2356 .lookup_filter(&serde_json::json!("tenant-a"))
2357 .unwrap()
2358 else {
2359 panic!("a custom lookup should produce one filter");
2360 };
2361
2362 assert_eq!(filter.field, "organization_id");
2363 assert!(matches!(
2364 filter.value,
2365 FilterValue::String(value) if value == "tenant-a"
2366 ));
2367 }
2368
2369 #[test]
2370 fn assigned_primary_key_filter_preserves_declared_key_binding() {
2371 let item = TestItem {
2372 id: Some(42),
2373 name: "item".to_owned(),
2374 organization: Some("tenant-a".to_owned()),
2375 };
2376 let FilterCondition::Single(filter) = assigned_primary_key_filter(&item).unwrap() else {
2377 panic!("single primary key should produce one filter");
2378 };
2379
2380 assert_eq!(filter.field, "id");
2381 assert!(matches!(filter.value, FilterValue::Integer(42)));
2382 }
2383
2384 #[tokio::test]
2385 async fn queryset_fn_without_pool_fails_closed() {
2386 let request = build_request("/items/");
2387 let handler = ModelViewSetHandler::<TestItem>::new()
2388 .with_queryset(vec![TestItem {
2389 id: Some(1),
2390 name: "visible".to_owned(),
2391 organization: None,
2392 }])
2393 .with_queryset_fn(|_| {
2394 Ok(Filter::new("organization_id", FilterOperator::Eq, 1_i64.into()).into())
2395 });
2396
2397 let error = handler.list(&request).await.unwrap_err();
2398
2399 assert!(matches!(error, ViewError::Internal(_)));
2400 }
2401
2402 #[tokio::test]
2403 async fn permission_denial_does_not_call_queryset_fn() {
2404 let hook_calls = Arc::new(AtomicUsize::new(0));
2405 let hook_calls_for_queryset = Arc::clone(&hook_calls);
2406 let handler = ModelViewSetHandler::<TestItem>::new()
2407 .add_permission(Arc::new(IsAuthenticated))
2408 .with_queryset_fn(move |_| {
2409 hook_calls_for_queryset.fetch_add(1, Ordering::SeqCst);
2410 Ok(Filter::new("organization_id", FilterOperator::Eq, 1_i64.into()).into())
2411 });
2412
2413 let error = handler.list(&build_request("/items/")).await.unwrap_err();
2414
2415 assert!(matches!(error, ViewError::Permission(_)));
2416 assert_eq!(hook_calls.load(Ordering::SeqCst), 0);
2417 }
2418
2419 #[tokio::test]
2420 async fn create_does_not_call_queryset_fn() {
2421 let hook_calls = Arc::new(AtomicUsize::new(0));
2422 let hook_calls_for_queryset = Arc::clone(&hook_calls);
2423 let handler = ModelViewSetHandler::<TestItem>::new().with_queryset_fn(move |_| {
2424 hook_calls_for_queryset.fetch_add(1, Ordering::SeqCst);
2425 Ok(Filter::new("organization_id", FilterOperator::Eq, 1_i64.into()).into())
2426 });
2427 let request = Request::builder()
2428 .method(Method::POST)
2429 .uri("/items/")
2430 .body(Bytes::from_static(br#"{"id":null,"name":"created"}"#))
2431 .build()
2432 .unwrap();
2433
2434 let response = handler.create(&request).await.unwrap();
2435
2436 assert_eq!(response.status, hyper::StatusCode::CREATED);
2437 assert_eq!(hook_calls.load(Ordering::SeqCst), 0);
2438 }
2439
2440 #[rstest]
2441 #[tokio::test]
2442 async fn test_list_denies_bare_user_id_extensions_for_active_permissions() {
2443 let handler = build_model_handler(vec![TestItem {
2445 id: Some(1),
2446 name: "first".to_string(),
2447 organization: None,
2448 }])
2449 .add_permission(Arc::new(IsAuthenticated))
2450 .add_permission(Arc::new(IsActiveUser));
2451 let request = build_request("/items/");
2452 request.extensions.insert("legacy-user".to_string());
2453
2454 let result = handler.list(&request).await;
2456
2457 let error = result.expect_err("bare user ID extensions must not grant authorization");
2459 assert!(matches!(error, ViewError::Permission(_)));
2460 }
2461
2462 #[rstest]
2463 #[tokio::test]
2464 async fn test_retrieve_strips_quotes_from_numeric_pk() {
2465 let items = vec![
2467 TestItem {
2468 id: Some(1),
2469 name: "first".to_string(),
2470 organization: None,
2471 },
2472 TestItem {
2473 id: Some(2),
2474 name: "second".to_string(),
2475 organization: None,
2476 },
2477 ];
2478 let handler = build_model_handler(items);
2479 let request = build_request("/items/1/");
2480
2481 let pk = serde_json::json!("1");
2483 let result = handler.retrieve(&request, pk).await;
2484
2485 assert!(result.is_ok(), "retrieve should succeed with quoted pk");
2487 let response = result.unwrap();
2488 assert_eq!(response.status, hyper::StatusCode::OK);
2489 let body: TestItem =
2490 serde_json::from_slice(&response.body).expect("response should be valid JSON");
2491 assert_eq!(body.name, "first");
2492 assert_eq!(body.id, Some(1));
2493 }
2494
2495 #[rstest]
2496 #[tokio::test]
2497 async fn test_retrieve_works_with_unquoted_numeric_pk() {
2498 let items = vec![TestItem {
2500 id: Some(42),
2501 name: "answer".to_string(),
2502 organization: None,
2503 }];
2504 let handler = build_model_handler(items);
2505 let request = build_request("/items/42/");
2506
2507 let pk = serde_json::json!(42);
2509 let result = handler.retrieve(&request, pk).await;
2510
2511 assert!(result.is_ok(), "retrieve should succeed with numeric pk");
2513 let response = result.unwrap();
2514 assert_eq!(response.status, hyper::StatusCode::OK);
2515 let body: TestItem =
2516 serde_json::from_slice(&response.body).expect("response should be valid JSON");
2517 assert_eq!(body.name, "answer");
2518 assert_eq!(body.id, Some(42));
2519 }
2520
2521 #[rstest]
2522 #[tokio::test]
2523 async fn test_retrieve_returns_not_found_for_nonexistent_pk() {
2524 let items = vec![TestItem {
2526 id: Some(1),
2527 name: "only".to_string(),
2528 organization: None,
2529 }];
2530 let handler = build_model_handler(items);
2531 let request = build_request("/items/999/");
2532
2533 let pk = serde_json::json!(999);
2535 let result = handler.retrieve(&request, pk).await;
2536
2537 assert!(result.is_err(), "retrieve should fail for nonexistent pk");
2539 let err = result.unwrap_err();
2540 assert!(
2541 matches!(err, ViewError::NotFound(_)),
2542 "error should be NotFound, got: {:?}",
2543 err
2544 );
2545 }
2546}