1use crate::viewsets::actions::Action;
2use crate::viewsets::filtering_support::{FilterConfig, FilterableViewSet, OrderingConfig};
3use crate::viewsets::handler::{ModelViewSetHandler, ViewError};
4use crate::viewsets::metadata::{ActionMetadata, get_actions_for_viewset};
5use crate::viewsets::middleware::{CompositeMiddleware, ViewSetMiddleware};
6use crate::viewsets::pagination_support::{PaginatedViewSet, PaginationConfig};
7use async_trait::async_trait;
8use hyper::Method;
9use reinhardt_auth::Permission;
10use reinhardt_db::orm::{FilterCondition, Model, query_types::DbBackend};
11use reinhardt_http::{Request, Response, Result};
12use reinhardt_rest::filters::FilterBackend;
13use reinhardt_rest::serializers::Serializer;
14use serde::Serialize;
15use serde::de::DeserializeOwned;
16use std::collections::HashMap;
17use std::marker::PhantomData;
18use std::sync::Arc;
19
20fn extract_pk(request: &Request, lookup_field: &str) -> Result<serde_json::Value> {
23 request
24 .path_params
25 .get(lookup_field)
26 .map(|v| serde_json::Value::String(v.clone()))
27 .ok_or_else(|| {
28 reinhardt_core::exception::Error::Http(format!(
29 "Missing path parameter: {}",
30 lookup_field
31 ))
32 })
33}
34
35fn method_not_allowed(method: &Method) -> reinhardt_core::exception::Error {
37 reinhardt_core::exception::Error::MethodNotAllowed(format!("Method {} not allowed", method))
38}
39
40#[async_trait]
43pub trait ViewSet: Send + Sync {
44 fn get_basename(&self) -> &str;
46
47 fn get_lookup_field(&self) -> &str {
50 "id"
51 }
52
53 async fn dispatch(&self, request: Request, action: Action) -> Result<Response>;
55
56 fn get_extra_actions(&self) -> Vec<ActionMetadata> {
61 let viewset_type = std::any::type_name::<Self>();
62
63 let mut actions = get_actions_for_viewset(viewset_type);
65
66 let manual_actions = crate::viewsets::registry::get_registered_actions(viewset_type);
68 actions.extend(manual_actions);
69
70 actions
71 }
72
73 fn get_extra_action_url_map(&self) -> HashMap<String, String> {
76 HashMap::new()
77 }
78
79 fn get_current_base_url(&self) -> Option<String> {
81 None
82 }
83
84 fn reverse_action(&self, _action_name: &str, _args: &[&str]) -> Result<String> {
86 Err(reinhardt_core::exception::Error::NotFound(
87 "ViewSet not bound to router".to_string(),
88 ))
89 }
90
91 fn get_middleware(&self) -> Option<Arc<dyn ViewSetMiddleware>> {
98 let permissions = self.get_required_permissions();
99 if !self.requires_login() && permissions.is_empty() {
100 return None;
101 }
102
103 let mut middleware = CompositeMiddleware::new();
104 if self.requires_login() {
105 middleware = middleware.with_authentication(true);
106 }
107 if !permissions.is_empty() {
108 middleware = middleware.with_permissions(permissions);
109 }
110 Some(Arc::new(middleware))
111 }
112
113 fn requires_login(&self) -> bool {
115 false
116 }
117
118 fn get_required_permissions(&self) -> Vec<String> {
120 Vec::new()
121 }
122}
123
124#[allow(dead_code)]
149#[derive(Clone)]
150pub struct GenericViewSet<T> {
151 basename: String,
152 handler: T,
153}
154
155impl<T: 'static> GenericViewSet<T> {
156 pub fn new(basename: impl Into<String>, handler: T) -> Self {
167 Self {
168 basename: basename.into(),
169 handler,
170 }
171 }
172
173 pub fn as_view(self) -> crate::viewsets::builder::ViewSetBuilder<Self>
187 where
188 T: Send + Sync,
189 {
190 crate::viewsets::builder::ViewSetBuilder::new(self)
191 }
192}
193
194#[async_trait]
195impl<T: Send + Sync> ViewSet for GenericViewSet<T> {
196 fn get_basename(&self) -> &str {
197 &self.basename
198 }
199
200 async fn dispatch(&self, _request: Request, action: Action) -> Result<Response> {
201 Err(reinhardt_core::exception::Error::NotFound(format!(
207 "GenericViewSet has no built-in CRUD logic for action {:?}. \
208 For real CRUD, use ModelViewSet<M, S> or ReadOnlyModelViewSet<M, S>. \
209 To implement custom logic, define your own struct and \
210 `impl ViewSet for YourType` with a hand-written dispatch().",
211 action.action_type
212 )))
213 }
214}
215
216pub struct ModelViewSet<M, S>
225where
226 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
227 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
228{
229 basename: String,
230 lookup_field: String,
231 pagination_config: Option<PaginationConfig>,
232 filter_config: Option<FilterConfig>,
233 ordering_config: Option<OrderingConfig>,
234 handler: ModelViewSetHandler<M>,
235 _serializer: PhantomData<S>,
236}
237
238impl<M, S> FilterableViewSet for ModelViewSet<M, S>
240where
241 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
242 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
243{
244 fn get_filter_config(&self) -> Option<FilterConfig> {
245 self.filter_config.clone()
246 }
247
248 fn get_ordering_config(&self) -> Option<OrderingConfig> {
249 self.ordering_config.clone()
250 }
251}
252
253impl<M, S> ModelViewSet<M, S>
254where
255 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
256 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
257{
258 pub fn new(basename: impl Into<String>) -> Self {
294 Self {
295 basename: basename.into(),
296 lookup_field: "id".to_string(),
297 pagination_config: Some(PaginationConfig::default()),
298 filter_config: None,
299 ordering_config: None,
300 handler: ModelViewSetHandler::<M>::new().with_serializer(Arc::new(S::default())),
301 _serializer: PhantomData,
302 }
303 }
304
305 pub fn with_lookup_field(mut self, field: impl Into<String>) -> Self {
342 self.lookup_field = field.into();
343 self.handler =
344 std::mem::take(&mut self.handler).with_lookup_field(self.lookup_field.clone());
345 self
346 }
347
348 pub fn with_pagination(mut self, config: PaginationConfig) -> Self {
381 self.pagination_config = Some(config);
382 self
383 }
384
385 pub fn without_pagination(mut self) -> Self {
409 self.pagination_config = None;
410 self
411 }
412
413 pub fn with_filters(mut self, config: FilterConfig) -> Self {
441 self.filter_config = Some(config);
442 self
443 }
444
445 pub fn with_ordering(mut self, config: OrderingConfig) -> Self {
473 self.ordering_config = Some(config);
474 self
475 }
476
477 pub fn with_pool(mut self, pool: Arc<sqlx::AnyPool>) -> Self {
482 self.handler = std::mem::take(&mut self.handler).with_pool(pool);
483 self
484 }
485
486 pub fn with_db_backend(mut self, backend: DbBackend) -> Self {
488 self.handler = std::mem::take(&mut self.handler).with_db_backend(backend);
489 self
490 }
491
492 pub fn with_serializer(
494 mut self,
495 serializer: Arc<dyn Serializer<Input = M, Output = String> + Send + Sync>,
496 ) -> Self {
497 self.handler = std::mem::take(&mut self.handler).with_serializer(serializer);
498 self
499 }
500
501 pub fn with_queryset(mut self, items: Vec<M>) -> Self {
503 self.handler = std::mem::take(&mut self.handler).with_queryset(items);
504 self
505 }
506
507 pub fn with_queryset_fn<F>(mut self, queryset_fn: F) -> Self
517 where
518 F: Fn(&Request) -> std::result::Result<FilterCondition, ViewError> + Send + Sync + 'static,
519 {
520 self.handler = std::mem::take(&mut self.handler).with_queryset_fn(queryset_fn);
521 self
522 }
523
524 pub fn add_permission(mut self, permission: Arc<dyn Permission>) -> Self {
526 self.handler = std::mem::take(&mut self.handler).add_permission(permission);
527 self
528 }
529
530 pub fn add_filter_backend(mut self, backend: Arc<dyn FilterBackend>) -> Self {
532 self.handler = std::mem::take(&mut self.handler).add_filter_backend(backend);
533 self
534 }
535
536 pub fn as_view(self) -> crate::viewsets::builder::ViewSetBuilder<Self> {
539 crate::viewsets::builder::ViewSetBuilder::new(self)
540 }
541}
542
543#[async_trait]
544impl<M, S> ViewSet for ModelViewSet<M, S>
545where
546 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
547 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
548{
549 fn get_basename(&self) -> &str {
550 &self.basename
551 }
552
553 fn get_lookup_field(&self) -> &str {
554 &self.lookup_field
555 }
556
557 async fn dispatch(&self, request: Request, action: Action) -> Result<Response> {
558 match (request.method.clone(), action.detail) {
562 (Method::GET, false) => self.handler.list(&request).await.map_err(Into::into),
563 (Method::POST, false) => self.handler.create(&request).await.map_err(Into::into),
564 (Method::GET, true) => {
565 let pk = extract_pk(&request, &self.lookup_field)?;
566 self.handler
567 .retrieve(&request, pk)
568 .await
569 .map_err(Into::into)
570 }
571 (Method::PUT, true) | (Method::PATCH, true) => {
572 let pk = extract_pk(&request, &self.lookup_field)?;
573 self.handler.update(&request, pk).await.map_err(Into::into)
574 }
575 (Method::DELETE, true) => {
576 let pk = extract_pk(&request, &self.lookup_field)?;
577 self.handler.destroy(&request, pk).await.map_err(Into::into)
578 }
579 _ => Err(method_not_allowed(&request.method)),
580 }
581 }
582}
583
584impl<M, S> PaginatedViewSet for ModelViewSet<M, S>
586where
587 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
588 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
589{
590 fn get_pagination_config(&self) -> Option<PaginationConfig> {
591 self.pagination_config.clone()
592 }
593}
594
595pub struct ReadOnlyModelViewSet<M, S>
600where
601 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
602 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
603{
604 basename: String,
605 lookup_field: String,
606 pagination_config: Option<PaginationConfig>,
607 filter_config: Option<FilterConfig>,
608 ordering_config: Option<OrderingConfig>,
609 handler: ModelViewSetHandler<M>,
610 _serializer: PhantomData<S>,
611}
612
613impl<M, S> ReadOnlyModelViewSet<M, S>
614where
615 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
616 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
617{
618 pub fn new(basename: impl Into<String>) -> Self {
654 Self {
655 basename: basename.into(),
656 lookup_field: "id".to_string(),
657 pagination_config: Some(PaginationConfig::default()),
658 filter_config: None,
659 ordering_config: None,
660 handler: ModelViewSetHandler::<M>::new().with_serializer(Arc::new(S::default())),
661 _serializer: PhantomData,
662 }
663 }
664
665 pub fn with_lookup_field(mut self, field: impl Into<String>) -> Self {
667 self.lookup_field = field.into();
668 self.handler =
669 std::mem::take(&mut self.handler).with_lookup_field(self.lookup_field.clone());
670 self
671 }
672
673 pub fn with_pagination(mut self, config: PaginationConfig) -> Self {
675 self.pagination_config = Some(config);
676 self
677 }
678
679 pub fn without_pagination(mut self) -> Self {
681 self.pagination_config = None;
682 self
683 }
684
685 pub fn with_filters(mut self, config: FilterConfig) -> Self {
700 self.filter_config = Some(config);
701 self
702 }
703
704 pub fn with_ordering(mut self, config: OrderingConfig) -> Self {
719 self.ordering_config = Some(config);
720 self
721 }
722
723 pub fn with_pool(mut self, pool: Arc<sqlx::AnyPool>) -> Self {
725 self.handler = std::mem::take(&mut self.handler).with_pool(pool);
726 self
727 }
728
729 pub fn with_db_backend(mut self, backend: DbBackend) -> Self {
731 self.handler = std::mem::take(&mut self.handler).with_db_backend(backend);
732 self
733 }
734
735 pub fn with_serializer(
737 mut self,
738 serializer: Arc<dyn Serializer<Input = M, Output = String> + Send + Sync>,
739 ) -> Self {
740 self.handler = std::mem::take(&mut self.handler).with_serializer(serializer);
741 self
742 }
743
744 pub fn with_queryset(mut self, items: Vec<M>) -> Self {
746 self.handler = std::mem::take(&mut self.handler).with_queryset(items);
747 self
748 }
749
750 pub fn with_queryset_fn<F>(mut self, queryset_fn: F) -> Self
759 where
760 F: Fn(&Request) -> std::result::Result<FilterCondition, ViewError> + Send + Sync + 'static,
761 {
762 self.handler = std::mem::take(&mut self.handler).with_queryset_fn(queryset_fn);
763 self
764 }
765
766 pub fn add_permission(mut self, permission: Arc<dyn Permission>) -> Self {
768 self.handler = std::mem::take(&mut self.handler).add_permission(permission);
769 self
770 }
771
772 pub fn add_filter_backend(mut self, backend: Arc<dyn FilterBackend>) -> Self {
774 self.handler = std::mem::take(&mut self.handler).add_filter_backend(backend);
775 self
776 }
777
778 pub fn as_view(self) -> crate::viewsets::builder::ViewSetBuilder<Self> {
781 crate::viewsets::builder::ViewSetBuilder::new(self)
782 }
783}
784
785#[async_trait]
786impl<M, S> ViewSet for ReadOnlyModelViewSet<M, S>
787where
788 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
789 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
790{
791 fn get_basename(&self) -> &str {
792 &self.basename
793 }
794
795 fn get_lookup_field(&self) -> &str {
796 &self.lookup_field
797 }
798
799 async fn dispatch(&self, request: Request, action: Action) -> Result<Response> {
800 match (request.method.clone(), action.detail) {
801 (Method::GET, false) => self.handler.list(&request).await.map_err(Into::into),
802 (Method::GET, true) => {
803 let pk = extract_pk(&request, &self.lookup_field)?;
804 self.handler
805 .retrieve(&request, pk)
806 .await
807 .map_err(Into::into)
808 }
809 _ => Err(method_not_allowed(&request.method)),
810 }
811 }
812}
813
814impl<M, S> PaginatedViewSet for ReadOnlyModelViewSet<M, S>
816where
817 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
818 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
819{
820 fn get_pagination_config(&self) -> Option<PaginationConfig> {
821 self.pagination_config.clone()
822 }
823}
824
825impl<M, S> FilterableViewSet for ReadOnlyModelViewSet<M, S>
827where
828 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
829 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
830{
831 fn get_filter_config(&self) -> Option<FilterConfig> {
832 self.filter_config.clone()
833 }
834
835 fn get_ordering_config(&self) -> Option<OrderingConfig> {
836 self.ordering_config.clone()
837 }
838}
839
840impl<M, S> std::panic::UnwindSafe for ModelViewSet<M, S>
850where
851 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
852 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
853{
854}
855impl<M, S> std::panic::RefUnwindSafe for ModelViewSet<M, S>
856where
857 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
858 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
859{
860}
861
862impl<M, S> std::panic::UnwindSafe for ReadOnlyModelViewSet<M, S>
863where
864 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
865 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
866{
867}
868impl<M, S> std::panic::RefUnwindSafe for ReadOnlyModelViewSet<M, S>
869where
870 M: Model + Serialize + DeserializeOwned + Clone + Send + Sync + 'static,
871 S: Serializer<Input = M, Output = String> + Default + Send + Sync + 'static,
872{
873}
874
875#[cfg(test)]
876mod tests {
877 use super::*;
878 use hyper::Method;
879 use reinhardt_db::orm::{FieldSelector, Filter, FilterOperator, Model};
880 use serde::{Deserialize, Serialize};
881 use std::collections::HashMap;
882 use std::sync::Arc;
883
884 #[derive(Debug, Clone, Serialize, Deserialize)]
887 struct DummyModel {
888 id: Option<i64>,
889 secret: String,
890 }
891
892 #[derive(Clone)]
893 struct DummyFields;
894
895 impl FieldSelector for DummyFields {
896 fn with_alias(self, _alias: &str) -> Self {
897 self
898 }
899 }
900
901 impl Model for DummyModel {
902 type PrimaryKey = i64;
903 type Fields = DummyFields;
904 type Objects = reinhardt_db::orm::Manager<Self>;
905 fn table_name() -> &'static str {
906 "dummy"
907 }
908 fn primary_key(&self) -> Option<Self::PrimaryKey> {
909 self.id
910 }
911 fn set_primary_key(&mut self, value: Self::PrimaryKey) {
912 self.id = Some(value);
913 }
914 fn new_fields() -> Self::Fields {
915 DummyFields
916 }
917 }
918
919 #[derive(Default)]
920 struct RedactingDummySerializer;
921
922 impl Serializer for RedactingDummySerializer {
923 type Input = DummyModel;
924 type Output = String;
925
926 fn serialize(
927 &self,
928 input: &Self::Input,
929 ) -> std::result::Result<Self::Output, reinhardt_rest::serializers::SerializerError> {
930 serde_json::to_string(&serde_json::json!({ "id": input.id })).map_err(|e| {
931 reinhardt_rest::serializers::SerializerError::Serde {
932 message: format!("Serialization error: {}", e),
933 }
934 })
935 }
936
937 fn deserialize(
938 &self,
939 output: &Self::Output,
940 ) -> std::result::Result<Self::Input, reinhardt_rest::serializers::SerializerError> {
941 let value: serde_json::Value = serde_json::from_str(output).map_err(|e| {
942 reinhardt_rest::serializers::SerializerError::Serde {
943 message: format!("Deserialization error: {}", e),
944 }
945 })?;
946 if value.get("secret").is_some() {
947 return Err(reinhardt_rest::serializers::SerializerError::Serde {
948 message: "secret is not writable".to_string(),
949 });
950 }
951 Ok(DummyModel {
952 id: value.get("id").and_then(serde_json::Value::as_i64),
953 secret: String::new(),
954 })
955 }
956 }
957
958 #[test]
959 fn queryset_fn_builders_preserve_viewset_object_safety() {
960 let model: Arc<dyn ViewSet> = Arc::new(
961 ModelViewSet::<DummyModel, RedactingDummySerializer>::new("test").with_queryset_fn(
962 |_| Ok(Filter::new("organization_id", FilterOperator::Eq, 1_i64.into()).into()),
963 ),
964 );
965 let read_only: Arc<dyn ViewSet> = Arc::new(
966 ReadOnlyModelViewSet::<DummyModel, RedactingDummySerializer>::new("test")
967 .with_queryset_fn(|_| {
968 Ok(Filter::new("organization_id", FilterOperator::Eq, 1_i64.into()).into())
969 }),
970 );
971
972 assert_eq!(model.get_basename(), "test");
973 assert_eq!(read_only.get_basename(), "test");
974 }
975
976 #[tokio::test]
977 async fn test_model_viewset_new_wires_declared_serializer() {
978 let viewset = ModelViewSet::<DummyModel, RedactingDummySerializer>::new("test")
979 .with_queryset(vec![DummyModel {
980 id: Some(7),
981 secret: "hidden".to_string(),
982 }]);
983 let request = Request::builder()
984 .method(Method::GET)
985 .uri("/test/")
986 .body(bytes::Bytes::new())
987 .build()
988 .unwrap();
989
990 let response = viewset.dispatch(request, Action::list()).await.unwrap();
991
992 assert_eq!(response.status, hyper::StatusCode::OK);
993 assert_eq!(response.body, bytes::Bytes::from_static(br#"[{"id":7}]"#));
994 }
995
996 #[tokio::test]
997 async fn test_viewset_builder_validation_empty_actions() {
998 let viewset = ModelViewSet::<
999 DummyModel,
1000 reinhardt_rest::serializers::JsonSerializer<DummyModel>,
1001 >::new("test");
1002 let builder = viewset.as_view();
1003
1004 let result = builder.build();
1006 assert!(result.is_err());
1007
1008 match result {
1010 Err(e) => assert!(
1011 e.to_string()
1012 .contains("The `actions` argument must be provided")
1013 ),
1014 Ok(_) => panic!("Expected error but got success"),
1015 }
1016 }
1017
1018 #[tokio::test]
1019 async fn test_viewset_builder_name_suffix_mutual_exclusivity() {
1020 let viewset = ModelViewSet::<
1021 DummyModel,
1022 reinhardt_rest::serializers::JsonSerializer<DummyModel>,
1023 >::new("test");
1024 let builder = viewset.as_view();
1025
1026 let result = builder
1028 .with_name("test_name")
1029 .and_then(|b| b.with_suffix("test_suffix"));
1030
1031 assert!(result.is_err());
1032
1033 match result {
1035 Err(e) => assert!(e.to_string().contains("received both `name` and `suffix`")),
1036 Ok(_) => panic!("Expected error but got success"),
1037 }
1038 }
1039
1040 #[tokio::test]
1041 async fn test_viewset_builder_successful_build() {
1042 let viewset = ModelViewSet::<
1043 DummyModel,
1044 reinhardt_rest::serializers::JsonSerializer<DummyModel>,
1045 >::new("test");
1046 let mut actions = HashMap::new();
1047 actions.insert(Method::GET, "list".to_string());
1048
1049 let builder = viewset.as_view();
1050 let result = builder.with_actions(actions).build();
1051
1052 let handler = result.unwrap();
1053
1054 assert!(Arc::strong_count(&handler) > 0);
1057 }
1058
1059 #[tokio::test]
1060 async fn test_viewset_builder_with_name() {
1061 let viewset = ModelViewSet::<
1062 DummyModel,
1063 reinhardt_rest::serializers::JsonSerializer<DummyModel>,
1064 >::new("test");
1065 let mut actions = HashMap::new();
1066 actions.insert(Method::GET, "list".to_string());
1067
1068 let builder = viewset.as_view();
1069 let result = builder
1070 .with_actions(actions)
1071 .with_name("test_view")
1072 .and_then(|b| b.build());
1073
1074 assert!(result.is_ok());
1075 }
1076
1077 #[tokio::test]
1078 async fn test_viewset_builder_with_suffix() {
1079 let viewset = ModelViewSet::<
1080 DummyModel,
1081 reinhardt_rest::serializers::JsonSerializer<DummyModel>,
1082 >::new("test");
1083 let mut actions = HashMap::new();
1084 actions.insert(Method::GET, "list".to_string());
1085
1086 let builder = viewset.as_view();
1087 let result = builder
1088 .with_actions(actions)
1089 .with_suffix("_list")
1090 .and_then(|b| b.build());
1091
1092 assert!(result.is_ok());
1093 }
1094}