django_query/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! # django-query
4//!
5//! This crate is a toolkit for assembling a mock instance of a
6//! Django-style API, although some of the parts may be useful beyond
7//! just mocking. The tools provided depend on the available features.
8//!
9//! There are the following features:
10//! - `"filter"` - Filtering values of a type using django filters.
11//! - `"sort"` - Sort values of a type using django orderings.
12//! - `"row"` - Convert a type into a row in tabular JSON output.
13//! - `"wiremock"` - Wiremock endpoints to expose data; implies `"filter"`, `"row"` and `"sort"`.
14#![cfg_attr(
15    feature = "persian-rug",
16    doc = r##"
17 - `"persian-rug"` - Support for types built with the [`persian-rug`](::persian_rug) crate.
18"##
19)]
20#![cfg_attr(
21    not(feature = "persian-rug"),
22    doc = r##"
23 - `"persian-rug"` - Support for types built with the `persian-rug` crate.
24"##
25)]
26#![cfg_attr(
27    feature = "clone-replace",
28    doc = r##"
29 - `"clone-replace"` - Use a [`CloneReplace`](clone_replace::CloneReplace) from the [`clone-replace`](::clone_replace) crate to provide data to mocks.
30"##
31)]
32#![cfg_attr(
33    not(feature = "clone-replace"),
34    doc = r##"
35 - `"clone-replace"` - Use a `CloneReplace` from the `clone-replace` crate to provide data to mocks.
36"##
37)]
38//!
39#![cfg_attr(
40    feature = "filter",
41    doc = r##"
42## Filtering
43
44The [`Filterable`](filtering::Filterable) trait, and its derive macro,
45which allow you to use attribute markup to automatically parse
46Django-style filter URLs into filter objects, when the `"filter"`
47feature is enabled.
48
49Example:
50```rust
51use django_query::filtering::{Filterable, OperatorSet};
52
53#[derive(Filterable)]
54struct Foo {
55    #[django(op(lt, gt))]
56    a: i32
57}
58
59let os = OperatorSet::<Foo>::new();
60let filter = os.create_filter_from_query("a__lt=4").unwrap();
61assert!(filter.filter_one(&Foo { a: 3}));
62```
63
64"##
65)]
66#![cfg_attr(
67    feature = "sort",
68    doc = r##"
69## Sorting
70
71The [`Sortable`](sorting::Sortable) trait, and its derive macro, which
72allow you to use attribute markup to automatically parse Django-style
73ordering URLs into sorter objects, when the `"sort"` feature is
74enabled.
75
76Example:
77```rust
78use core::cmp::Ordering;
79use django_query::sorting::{OrderingSet, Sortable};
80
81#[derive(Sortable)]
82struct Foo {
83    #[django(sort)]
84    a: i32
85}
86
87let os = OrderingSet::<Foo>::new();
88let sort = os.create_sort("-a").unwrap();
89assert_eq!(sort.compare(&Foo { a: 3}, &Foo {a: 4}), Ordering::Greater);
90```
91
92"##
93)]
94#![cfg_attr(
95    feature = "row",
96    doc = r##"
97
98## Tabular Output
99
100The [`IntoRow`](row::IntoRow) trait, and its derive macro, which allow
101you to create Django-style JSON responses for your type; note that
102this isn't just standard serialization because complex objects are
103replaced by one of their fields, which functions as a foreign
104key. This is available when the `"row"` feature is enabled.
105
106Example:
107```rust
108use django_query::row::{IntoRow, Serializer};
109use serde_json::json;
110
111#[derive(IntoRow)]
112struct Foo {
113    a: Vec<i32>
114}
115
116let ser = Foo::get_serializer();
117let f = Foo { a: vec![2, 3]};
118assert_eq!(ser.to_json(&f), json! {
119  { "a": [2, 3] }
120});
121```
122
123"##
124)]
125#![cfg_attr(
126    feature = "wiremock",
127    doc = r##"
128
129## Mock Endpoints
130
131The [`Endpoint`](mock::Endpoint) type, which implements
132[`wiremock::Respond`], and can provide a mock endpoint for a
133collection of objects whose type implements the preceding three
134traits. This is available when the `"wiremock"` feature is enabled.
135
136Example:
137```rust
138use django_query::{filtering::Filterable, mock::Endpoint, row::IntoRow, sorting::Sortable};
139use wiremock::{Mock, MockServer, matchers, http::Url};
140
141#[derive(Clone, IntoRow, Filterable, Sortable)]
142struct Foo {
143    name: String,
144}
145
146# tokio_test::block_on( async {
147let server = MockServer::start().await;
148let foos = vec![
149  Foo { name: "foo1".to_string() },
150  Foo { name: "foo2".to_string() }
151];
152
153Mock::given(matchers::method("GET"))
154     .respond_with(Endpoint::new(foos, Some(&server.uri())))
155     .mount(&server)
156     .await;
157
158let url = Url::parse(&server.uri()).expect("failed to parse MockServer URL");
159
160let body: serde_json::Value = reqwest::get(url)
161    .await
162    .expect("error getting response")
163    .json()
164    .await
165    .expect("error parsing response");
166
167assert_eq!(body, serde_json::json!{
168  {
169    "count": 2,
170    "next": null,
171    "previous": null,
172    "results": [
173      { "name": "foo1" },
174      { "name": "foo2" },
175    ]
176  }
177});
178
179# });
180```
181"##
182)]
183#![cfg_attr(
184    any(
185        feature = "row",
186        feature = "filter",
187        feature = "sort",
188        feature = "wiremock"
189    ),
190    doc = r##"
191
192## Context support
193
194There is support throughout this crate for types that require some
195context value in order to perform processing with them. Each module
196has an additional entry point with context support:
197"##
198)]
199#![cfg_attr(
200    feature = "filter",
201    doc = r##"
202- [`FilterableWithContext`](filtering::FilterableWithContext) in [`filtering`].
203"##
204)]
205#![cfg_attr(
206    feature = "sort",
207    doc = r##"
208- [`SortableWithContext`](sorting::SortableWithContext) in [`sorting`].
209"##
210)]
211#![cfg_attr(
212    feature = "row",
213    doc = r##"
214- [`IntoRowWithContext`](row::IntoRowWithContext) in [`row`].
215"##
216)]
217#![cfg_attr(
218    feature = "wiremock",
219    doc = r##"
220- [`EndpointWithContext`](mock::EndpointWithContext`) in [`mock`].
221"##
222)]
223#![cfg_attr(
224    any(
225        feature = "row",
226        feature = "filter",
227        feature = "sort",
228        feature = "wiremock"
229    ),
230    doc = r##"
231
232The context support makes no more than basic assumptions about what the
233context value is, or how it behaves. 
234"##
235)]
236#![cfg_attr(
237    any(feature = "row", feature = "filter", feature = "sort"),
238    doc = r##"
239However, corresponding
240derive macros for the traits are not provided, for the same reason.
241"##
242)]
243#![cfg_attr(
244    all(
245        feature = "persian-rug",
246        any(feature = "row", feature = "filter", feature = "sort")
247    ),
248    doc = r##"
249
250The `"persian-rug"` feature provides derive macros for using a [`persian_rug::Context`][::persian_rug::Context]:
251for types that require it:"##
252)]
253#![cfg_attr(
254    all(feature = "persian-rug", feature = "filter"),
255    doc = r##"
256- [`FilterableWithPersianRug`](filtering::FilterableWithPersianRug),
257"##
258)]
259#![cfg_attr(
260    all(feature = "persian-rug", feature = "sort"),
261    doc = r##"
262- [`SortableWithPersianRug`](sorting::SortableWithPersianRug)
263"##
264)]
265#![cfg_attr(
266    all(feature = "persian-rug", feature = "sort"),
267    doc = r##"
268- [`IntoRowWithPersianRug`](row::IntoRowWithPersianRug).
269"##
270)]
271#![cfg_attr(
272    all(
273        feature = "persian-rug",
274        any(feature = "row", feature = "filter", feature = "sort")
275    ),
276    doc = r##"
277
278Note that there is no corresponding `PersianRug` trait, only a derive
279macro which produces an implementation for the generic (`WithContext`)
280trait.
281"##
282)]
283#![cfg_attr(
284    all(feature = "clone-replace", feature = "wiremock"),
285    doc = r##"
286
287## Mutable collection support
288
289Ideally, the data served by the mock endpoints using this crate should
290be mutable, so that testing can examine how code reacts as the data
291store evolves. This crate uses a [`RowSource`](mock::RowSource) trait to
292represent something that:
293- Can be locked or queried to obtain a fixed view of the data.
294- For which references to that retrieved fixed view are iterable.
295
296This probably cannot be fully abstracted in stable Rust as it stands
297at the moment of writing, because of the lack of generic associated
298types.
299
300The standard [`RowSource`](mock::RowSource) implementations are for
301[`Arc<Vec<T>>`](::std::sync::Arc) and [`Vec<T>`], which clone
302themselves on each request (the latter being provided because it is
303convenient for small tests, even though it is expensive). Neither
304permits mutability.
305
306However, if the `"clone-replace"` feature is enabled, a
307[`CloneReplace`](::clone_replace::CloneReplace) from the
308[`clone-replace`](::clone_replace) crate can be used as a
309[`RowSource`](mock::RowSource).  Note that in addition to the
310implementation for
311[`CloneReplace<Vec<T>>`](::clone_replace::CloneReplace), there is also
312a
313[`CloneReplaceFieldSource`](mock::clone_replace::CloneReplaceFieldSource)
314which allows you to extract a [`Vec`] field from a structure which is
315entirely contained within a
316[`CloneReplace`](::clone_replace::CloneReplace).
317
318"##
319)]
320#![cfg_attr(
321    all(
322        feature = "clone-replace",
323        feature = "wiremock",
324        feature = "persian-rug"
325    ),
326    doc = r##"
327
328If the `"persian-rug"` feature is enabled, then in combination with
329`"clone-replace"` it allows you to use individual tables from a
330[`persian_rug::Context`](::persian_rug::Context) wrapped in a
331[`CloneReplace`](::clone_replace::CloneReplace) as data sources via
332[`CloneReplacePersianRugTableSource`](mock::clone_replace::persian_rug::CloneReplacePersianRugTableSource).
333
334"##
335)]
336
337#[cfg(feature = "filter")]
338#[cfg_attr(docsrs, doc(cfg(feature = "filter")))]
339pub mod filtering;
340
341#[cfg(feature = "wiremock")]
342#[cfg_attr(docsrs, doc(cfg(feature = "wiremock")))]
343pub mod mock;
344
345#[cfg(feature = "sort")]
346#[cfg_attr(docsrs, doc(cfg(feature = "sort")))]
347pub mod sorting;
348
349#[cfg(feature = "row")]
350#[cfg_attr(docsrs, doc(cfg(feature = "row")))]
351pub mod row;
352
353#[cfg(feature = "persian-rug")]
354#[doc(hidden)]
355pub mod persian_rug;