Skip to main content

modkit_sdk/
odata.rs

1//! Typed `OData` query builder - re-exported from `modkit-odata`
2//!
3//! This module re-exports the canonical `OData` query building functionality from `modkit-odata`,
4//! along with SDK-specific streaming utilities for cursor-based pagination.
5//!
6//! The SDK re-exports the canonical `QueryBuilder` from `modkit-odata`.
7//! Streaming adapters are provided as free functions `pages_stream` and `items_stream`.
8//!
9//! # Example
10//!
11//! ```rust,ignore
12//! use modkit_sdk::odata::{items_stream, pages_stream, FieldRef, QueryBuilder, Schema};
13//! use modkit_odata::SortDir;
14//!
15//! #[derive(Copy, Clone, Eq, PartialEq)]
16//! enum UserField {
17//!     Id,
18//!     Name,
19//!     Email,
20//! }
21//!
22//! struct UserSchema;
23//!
24//! impl Schema for UserSchema {
25//!     type Field = UserField;
26//!
27//!     fn field_name(field: Self::Field) -> &'static str {
28//!         match field {
29//!             UserField::Id => "id",
30//!             UserField::Name => "name",
31//!             UserField::Email => "email",
32//!         }
33//!     }
34//! }
35//!
36//! // Define typed field references
37//! const ID: FieldRef<UserSchema, uuid::Uuid> = FieldRef::new(UserField::Id);
38//! const NAME: FieldRef<UserSchema, String> = FieldRef::new(UserField::Name);
39//!
40//! // Build a query
41//! let user_id = uuid::Uuid::new_v4();
42//! let query = QueryBuilder::<UserSchema>::new()
43//!     .filter(ID.eq(user_id).and(NAME.contains("john")))
44//!     .order_by(NAME, SortDir::Asc)
45//!     .page_size(50)
46//!     .build();
47//!
48//! // Stream pages
49//! let pages = pages_stream(
50//!     QueryBuilder::<UserSchema>::new()
51//!         .filter(ID.eq(user_id).and(NAME.contains("john")))
52//!         .page_size(50),
53//!     |q| async move { client.list_users(q).await },
54//! );
55//!
56//! // Stream items
57//! let items = items_stream(
58//!     QueryBuilder::<UserSchema>::new()
59//!         .filter(ID.eq(user_id).and(NAME.contains("john")))
60//!         .page_size(50),
61//!     |q| async move { client.list_users(q).await },
62//! );
63//! ```
64
65pub use modkit_odata::ODataQuery;
66
67// Re-export core OData types from modkit-odata (the canonical source)
68pub use modkit_odata::QueryBuilder;
69pub use modkit_odata::schema::{AsFieldKey, AsFieldName, FieldRef, IntoODataValue, Schema};
70use std::future::Future;
71use std::pin::Pin;
72
73/// Boxed future for `OData` page fetchers.
74pub type BoxedODataFuture<'a, T, E> =
75    Pin<Box<dyn Future<Output = Result<modkit_odata::Page<T>, E>> + Send + 'a>>;
76
77/// Boxed fetcher for `OData` pagination (accepts an `ODataQuery` and returns a boxed future).
78pub type BoxedODataFetcher<'a, T, E> =
79    Box<dyn FnMut(ODataQuery) -> BoxedODataFuture<'a, T, E> + Send + 'a>;
80
81/// Named stream type produced by `items_stream` when using boxed fetchers.
82pub type ItemsStream<'a, T, E> =
83    crate::pager::CursorPager<T, E, BoxedODataFetcher<'a, T, E>, BoxedODataFuture<'a, T, E>>;
84
85/// Create a stream of pages using cursor pagination.
86///
87/// This consumes the builder, builds `ODataQuery`, then returns a `PagesPager`.
88pub fn pages_stream<S, T, E, F, Fut>(
89    builder: QueryBuilder<S>,
90    fetcher: F,
91) -> crate::pager::PagesPager<T, E, F, Fut>
92where
93    S: Schema,
94    F: FnMut(ODataQuery) -> Fut,
95    Fut: std::future::Future<Output = Result<modkit_odata::Page<T>, E>>,
96{
97    let query = builder.build();
98    crate::pager::PagesPager::new(query, fetcher)
99}
100
101/// Create a stream of items using cursor pagination.
102///
103/// This consumes the builder, builds `ODataQuery`, then returns a `CursorPager`.
104pub fn items_stream<S, T, E, F, Fut>(
105    builder: QueryBuilder<S>,
106    fetcher: F,
107) -> crate::pager::CursorPager<T, E, F, Fut>
108where
109    S: Schema,
110    F: FnMut(ODataQuery) -> Fut,
111    Fut: std::future::Future<Output = Result<modkit_odata::Page<T>, E>>,
112{
113    let query = builder.build();
114    crate::pager::CursorPager::new(query, fetcher)
115}
116
117/// Create a boxed stream of pages using cursor pagination.
118///
119/// This helper mirrors `pages_stream` but accepts a boxed fetcher and returns the boxed pager type.
120///
121/// # Example
122/// ```rust,ignore
123/// use modkit_sdk::odata::{pages_stream_boxed, QueryBuilder};
124/// use std::pin::Pin;
125///
126/// let stream = pages_stream_boxed(
127///     QueryBuilder::<UserSchema>::new().page_size(50),
128///     Box::new(|q| Box::pin(async move { service.list_users_page(&ctx, &q).await })),
129/// );
130/// ```
131#[must_use]
132pub fn pages_stream_boxed<S, T, E>(
133    builder: QueryBuilder<S>,
134    fetcher: BoxedODataFetcher<'_, T, E>,
135) -> crate::pager::PagesPager<T, E, BoxedODataFetcher<'_, T, E>, BoxedODataFuture<'_, T, E>>
136where
137    S: Schema,
138{
139    let query = builder.build();
140    crate::pager::PagesPager::new(query, fetcher)
141}
142
143/// Create a boxed stream of items using cursor pagination.
144///
145/// This helper mirrors `items_stream` but accepts a boxed fetcher and returns the named `ItemsStream` alias.
146///
147/// # Example
148/// ```rust,ignore
149/// use modkit_sdk::odata::{items_stream_boxed, QueryBuilder};
150///
151/// let stream = items_stream_boxed(
152///     QueryBuilder::<UserSchema>::new().page_size(50),
153///     Box::new(|q| Box::pin(async move { service.list_users_page(&ctx, &q).await })),
154/// );
155/// ```
156#[must_use]
157pub fn items_stream_boxed<S, T, E>(
158    builder: QueryBuilder<S>,
159    fetcher: BoxedODataFetcher<'_, T, E>,
160) -> ItemsStream<'_, T, E>
161where
162    S: Schema,
163{
164    let query = builder.build();
165    crate::pager::CursorPager::new(query, fetcher)
166}