1use crate::{
2 CompiledQuery, DataModelError, FilterOperator, QueryField, QueryFilter, QuerySort, QuerySpec,
3 TableName, compile_filters, ensure_repository_field, quote_identifier, require_non_empty,
4};
5
6#[derive(Debug, Clone, PartialEq, Eq, Default)]
7pub struct RepositoryContextBindings {
8 pub locale_field: Option<QueryField>,
9 pub publication_field: Option<QueryField>,
10 pub published_value: Option<String>,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct RepositorySpec {
15 pub id: String,
16 pub table: TableName,
17 pub projection: Vec<QueryField>,
18 pub filterable_fields: Vec<QueryField>,
19 pub sortable_fields: Vec<QueryField>,
20 pub default_sort: Vec<QuerySort>,
21 pub context: RepositoryContextBindings,
22}
23
24impl RepositorySpec {
25 pub fn new(
26 id: impl Into<String>,
27 table: TableName,
28 projection: Vec<QueryField>,
29 ) -> Result<Self, DataModelError> {
30 let id = require_non_empty("repository_id", id.into())?;
31 if projection.is_empty() {
32 return Err(DataModelError::EmptyProjection { repository: id });
33 }
34
35 Ok(Self {
36 id,
37 table,
38 filterable_fields: projection.clone(),
39 sortable_fields: projection.clone(),
40 projection,
41 default_sort: Vec::new(),
42 context: RepositoryContextBindings::default(),
43 })
44 }
45
46 pub fn with_filterable_field(
47 mut self,
48 field: impl Into<String>,
49 ) -> Result<Self, DataModelError> {
50 let field = QueryField::new(field)?;
51 if !self.filterable_fields.contains(&field) {
52 self.filterable_fields.push(field);
53 }
54 Ok(self)
55 }
56
57 pub fn with_sortable_field(mut self, field: impl Into<String>) -> Result<Self, DataModelError> {
58 let field = QueryField::new(field)?;
59 if !self.sortable_fields.contains(&field) {
60 self.sortable_fields.push(field);
61 }
62 Ok(self)
63 }
64
65 pub fn with_default_sort(mut self, sort: QuerySort) -> Self {
66 self.default_sort.push(sort);
67 self
68 }
69
70 pub fn with_locale_field(mut self, field: impl Into<String>) -> Result<Self, DataModelError> {
71 self.context.locale_field = Some(QueryField::new(field)?);
72 Ok(self)
73 }
74
75 pub fn with_publication_field(
76 mut self,
77 field: impl Into<String>,
78 published_value: impl Into<String>,
79 ) -> Result<Self, DataModelError> {
80 self.context.publication_field = Some(QueryField::new(field)?);
81 self.context.published_value = Some(require_non_empty(
82 "published_value",
83 published_value.into(),
84 )?);
85 Ok(self)
86 }
87
88 pub fn compile_query(&self, spec: &QuerySpec) -> Result<CompiledQuery, DataModelError> {
89 let mut filters = Vec::new();
90 if let (Some(locale), Some(locale_field)) = (
91 spec.context.locale.as_ref(),
92 self.context.locale_field.as_ref(),
93 ) {
94 filters.push(QueryFilter::new(
95 locale_field.as_str(),
96 FilterOperator::Eq,
97 vec![locale.clone()],
98 )?);
99 }
100
101 if let (
102 crate::PublicationVisibility::PublishedOnly,
103 Some(publication_field),
104 Some(published),
105 ) = (
106 spec.context.publication_visibility,
107 self.context.publication_field.as_ref(),
108 self.context.published_value.as_ref(),
109 ) {
110 filters.push(QueryFilter::new(
111 publication_field.as_str(),
112 FilterOperator::Eq,
113 vec![published.clone()],
114 )?);
115 }
116
117 filters.extend(spec.filters.clone());
118
119 for filter in &filters {
120 ensure_repository_field(
121 &self.id,
122 &filter.field,
123 &self.filterable_fields,
124 self.context.locale_field.as_ref(),
125 self.context.publication_field.as_ref(),
126 )?;
127 }
128
129 let sort = if spec.sort.is_empty() {
130 self.default_sort.clone()
131 } else {
132 spec.sort.clone()
133 };
134
135 for sort_field in &sort {
136 ensure_repository_field(
137 &self.id,
138 &sort_field.field,
139 &self.sortable_fields,
140 self.context.locale_field.as_ref(),
141 self.context.publication_field.as_ref(),
142 )?;
143 }
144
145 let projection = self
146 .projection
147 .iter()
148 .map(|field| quote_identifier(field.as_str()))
149 .collect::<Vec<_>>()
150 .join(", ");
151 let mut sql = format!(
152 "SELECT {projection} FROM {}",
153 quote_identifier(self.table.as_str())
154 );
155
156 let (where_clauses, bind_values, _) = compile_filters(&filters, 1)?;
157 if !where_clauses.is_empty() {
158 sql.push_str(" WHERE ");
159 sql.push_str(&where_clauses.join(" AND "));
160 }
161
162 if !sort.is_empty() {
163 sql.push_str(" ORDER BY ");
164 sql.push_str(
165 &sort
166 .iter()
167 .map(|sort| {
168 format!(
169 "{} {}",
170 quote_identifier(sort.field.as_str()),
171 sort.direction.to_string().to_uppercase()
172 )
173 })
174 .collect::<Vec<_>>()
175 .join(", "),
176 );
177 }
178
179 sql.push_str(&format!(
180 " LIMIT {} OFFSET {}",
181 spec.page.size,
182 spec.page.offset()
183 ));
184
185 Ok(CompiledQuery {
186 sql,
187 bind_values,
188 page: spec.page,
189 context: spec.context.clone(),
190 })
191 }
192}