1use core::cmp::min;
88use core::fmt::Debug;
89use regex::{Captures, Regex};
90use std::num::ParseIntError;
91use std::str::FromStr;
92
93use log::{debug, trace};
94use thiserror::Error;
95use wiremock::http::Url;
96use wiremock::{Request, Respond, ResponseTemplate};
97
98use crate::filtering::{
99 Filter, Filterable, FilterableWithContext, OperatorSet, OperatorSetWithContext,
100};
101use crate::row::{IntoRow, IntoRowWithContext, Serializer};
102use crate::sorting::{OrderingSet, OrderingSetWithContext, Sortable, SortableWithContext, Sorter};
103
104#[derive(Debug, Error)]
105#[cfg_attr(docsrs, doc(cfg(feature = "wiremock")))]
106pub enum MockError {
108 #[error("unknown query parameter `{0}`")]
113 UnknownQueryParameter(String),
114 #[error("expected integer value in query")]
118 BadIntegerInQuery(#[from] ParseIntError),
119 #[error("bad value in query: {0}")]
121 BadValue(#[from] strum::ParseError),
122 #[error("bad datetime value in query")]
124 BadDateInQuery(#[from] chrono::ParseError),
125 #[error("invalid sort: {0}")]
127 BadSort(#[from] crate::sorting::SorterError),
128}
129
130struct ResponseSet<T> {
131 contents: Vec<T>,
132 total_matches: usize,
133 next: Option<String>,
134 prev: Option<String>,
135}
136
137impl<T> ResponseSet<T> {
138 pub fn new(
139 contents: Vec<T>,
140 total_matches: usize,
141 next: Option<String>,
142 prev: Option<String>,
143 ) -> Self {
144 ResponseSet {
145 contents,
146 total_matches,
147 next,
148 prev,
149 }
150 }
151}
152
153impl<'q, T: IntoRow<'q>> ResponseSet<&T> {
154 pub fn mock_json(&self) -> serde_json::Value {
155 let mut map = serde_json::map::Map::new();
156 map.insert(
157 "count".to_string(),
158 serde_json::Value::Number(serde_json::Number::from(self.total_matches)),
159 );
160 map.insert("results".to_string(), to_rows(&self.contents));
161 map.insert(
162 "next".to_string(),
163 self.next
164 .as_ref()
165 .map(|x| serde_json::Value::String(x.clone()))
166 .unwrap_or(serde_json::Value::Null),
167 );
168 map.insert(
169 "previous".to_string(),
170 self.prev
171 .as_ref()
172 .map(|x| serde_json::Value::String(x.clone()))
173 .unwrap_or(serde_json::Value::Null),
174 );
175
176 serde_json::Value::Object(map)
177 }
178}
179
180fn to_rows<'q, T: IntoRow<'q>>(data: &[&T]) -> serde_json::Value {
181 let mut array = Vec::new();
182 let ser = T::get_serializer();
183 for item in data {
184 array.push(ser.to_json(item));
185 }
186 serde_json::Value::Array(array)
187}
188
189struct Page {
190 offset: usize,
191 limit: usize,
192}
193
194struct PaginatedResponse<'a, T> {
195 data: Vec<&'a T>,
196 total: usize,
197 next: Option<Page>,
198 prev: Option<Page>,
199}
200
201struct ResponseSetBuilder<'a, T> {
202 ordering: Vec<Box<dyn Sorter<T> + 'a>>,
203 filtering: Vec<Box<dyn Filter<T> + 'a>>,
204 limit: Option<usize>,
205 offset: usize,
206}
207
208impl<'a, T> Default for ResponseSetBuilder<'a, T> {
209 fn default() -> Self {
210 Self::new(None)
211 }
212}
213
214impl<'a, T> ResponseSetBuilder<'a, T> {
215 pub fn new(default_limit: Option<usize>) -> Self {
216 ResponseSetBuilder {
217 ordering: Vec::new(),
218 filtering: Vec::new(),
219 limit: default_limit,
220 offset: 0,
221 }
222 }
223
224 pub fn order_by(&mut self, order: Box<dyn Sorter<T> + 'a>) -> &mut Self {
225 self.ordering.push(order);
226 self
227 }
228
229 pub fn filter_by(&mut self, filter: Box<dyn Filter<T> + 'a>) -> &mut Self {
230 self.filtering.push(filter);
231 self
232 }
233
234 pub fn limit(&mut self, limit: usize) -> &mut Self {
235 self.limit = Some(limit);
236 self
237 }
238
239 pub fn offset(&mut self, offset: usize) -> &mut Self {
240 self.offset = offset;
241 self
242 }
243
244 pub fn apply<'b, I: Iterator<Item = &'b T>>(&mut self, iter: I) -> PaginatedResponse<'b, T> {
245 let mut v = Vec::new();
246 for item in iter {
247 v.push(item)
248 }
249
250 for f in &self.filtering {
251 f.filter_ref_vec(&mut v);
252 }
253
254 for order in &self.ordering {
255 order.sort_ref_vec(&mut v);
256 }
257
258 let limit = self.limit.unwrap_or(v.len());
259 let total = v.len();
260
261 let start = min(v.len(), self.offset);
262 let prev = if start > 0 {
263 Some(Page {
264 offset: self.offset - min(self.offset, limit),
265 limit,
266 })
267 } else {
268 None
269 };
270
271 v.drain(..start);
272
273 let end = min(v.len(), limit);
274 let next = if end < v.len() {
275 Some(Page {
276 offset: self.offset + limit,
277 limit,
278 })
279 } else {
280 None
281 };
282
283 v.drain(end..);
284
285 PaginatedResponse {
286 data: v,
287 total,
288 next,
289 prev,
290 }
291 }
292}
293
294fn make_page_url(url: &Url, offset: usize, limit: usize) -> Url {
295 let mut new_url = url.clone();
296
297 let mut new_pairs = new_url.query_pairs_mut();
298 let old_pairs = url.query_pairs();
299
300 let mut offset_seen = false;
301
302 new_pairs.clear();
303 for (key, value) in old_pairs {
304 match key.as_ref() {
305 "limit" => {
306 new_pairs.append_pair("limit", &limit.to_string());
307 }
308 "offset" => {
309 if offset > 0 {
310 new_pairs.append_pair("offset", &offset.to_string());
311 }
312 offset_seen = true;
313 }
314 _ => {
315 new_pairs.append_pair(key.as_ref(), value.as_ref());
316 }
317 }
318 }
319
320 if !offset_seen && offset > 0 {
321 new_pairs.append_pair("offset", &offset.to_string());
322 }
323
324 new_pairs.finish();
325 drop(new_pairs);
326
327 new_url
328}
329
330#[cfg_attr(
352 feature = "clone-replace",
353 doc = r##"
354The `"clone-replace"` feature enables a mutable [`RowSource`] using a
355[`clone_replace::CloneReplace`](::clone_replace::CloneReplace). Your
356test code is free to hold a reference to, and mutate, the
357[`CloneReplace`](::clone_replace::CloneReplace) wrapped collection.
358Each query will get an [`Arc<T>`](std::sync::Arc) of a snapshot of the
359state of the collection at the start of the query. You can also
360extract a single [`Vec<T>`] field from a struct and serve that using
361[`CloneReplaceFieldSource`].
362"##
363)]
364#[cfg_attr(
365 all(feature = "clone-replace", feature = "persian-rug"),
366 doc = r##"
367When both `"clone-replace"` and `"persian-rug"` are enabled, you can
368combine a [`CloneReplace`](::clone_replace::CloneReplace) and a
369[`persian_rug::Context`] using a [`CloneReplacePersianRugTableSource`]
370to create a [`RowSource`] for a single type inside the shared state, which
371remains mutable.
372"##
373)]
374#[cfg_attr(docsrs, doc(cfg(feature = "wiremock")))]
375pub trait RowSource
376where
377 Self::Rows: RowSet<Item = Self::Item>,
378 for<'a> &'a <Self::Rows as RowSet>::Target: IntoIterator<Item = &'a Self::Item>,
379{
380 type Item;
382 type Rows;
384 fn get(&self) -> Self::Rows;
399}
400
401pub trait RowSet {
411 type Target;
413 type Item;
415 fn get(&self) -> &Self::Target;
417}
418
419impl<T: Clone> RowSource for Vec<T> {
420 type Item = T;
421 type Rows = Self;
422 fn get(&self) -> Self::Rows {
423 self.clone()
424 }
425}
426
427impl<T> RowSet for Vec<T> {
428 type Item = T;
429 type Target = Self;
430 fn get(&self) -> &Self::Target {
431 self
432 }
433}
434
435impl<T> RowSource for std::sync::Arc<Vec<T>> {
436 type Item = T;
437 type Rows = Self;
438 fn get(&self) -> Self::Rows {
439 self.clone()
440 }
441}
442
443impl<T> RowSet for std::sync::Arc<Vec<T>> {
444 type Item = T;
445 type Target = Vec<T>;
446 fn get(&self) -> &Self::Target {
447 self
448 }
449}
450
451#[cfg(all(feature = "clone-replace", feature = "persian-rug"))]
452pub use self::clone_replace::persian_rug::CloneReplacePersianRugTableSource;
453#[cfg(feature = "clone-replace")]
454pub use self::clone_replace::CloneReplaceFieldSource;
455
456#[doc(hidden)]
457#[cfg(feature = "clone-replace")]
458pub mod clone_replace {
459 use super::{RowSet, RowSource};
460 use ::clone_replace::CloneReplace;
461
462 impl<T> RowSource for CloneReplace<Vec<T>> {
465 type Item = T;
466 type Rows = std::sync::Arc<Vec<T>>;
467
468 fn get(&self) -> Self::Rows {
469 self.access()
470 }
471 }
472
473 pub struct CloneReplaceFieldSource<F, T> {
475 data: CloneReplace<T>,
476 getter: F,
477 }
478
479 impl<F, T, U> CloneReplaceFieldSource<F, T>
480 where
481 F: Fn(&T) -> &Vec<U> + Clone,
482 {
483 pub fn new(data: CloneReplace<T>, getter: F) -> Self {
489 Self { data, getter }
490 }
491 }
492
493 impl<F, T, U> RowSource for CloneReplaceFieldSource<F, T>
494 where
495 F: Fn(&T) -> &Vec<U> + Clone,
496 {
497 type Item = U;
498 type Rows = ArcField<F, T>;
499 fn get(&self) -> Self::Rows {
500 ArcField {
501 getter: self.getter.clone(),
502 data: self.data.access(),
503 }
504 }
505 }
506
507 #[doc(hidden)]
508 pub struct ArcField<F, T> {
509 getter: F,
510 data: std::sync::Arc<T>,
511 }
512
513 impl<F, T, U> RowSet for ArcField<F, T>
514 where
515 F: Fn(&T) -> &Vec<U>,
516 {
517 type Item = U;
518 type Target = Vec<U>;
519 fn get(&self) -> &Self::Target {
520 (self.getter)(&self.data)
521 }
522 }
523
524 #[cfg(feature = "persian-rug")]
525 pub mod persian_rug {
526 use super::{RowSet, RowSource};
527 use clone_replace::CloneReplace;
528
529 pub struct CloneReplacePersianRugTableSource<F, T> {
539 data: CloneReplace<T>,
540 getter: F,
541 }
542
543 impl<C, F, U> CloneReplacePersianRugTableSource<F, C>
544 where
545 F: Fn(&std::sync::Arc<C>) -> ::persian_rug::TableIterator<'_, U> + Clone,
546 for<'a> &'a PersianRugTable<std::sync::Arc<C>, F>: IntoIterator<Item = &'a U>,
547 {
548 pub fn new(data: CloneReplace<C>, getter: F) -> Self {
549 Self { data, getter }
550 }
551 }
552
553 #[persian_rug::constraints(context = C, access(U))]
554 impl<C, F, U> RowSource for CloneReplacePersianRugTableSource<F, C>
555 where
556 F: Fn(&std::sync::Arc<C>) -> ::persian_rug::TableIterator<'_, U> + Clone,
557 for<'a> &'a PersianRugTable<std::sync::Arc<C>, F>: IntoIterator<Item = &'a U>,
558 {
559 type Item = U;
560 type Rows = PersianRugTable<std::sync::Arc<C>, F>;
561 fn get(&self) -> Self::Rows {
562 PersianRugTable {
563 getter: self.getter.clone(),
564 access: self.data.access(),
565 }
566 }
567 }
568
569 #[doc(hidden)]
570 #[derive(Clone)]
571 pub struct PersianRugTable<A: Clone, F: Clone> {
572 access: A,
573 getter: F,
574 }
575
576 #[persian_rug::constraints(context = C, access(U))]
577 impl<A, C, F, U> RowSet for PersianRugTable<A, F>
578 where
579 A: ::persian_rug::Accessor<Context = C>,
580 F: Fn(&A) -> ::persian_rug::TableIterator<'_, U> + Clone,
581 {
582 type Item = U;
583 type Target = Self;
584 fn get(&self) -> &Self::Target {
585 self
586 }
587 }
588
589 impl<A, C, F> ::persian_rug::Accessor for PersianRugTable<A, F>
590 where
591 A: persian_rug::Accessor<Context = C>,
592 C: persian_rug::Context,
593 F: Clone,
594 {
595 type Context = C;
596
597 fn get<T>(&self, what: &persian_rug::Proxy<T>) -> &T
598 where
599 Self::Context: persian_rug::Owner<T>,
600 T: persian_rug::Contextual<Context = Self::Context>,
601 {
602 self.access.get(what)
603 }
604
605 fn get_iter<T>(&self) -> persian_rug::TableIterator<'_, T>
606 where
607 Self::Context: persian_rug::Owner<T>,
608 T: persian_rug::Contextual<Context = Self::Context>,
609 {
610 self.access.get_iter()
611 }
612
613 fn get_proxy_iter<T>(&self) -> persian_rug::TableProxyIterator<'_, T>
614 where
615 Self::Context: persian_rug::Owner<T>,
616 T: persian_rug::Contextual<Context = Self::Context>,
617 {
618 self.access.get_proxy_iter()
619 }
620 }
621
622 #[persian_rug::constraints(context = C, access(U))]
623 impl<'a, A, C, F, U> IntoIterator for &'a PersianRugTable<A, F>
624 where
625 A: persian_rug::Accessor<Context = C>,
626 F: Fn(&'a A) -> persian_rug::TableIterator<'a, U> + Clone,
627 U: 'a,
628 {
629 type Item = &'a U;
630 type IntoIter = persian_rug::TableIterator<'a, U>;
631 fn into_iter(self) -> Self::IntoIter {
632 (self.getter)(&self.access)
633 }
634 }
635 }
636}
637
638fn parse_query<'a, 'q, R, I>(
639 input_url: &Url,
640 output_url: &Url,
641 iter: I,
642 default_limit: Option<usize>,
643) -> Result<ResponseSet<&'a R>, MockError>
644where
645 R: Filterable<'q>,
646 R: Sortable<'q> + 'q,
648 R: IntoRow<'q>,
649 I: Iterator<Item = &'a R>,
650{
651 let mut rb = ResponseSetBuilder::new(default_limit);
652 let qr = OperatorSet::<R>::new();
653 let sr = OrderingSet::<R>::new();
654 let pairs = input_url.query_pairs();
655 for (key, value) in pairs {
656 match key.as_ref() {
657 "ordering" => {
658 rb.order_by(sr.create_sort(&*value)?);
659 }
660 "offset" => {
661 let v = usize::from_str(value.as_ref())?;
662 rb.offset(v);
663 }
664 "limit" => {
665 let v = usize::from_str(value.as_ref())?;
666 rb.limit(v);
667 }
668 _ => {
669 if let Ok(filter) = qr.create_filter_from_query_pair(&key, &value) {
670 rb.filter_by(filter);
671 continue;
672 }
673 return Err(MockError::UnknownQueryParameter(String::from(key.as_ref())));
674 }
675 }
676 }
677 let response = rb.apply(iter);
678 Ok(ResponseSet::new(
679 response.data,
680 response.total,
681 response
682 .next
683 .map(|page| make_page_url(output_url, page.offset, page.limit).to_string()),
684 response
685 .prev
686 .map(|page| make_page_url(output_url, page.offset, page.limit).to_string()),
687 ))
688}
689
690#[cfg_attr(docsrs, doc(cfg(feature = "wiremock")))]
704pub struct Endpoint<T>
705where
706 T: Send + Sync + RowSource,
707 for<'t> &'t <<T as RowSource>::Rows as RowSet>::Target:
708 IntoIterator<Item = &'t <T as RowSource>::Item>,
709{
710 row_source: T,
711 base_uri: Option<Url>,
712 default_limit: Option<usize>,
713}
714
715impl<'q, T> Endpoint<T>
716where
717 T: Send + Sync + RowSource,
718 for<'t> &'t <<T as RowSource>::Rows as RowSet>::Target:
719 IntoIterator<Item = &'t <T as RowSource>::Item>,
720 <T as RowSource>::Item: Send + Sync + Filterable<'q> + Sortable<'q>,
721{
722 pub fn new(row_source: T, base_uri: Option<&str>) -> Self {
736 Self {
737 row_source,
738 base_uri: base_uri.map(|x| Url::parse(x).unwrap()),
739 default_limit: None,
740 }
741 }
742
743 pub fn default_limit(&mut self, limit: usize) {
751 self.default_limit = Some(limit);
752 }
753}
754
755impl<'q, T> Respond for Endpoint<T>
756where
757 T: Send + Sync + RowSource,
758 for<'t> &'t <<T as RowSource>::Rows as RowSet>::Target:
759 IntoIterator<Item = &'t <T as RowSource>::Item>,
760 <T as RowSource>::Item: Send + Sync + Filterable<'q> + Sortable<'q> + IntoRow<'q> + 'q,
762{
763 fn respond(&self, request: &Request) -> ResponseTemplate {
764 let mut u = request.url.clone();
767 if let Some(ref base) = self.base_uri {
768 u.set_host(base.host_str()).unwrap();
769 u.set_scheme(base.scheme()).unwrap();
770 u.set_port(base.port()).unwrap();
771 }
772 let res = {
773 let data = self.row_source.get();
774 let res = {
775 let rows = data.get().into_iter();
776 let body = parse_query::<_, _>(&request.url, &u, rows, self.default_limit);
777 match body {
778 Ok(rs) => {
779 let bb = rs.mock_json();
780 ResponseTemplate::new(200).set_body_json(bb)
781 }
782 Err(e) => {
783 debug!("Failed to respond to {}: {}", request.url, e);
784 ResponseTemplate::new(500).set_body_string(e.to_string())
785 }
786 }
787 };
788 res
789 };
790 res
791 }
792}
793
794#[cfg_attr(docsrs, doc(cfg(feature = "wiremock")))]
807pub fn nested_endpoint_matches(root: &str, parent: &str, child: &str) -> impl wiremock::Match {
808 wiremock::matchers::path_regex(format!(r"^{}/{}/[^/]+/{}/$", root, parent, child))
809}
810
811fn replace_into(target: &str, cap: &Captures<'_>) -> String {
812 let mut res = String::new();
813 cap.expand(target, &mut res);
814 res
815}
816
817struct UrlTransform {
818 regex: Regex,
819 path: String,
820 pairs: Vec<(String, String)>,
821}
822
823impl UrlTransform {
824 pub fn new(pattern: &str, path: &str, pairs: Vec<(&str, &str)>) -> UrlTransform {
825 Self {
826 regex: Regex::new(pattern).unwrap(),
827 path: path.to_string(),
828 pairs: pairs
829 .into_iter()
830 .map(|(x, y)| (x.to_string(), y.to_string()))
831 .collect(),
832 }
833 }
834
835 pub fn transform(&self, url: &Url) -> Url {
836 debug!("Matching {} against {:?}", url, self.regex);
837 if let Some(captures) = self.regex.captures(url.path()) {
838 let mut u = url.clone();
839 u.set_path(&replace_into(&self.path, &captures));
840 for (k, v) in self.pairs.iter() {
841 u.query_pairs_mut()
842 .append_pair(&replace_into(k, &captures), &replace_into(v, &captures));
843 }
844 u
845 } else {
846 url.clone()
847 }
848 }
849}
850
851#[cfg_attr(docsrs, doc(cfg(feature = "wiremock")))]
872pub struct NestedEndpoint<T> {
873 transform: UrlTransform,
874 row_source: T,
875 base_uri: Option<Url>,
876 default_limit: Option<usize>,
877}
878
879#[cfg_attr(docsrs, doc(cfg(feature = "wiremock")))]
941pub struct NestedEndpointParams<'a> {
942 pub root: &'a str,
944 pub parent: &'a str,
946 pub child: &'a str,
948 pub parent_query: &'a str,
953 pub base_uri: Option<&'a str>,
958}
959
960impl<'q, T> NestedEndpoint<T>
961where
962 T: Send + Sync + RowSource,
963 for<'t> &'t <<T as RowSource>::Rows as RowSet>::Target:
964 IntoIterator<Item = &'t <T as RowSource>::Item>,
965 <T as RowSource>::Item: Send + Sync + Filterable<'q> + Sortable<'q>,
966{
967 pub fn new(row_source: T, p: NestedEndpointParams<'_>) -> Self {
972 Self {
973 transform: UrlTransform::new(
974 &format!(r"^{}/{}/(?P<parent>[^/]+)/{}/$", p.root, p.parent, p.child),
975 &format!("{}/{}/", p.root, p.child),
976 vec![(&*p.parent_query, "${parent}")],
977 ),
978 row_source,
979 base_uri: p.base_uri.map(|x| Url::parse(x).unwrap()),
980 default_limit: None,
981 }
982 }
983
984 pub fn default_limit(&mut self, limit: usize) {
992 self.default_limit = Some(limit);
993 }
994}
995
996impl<'q, T> Respond for NestedEndpoint<T>
997where
998 T: Send + Sync + RowSource,
999 for<'t> &'t <<T as RowSource>::Rows as RowSet>::Target:
1000 IntoIterator<Item = &'t <T as RowSource>::Item>,
1001 <T as RowSource>::Item: Send + Sync + Filterable<'q> + Sortable<'q> + IntoRow<'q> + 'q,
1003{
1004 fn respond(&self, request: &Request) -> ResponseTemplate {
1005 trace!("Request URL: {}", request.url);
1006 let input_url = self.transform.transform(&request.url);
1007 trace!("Transformed URL: {}", input_url);
1008
1009 let data = self.row_source.get();
1010 let mut output_url = request.url.clone();
1011 if let Some(ref base) = self.base_uri {
1012 output_url.set_host(base.host_str()).unwrap();
1013 output_url.set_scheme(base.scheme()).unwrap();
1014 output_url.set_port(base.port()).unwrap();
1015 }
1016 let body = parse_query::<_, _>(
1017 &input_url,
1018 &output_url,
1019 data.get().into_iter(),
1020 self.default_limit,
1021 );
1022 match body {
1023 Ok(rs) => ResponseTemplate::new(200).set_body_json(rs.mock_json()),
1024 Err(e) => {
1025 debug!("Failed to respond to {}: {}", request.url, e);
1026 ResponseTemplate::new(500).set_body_string(e.to_string())
1027 }
1028 }
1029 }
1030}
1031
1032impl<'q, T> ResponseSet<&T> {
1035 pub fn mock_json_with_context<A>(&self, access: A) -> serde_json::Value
1036 where
1037 T: IntoRowWithContext<'q, A>,
1038 A: 'q,
1039 {
1040 let mut map = serde_json::map::Map::new();
1041 map.insert(
1042 "count".to_string(),
1043 serde_json::Value::Number(serde_json::Number::from(self.total_matches)),
1044 );
1045 map.insert(
1046 "results".to_string(),
1047 to_rows_with_context(&self.contents, access),
1048 );
1049 map.insert(
1050 "next".to_string(),
1051 self.next
1052 .as_ref()
1053 .map(|x| serde_json::Value::String(x.clone()))
1054 .unwrap_or(serde_json::Value::Null),
1055 );
1056 map.insert(
1057 "previous".to_string(),
1058 self.prev
1059 .as_ref()
1060 .map(|x| serde_json::Value::String(x.clone()))
1061 .unwrap_or(serde_json::Value::Null),
1062 );
1063
1064 serde_json::Value::Object(map)
1065 }
1066}
1067
1068fn to_rows_with_context<'q, T: IntoRowWithContext<'q, A>, A: 'q>(
1069 data: &[&T],
1070 access: A,
1071) -> serde_json::Value {
1072 let mut array = Vec::new();
1073 let ser = T::get_serializer(access);
1074 for item in data {
1075 array.push(ser.to_json(item));
1076 }
1077 serde_json::Value::Array(array)
1078}
1079
1080fn parse_query_with_context<'a, 'q, R, I, A>(
1081 input_url: &Url,
1082 output_url: &Url,
1083 access: A,
1084 iter: I,
1085 default_limit: Option<usize>,
1086) -> Result<ResponseSet<&'a R>, MockError>
1087where
1088 R: FilterableWithContext<'q, A> + SortableWithContext<'q, A> + IntoRowWithContext<'q, A> + 'q,
1090 I: Iterator<Item = &'a R>,
1091 A: Clone + 'q,
1092{
1093 let mut rb = ResponseSetBuilder::new(default_limit);
1094 let qr = OperatorSetWithContext::<R, A>::new(access.clone());
1095 let sr = OrderingSetWithContext::<R, A>::new(access);
1096 let pairs = input_url.query_pairs();
1097 for (key, value) in pairs {
1098 match key.as_ref() {
1099 "ordering" => {
1100 rb.order_by(sr.create_sort(&*value)?);
1101 }
1102 "offset" => {
1103 let v = usize::from_str(value.as_ref())?;
1104 rb.offset(v);
1105 }
1106 "limit" => {
1107 let v = usize::from_str(value.as_ref())?;
1108 rb.limit(v);
1109 }
1110 _ => {
1111 if let Ok(filter) = qr.create_filter_from_query_pair(&key, &value) {
1112 rb.filter_by(filter);
1113 continue;
1114 }
1115 return Err(MockError::UnknownQueryParameter(String::from(key.as_ref())));
1116 }
1117 }
1118 }
1119 let response = rb.apply(iter);
1120 Ok(ResponseSet::new(
1121 response.data,
1122 response.total,
1123 response
1124 .next
1125 .map(|page| make_page_url(output_url, page.offset, page.limit).to_string()),
1126 response
1127 .prev
1128 .map(|page| make_page_url(output_url, page.offset, page.limit).to_string()),
1129 ))
1130}
1131
1132#[cfg_attr(docsrs, doc(cfg(feature = "wiremock")))]
1148pub struct EndpointWithContext<T>
1149where
1150 T: Send + Sync + RowSource,
1151 for<'t> &'t <<T as RowSource>::Rows as RowSet>::Target:
1152 IntoIterator<Item = &'t <T as RowSource>::Item>,
1153{
1154 row_source: T,
1155 base_uri: Option<Url>,
1156 default_limit: Option<usize>,
1157}
1158
1159impl<'q, T> EndpointWithContext<T>
1160where
1161 T: Send + Sync + RowSource,
1162 for<'t> &'t <<T as RowSource>::Rows as RowSet>::Target:
1163 IntoIterator<Item = &'t <T as RowSource>::Item>,
1164 {
1166 pub fn new(row_source: T, base_uri: Option<&str>) -> Self {
1180 Self {
1181 row_source,
1182 base_uri: base_uri.map(|x| Url::parse(x).unwrap()),
1183 default_limit: None,
1184 }
1185 }
1186
1187 pub fn default_limit(&mut self, limit: usize) {
1195 self.default_limit = Some(limit);
1196 }
1197}
1198
1199impl<'q, T, A, R> Respond for EndpointWithContext<T>
1200where
1201 T: Send + Sync + RowSource<Rows = A, Item = R>,
1202 for<'t> &'t <A as RowSet>::Target: IntoIterator<Item = &'t R>,
1203 R: Send
1205 + Sync
1206 + FilterableWithContext<'q, A>
1207 + SortableWithContext<'q, A>
1208 + IntoRowWithContext<'q, A>
1209 + 'q,
1210 A: Clone + 'q + RowSet,
1211{
1212 fn respond(&self, request: &Request) -> ResponseTemplate {
1213 let mut u = request.url.clone();
1216 if let Some(ref base) = self.base_uri {
1217 u.set_host(base.host_str()).unwrap();
1218 u.set_scheme(base.scheme()).unwrap();
1219 u.set_port(base.port()).unwrap();
1220 }
1221 let res = {
1222 let access = self.row_source.get();
1223 let res = {
1224 let into_iter = access.clone();
1225 let rows = into_iter.get().into_iter();
1226 let body = parse_query_with_context(
1227 &request.url,
1228 &u,
1229 access.clone(),
1230 rows,
1231 self.default_limit,
1232 );
1233 match body {
1234 Ok(rs) => {
1235 let bb = rs.mock_json_with_context(access);
1236 ResponseTemplate::new(200).set_body_json(bb)
1237 }
1238 Err(e) => {
1239 debug!("Failed to respond to {}: {}", request.url, e);
1240 ResponseTemplate::new(500).set_body_string(e.to_string())
1241 }
1242 }
1243 };
1244 res
1245 };
1246 res
1247 }
1248}
1249
1250#[cfg_attr(docsrs, doc(cfg(feature = "wiremock")))]
1272pub struct NestedEndpointWithContext<T>
1273where
1274 T: Send + Sync + RowSource,
1275 for<'t> &'t <<T as RowSource>::Rows as RowSet>::Target:
1276 IntoIterator<Item = &'t <T as RowSource>::Item>,
1277{
1278 transform: UrlTransform,
1279 row_source: T,
1280 base_uri: Option<Url>,
1281 default_limit: Option<usize>,
1282}
1283
1284impl<'q, T> NestedEndpointWithContext<T>
1285where
1286 T: Send + Sync + RowSource,
1287 for<'t> &'t <<T as RowSource>::Rows as RowSet>::Target:
1288 IntoIterator<Item = &'t <T as RowSource>::Item>,
1289 {
1291 pub fn new(row_source: T, p: NestedEndpointParams<'_>) -> Self {
1296 Self {
1297 transform: UrlTransform::new(
1298 &format!(r"^{}/{}/(?P<parent>[^/]+)/{}/$", p.root, p.parent, p.child),
1299 &format!("{}/{}/", p.root, p.child),
1300 vec![(&*p.parent_query, "${parent}")],
1301 ),
1302 row_source,
1303 base_uri: p.base_uri.map(|x| Url::parse(x).unwrap()),
1304 default_limit: None,
1305 }
1306 }
1307
1308 pub fn default_limit(&mut self, limit: usize) {
1309 self.default_limit = Some(limit);
1310 }
1311}
1312
1313impl<'q, T, A, R> Respond for NestedEndpointWithContext<T>
1314where
1315 T: Send + Sync + RowSource<Rows = A, Item = R>,
1316 for<'t> &'t <A as RowSet>::Target: IntoIterator<Item = &'t R>,
1317 R: Send
1319 + Sync
1320 + FilterableWithContext<'q, A>
1321 + SortableWithContext<'q, A>
1322 + IntoRowWithContext<'q, A>
1323 + 'q,
1324 A: Clone + 'q + RowSet,
1325{
1326 fn respond(&self, request: &Request) -> ResponseTemplate {
1327 trace!("Request URL: {}", request.url);
1328 let input_url = self.transform.transform(&request.url);
1329 trace!("Transformed URL: {}", input_url);
1330
1331 let mut output_url = request.url.clone();
1332 if let Some(ref base) = self.base_uri {
1333 output_url.set_host(base.host_str()).unwrap();
1334 output_url.set_scheme(base.scheme()).unwrap();
1335 output_url.set_port(base.port()).unwrap();
1336 }
1337
1338 let res = {
1339 let access = self.row_source.get();
1340 let res = {
1341 let into_iter = access.clone();
1342 let rows = into_iter.get().into_iter();
1343 let body = parse_query_with_context(
1344 &input_url,
1345 &output_url,
1346 access.clone(),
1347 rows,
1348 self.default_limit,
1349 );
1350 match body {
1351 Ok(rs) => {
1352 let bb = rs.mock_json_with_context(access);
1353 ResponseTemplate::new(200).set_body_json(bb)
1354 }
1355 Err(e) => {
1356 debug!("Failed to respond to {}: {}", request.url, e);
1357 ResponseTemplate::new(500).set_body_string(e.to_string())
1358 }
1359 }
1360 };
1361 res
1362 };
1363 res
1364 }
1365}
1366
1367#[cfg(test)]
1368mod tests {
1369 use super::*;
1370
1371 use test_log::test;
1372 use wiremock::http::Url;
1373
1374 #[test]
1375 fn test_transform() {
1376 let u = Url::parse("http://foo.bar/jobs/3235/tests/?name=womble&path=bongle")
1377 .expect("failed to parse url");
1378 let t = UrlTransform::new(r"^/jobs/(\d+)/tests/$", r"/tests/$1", vec![("job", "$1")]);
1379 let v = t.transform(&u);
1380 assert_eq!(
1381 v,
1382 Url::parse("http://foo.bar/tests/3235?name=womble&path=bongle&job=3235")
1383 .expect("failed to parse url")
1384 );
1385
1386 let u = Url::parse("http://foo.bar/jobs/hello/tests/?name=womble&path=bongle")
1387 .expect("failed to parse url");
1388 let v = t.transform(&u);
1389 assert_eq!(v, u);
1390
1391 let u = Url::parse(
1392 "http://foo.bar/jobs/snomble/bomble/tests/sniffle_snaffle?name=womble&path=bongle",
1393 )
1394 .expect("failed to parse url");
1395 let t = UrlTransform::new(
1396 r"^/jobs/(?P<name>.*)/tests/(?P<womble>.*)$",
1397 r"/tests/${name}",
1398 vec![("job", "$womble")],
1399 );
1400 let v = t.transform(&u);
1401 assert_eq!(
1402 v,
1403 Url::parse(
1404 "http://foo.bar/tests/snomble/bomble?name=womble&path=bongle&job=sniffle_snaffle"
1405 )
1406 .expect("failed to parse url")
1407 );
1408
1409 let u = Url::parse("http://foo.bar/jobs/hello/tests/?name=womble&path=bongle")
1410 .expect("failed to parse url");
1411 let v = t.transform(&u);
1412 assert_eq!(
1413 v,
1414 Url::parse("http://foo.bar/tests/hello?name=womble&path=bongle&job=")
1415 .expect("failed to parse url")
1416 );
1417 }
1418}