Expand description

django-query

This crate is a toolkit for assembling a mock instance of a Django-style API, although some of the parts may be useful beyond just mocking. The main tools provided are:

  • The Filterable trait, and its derive macro, which allow you to use attribute markup to automatically parse Django-style filter URLs into filter objects.

  • The Sortable trait, and its derive macro, which allow you to use attribute markup to automatically parse Django-style ordering URLs into sorter objects.

  • The IntoRow trait, and its derive macro, which allow you to create Django-style JSON responses for your type; note that this isn’t just standard serialization because complex objects are replaced by one of their fields, which functions as a foreign key.

  • The mock::Endpoint type, which implements wiremock::Respond, and can provide a mock endpoint for a collection of objects whose type implements the preceding three traits.

Example:

use django_query::{IntoRow, Filterable, Sortable, mock::Endpoint};
use std::sync::Arc;
use wiremock::{Mock, MockServer, matchers, http::Url};

#[derive(IntoRow, Filterable, Sortable)]
struct Foo {
    #[django(sort, op(in, icontains, iexact))]
    name: String,
    #[django(sort, op(lt, gt))]
    value: i32
}

#[derive(IntoRow, Filterable, Sortable)]
struct Bar {
    #[django(op(icontains, startswith))]
    names: Vec<String>,
    #[django(traverse, foreign_key="name")]
    foo: Arc<Foo>
}

tokio_test::block_on( async {
   let server = MockServer::start().await;
   let bars = Arc::new(vec![
       Bar {
         names: vec! [
           "apple".to_string(),
           "banana".to_string()
         ],
         foo: Arc::new(Foo {
           name: "foo1".to_string(),
           value: 5,
         }),
       },
       Bar {
         names: vec! [
           "carrot".to_string(),
         ],
         foo: Arc::new(Foo {
           name: "foo2".to_string(),
           value: 4,
         }),
      },
   ]);

   Mock::given(matchers::method("GET"))
        .respond_with(Endpoint::new(bars, Some(&server.uri())))
        .mount(&server)
        .await;

   // Let's build up a Django-style query URL to test; first the
   // mock server's base URI
   let mut url = Url::parse(&server.uri())
       .expect("failed to parse MockServer URL");
   // Now request all only Bars that have a name in names that contains
   // "PL" (case insensitive)
   url.query_pairs_mut().append_pair("names__icontains", "PL");
   // Request only Bars whose foo has a value greater than 4
   url.query_pairs_mut().append_pair("foo__value__gt", "4");
   let body: serde_json::Value = reqwest::get(url)
       .await
       .expect("error getting response")
       .json()
       .await
       .expect("error parsing response");

   assert_eq!(body, serde_json::json!{
     {
       "count": 1,
       "next": null,
       "prev": null,
       "results": [
         { "names": ["apple", "banana"], "foo": "foo1" },
       ]
     }
   });
});

Re-exports

pub use crate::filtering::Filterable;
pub use crate::filtering::OperatorSet;
pub use crate::operators::Scalar;
pub use crate::ordering::OrderingSet;
pub use crate::ordering::Sortable;
pub use crate::row::IntoCellValue;
pub use crate::row::IntoRow;
pub use crate::row::StringCellValue;

Modules

Create filters for Rust objects from query URLs.

Create Django-style endpoints using wiremock.

Standard operators from Django for use with filtering.

Create sort orders for Rust objects from query URLs.

Convert Rust structs with named fields into tables of output.

Derive Macros

Derive the Filterable trait, creating suitable FilterClass types.

Derive the IntoRow trait, determining the display of nested objects.

Derive the Sortable trait, creating suitable SorterClass types.