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}