dtrpg_sdk/library/models.rs
1//! Library resource types for the DriveThruRPG SDK.
2//!
3//! This module provides Rust model types that mirror the API-defined schemas for library
4//! resources: ordered products, product files, product lists, and associated pagination
5//! and metadata structures.
6//!
7//! All response types implement [`serde::Deserialize`] (and [`serde::Serialize`]) so they
8//! can be decoded directly from JSON returned by the DriveThruRPG API. Field names follow
9//! the API contract and are mapped from camelCase JSON to snake_case Rust conventions via
10//! `#[serde(rename = "...")]` attributes.
11//!
12//! Query parameter structs ([`LibraryItemsParams`], [`PageParams`]) are plain Rust structs
13//! with no serde requirement; they are consumed by [`LibraryClient`] methods to build
14//! URL query strings.
15//!
16//! [`LibraryClient`]: crate::LibraryClient
17
18use serde::{Deserialize, Deserializer, Serialize};
19
20/// Deserializes a JSON null or missing field as the type's default value.
21///
22/// Use via `#[serde(default, deserialize_with = "null_as_default")]` on fields that the
23/// API may send as `null` but that should be treated as empty collections or zero values.
24fn null_as_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
25where
26 T: Default + Deserialize<'de>,
27 D: Deserializer<'de>,
28{
29 Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
30}
31
32// ── Pagination ────────────────────────────────────────────────────────────────
33
34/// Pagination links included in all paginated API responses.
35///
36/// The `self_` field corresponds to the JSON key `"self"`, which is a Rust reserved
37/// keyword and is therefore renamed at the struct level via `#[serde(rename = "self")]`.
38#[derive(Clone, Debug, Deserialize, Serialize)]
39pub struct PaginationLinks {
40 /// The canonical URL for the current page of results.
41 #[serde(rename = "self")]
42 pub self_: String,
43 /// URL for the first page of results, if available.
44 pub first: Option<String>,
45 /// URL for the last page of results, if available.
46 pub last: Option<String>,
47 /// URL for the previous page of results, if available.
48 pub prev: Option<String>,
49 /// URL for the next page of results, if available.
50 pub next: Option<String>,
51}
52
53/// Pagination metadata included in all paginated API responses.
54#[derive(Clone, Debug, Deserialize, Serialize)]
55pub struct PaginationMeta {
56 /// The number of items returned per page.
57 #[serde(rename = "itemsPerPage")]
58 pub items_per_page: u32,
59 /// The current page number (1-based).
60 #[serde(rename = "currentPage")]
61 pub current_page: u32,
62}
63
64// ── File / Checksum ───────────────────────────────────────────────────────────
65
66/// Checksum information for a single downloadable product file.
67#[derive(Clone, Debug, Deserialize, Serialize)]
68pub struct FileChecksum {
69 /// The checksum hash string for the file.
70 pub checksum: String,
71 /// The date when the checksum was generated (ISO 8601 string).
72 #[serde(rename = "checksumDate")]
73 pub checksum_date: String,
74}
75
76/// A downloadable file associated with an ordered product.
77#[derive(Clone, Debug, Deserialize, Serialize)]
78pub struct OrderProductFile {
79 /// The index of this file within the ordered product's file list.
80 pub index: u32,
81 /// The unique identifier for this specific download record.
82 #[serde(rename = "orderProductDownloadId")]
83 pub order_product_download_id: u64,
84 /// The display title of the file.
85 pub title: String,
86 /// The filename as it will appear when downloaded.
87 pub filename: String,
88 /// The file size in bytes.
89 pub size: u64,
90 /// The file size expressed in megabytes as a formatted string.
91 #[serde(rename = "sizeMB")]
92 pub size_mb: String,
93 /// Checksums available for verifying the integrity of the downloaded file.
94 ///
95 /// The API may return `null` for products without checksum data; treated as empty.
96 #[serde(default, deserialize_with = "null_as_default")]
97 pub checksums: Vec<FileChecksum>,
98}
99
100// ── Filters / History / Attributes ───────────────────────────────────────────
101
102/// A filter category associated with an ordered product.
103///
104/// Populated when `getFilters=1` is included in the request.
105#[derive(Clone, Debug, Deserialize, Serialize)]
106pub struct OrderProductFilter {
107 /// The unique identifier of this filter category.
108 #[serde(rename = "filterId")]
109 pub filter_id: u64,
110 /// The unique identifier of this filter's parent category.
111 #[serde(rename = "parentFilterId")]
112 pub parent_filter_id: u64,
113 /// The display name of this filter category.
114 pub name: String,
115 /// The display name of this filter's parent category.
116 #[serde(rename = "parentName")]
117 pub parent_name: String,
118}
119
120/// A single history entry recording a change made to an ordered product.
121#[derive(Clone, Debug, Deserialize, Serialize)]
122pub struct OrderProductHistoryEntry {
123 /// The date and time when the change occurred (ISO 8601 string).
124 pub changed: String,
125 /// A human-readable description of what changed.
126 pub changes: String,
127}
128
129/// An individual attribute option associated with an ordered product.
130///
131/// Attributes describe purchase options such as format or edition.
132#[derive(Clone, Debug, Deserialize, Serialize)]
133pub struct OrderProductAttribute {
134 /// The unique identifier of the order this attribute belongs to.
135 #[serde(rename = "orderId")]
136 pub order_id: u64,
137 /// The name of the option (e.g., `"Format"`).
138 #[serde(rename = "optionName")]
139 pub option_name: String,
140 /// The display name of the selected option value (e.g., `"PDF"`).
141 #[serde(rename = "optionValueName")]
142 pub option_value_name: String,
143 /// The price associated with this option, as a formatted string.
144 pub price: String,
145 /// A prefix to display before the price (e.g., `"$"`).
146 #[serde(rename = "pricePrefix")]
147 pub price_prefix: String,
148 /// The unique identifier for the selected option value.
149 #[serde(rename = "optionValueId")]
150 pub option_value_id: u64,
151 /// The type classification of this option.
152 #[serde(rename = "optionType")]
153 pub option_type: String,
154}
155
156// ── OrderProduct ──────────────────────────────────────────────────────────────
157
158/// The full attribute set for an ordered product.
159///
160/// This is the primary payload within an [`OrderProductItem`]. It includes required fields
161/// present on every ordered product as well as optional collections (filters, history,
162/// attributes) that are populated only when specifically requested.
163#[derive(Clone, Debug, Deserialize, Serialize)]
164pub struct OrderProductAttributes {
165 /// The unique identifier of the order this product belongs to.
166 #[serde(rename = "orderId")]
167 pub order_id: u64,
168 /// The unique identifier of the product.
169 #[serde(rename = "productId")]
170 pub product_id: u64,
171 /// The publisher identifier used for royalty tracking.
172 #[serde(rename = "royaltyPublisherId")]
173 pub royalty_publisher_id: u64,
174 /// The ISBN of the product, if applicable.
175 pub isbn: Option<String>,
176 /// The display name of the product.
177 pub name: String,
178 /// The date the product was purchased (ISO 8601 string), if available.
179 #[serde(rename = "datePurchased")]
180 pub date_purchased: Option<String>,
181 /// The total file size in bytes, if available.
182 pub filesize: Option<u64>,
183 /// The final price paid for the product.
184 #[serde(rename = "finalPrice")]
185 pub final_price: f64,
186 /// The quantity of this product in the order.
187 pub quantity: u32,
188 /// The bundle identifier, if the product was purchased as part of a bundle.
189 #[serde(rename = "bundleId")]
190 pub bundle_id: u64,
191 /// Indicates whether the product has been archived (`1`) or not (`0`).
192 pub archived: u8,
193 /// Additional add-on information associated with this product, if any.
194 #[serde(rename = "addOnInfo")]
195 pub add_on_info: Option<String>,
196 /// The unique identifier for this order-product record.
197 #[serde(rename = "orderProductId")]
198 pub order_product_id: u64,
199 /// The unique identifier of the customer who owns this order.
200 #[serde(rename = "customerId")]
201 pub customer_id: u64,
202 /// The date the product files were last modified (ISO 8601 string), if known.
203 #[serde(rename = "fileLastModified")]
204 pub file_last_modified: Option<String>,
205 /// The date the product files were last downloaded (ISO 8601 string), if known.
206 #[serde(rename = "fileLastDownloaded")]
207 pub file_last_downloaded: Option<String>,
208 /// The list of downloadable files associated with this ordered product.
209 pub files: Vec<OrderProductFile>,
210 /// Filter categories for this product. Populated when `getFilters=1` is requested.
211 pub filters: Option<Vec<OrderProductFilter>>,
212 /// The change history for this ordered product, if requested.
213 pub history: Option<Vec<OrderProductHistoryEntry>>,
214 /// Optional attributes describing purchase options (format, edition, etc.).
215 pub attributes: Option<Vec<OrderProductAttribute>>,
216 /// Publisher metadata embedded directly on this ordered product's attributes, when the
217 /// API includes it inline (in addition to, or instead of, sideloaded `included` publisher
218 /// resources).
219 #[serde(default)]
220 pub publisher: Option<OrderProductPublisher>,
221 /// Product catalog metadata (cover images, description) embedded on this ordered product.
222 #[serde(default)]
223 pub product: Option<OrderProductInfo>,
224 /// Order summary metadata embedded on this ordered product.
225 #[serde(default)]
226 pub order: Option<OrderProductOrder>,
227}
228
229/// Publisher metadata embedded directly on an ordered product's attributes.
230#[derive(Clone, Debug, Deserialize, Serialize)]
231pub struct OrderProductPublisher {
232 /// The display name of the publisher.
233 pub name: String,
234 /// The unique identifier of the publisher.
235 #[serde(rename = "publisherId")]
236 pub publisher_id: u64,
237 /// The URL slug for the publisher's storefront page.
238 pub slug: String,
239}
240
241/// Descriptive text for a product, embedded within [`OrderProductInfo`].
242#[derive(Clone, Debug, Deserialize, Serialize)]
243pub struct OrderProductDescription {
244 /// The display name of the product.
245 pub name: String,
246 /// HTML purchase note shown to the customer, if any.
247 #[serde(rename = "purchaseNote", default)]
248 pub purchase_note: Option<String>,
249 /// The URL slug for the product's storefront page.
250 pub slug: String,
251 /// A short marketing description of the product.
252 #[serde(rename = "shortDescription", default)]
253 pub short_description: Option<String>,
254}
255
256/// Product catalog metadata embedded directly on an ordered product's attributes, including
257/// relative paths to cover images.
258///
259/// Image paths (`image`, `web_image`, `thumbnail`, `thumbnail_100`) are relative to the
260/// DriveThruRPG images base URL (`https://api.drivethrurpg.com/images/`).
261#[derive(Clone, Debug, Deserialize, Serialize)]
262pub struct OrderProductInfo {
263 /// Relative path to the full-size cover image, if available.
264 #[serde(default)]
265 pub image: Option<String>,
266 /// Relative path to the web-optimized (WebP) cover image, if available.
267 #[serde(rename = "webImage", default)]
268 pub web_image: Option<String>,
269 /// Relative path to the 140px cover thumbnail image, if available.
270 #[serde(default)]
271 pub thumbnail: Option<String>,
272 /// Relative path to the 100px cover thumbnail image, if available.
273 #[serde(rename = "thumbnail100", default)]
274 pub thumbnail_100: Option<String>,
275 /// Bundle ID if this product is part of a bundle, otherwise 0.
276 #[serde(rename = "bundleId")]
277 pub bundle_id: u64,
278 /// Date and time when the product was added to the DTRPG catalog, if known.
279 #[serde(rename = "dateCreated", default)]
280 pub date_created: Option<String>,
281 /// Unique identifier for the product in the DTRPG catalog.
282 #[serde(rename = "productId")]
283 pub product_id: u64,
284 /// Descriptive text for the product, if requested.
285 #[serde(default)]
286 pub description: Option<OrderProductDescription>,
287 /// Total file size in megabytes, if known.
288 #[serde(default)]
289 pub filesize: Option<f64>,
290}
291
292/// Order summary metadata embedded on an ordered product's attributes.
293#[derive(Clone, Debug, Deserialize, Serialize)]
294pub struct OrderProductOrder {
295 /// Date and time when the order was created, if known.
296 #[serde(rename = "dateCreated", default)]
297 pub date_created: Option<String>,
298 /// The unique identifier of the order.
299 #[serde(rename = "orderId")]
300 pub order_id: u64,
301}
302
303/// A single item in an ordered products collection response.
304///
305/// Follows the JSON:API resource object structure with `id`, `type`, and `attributes`.
306///
307/// The live API does *not* embed `publisher`/`product`/`order` metadata directly on
308/// `attributes` for this endpoint (despite what earlier documentation examples showed) —
309/// it references them via `relationships`, resolved against the response's top-level
310/// `included` array. See [`OrderProductRelationships`] and [`IncludedItem`].
311#[derive(Clone, Debug, Deserialize, Serialize)]
312pub struct OrderProductItem {
313 /// The JSON:API resource identifier.
314 pub id: String,
315 /// The JSON:API resource type string (e.g., `"order_product"`).
316 #[serde(rename = "type")]
317 pub resource_type: String,
318 /// The full attribute set for this ordered product.
319 pub attributes: OrderProductAttributes,
320 /// JSON:API relationship references to sideloaded `Publisher`/`Product`/`Order`
321 /// resources, resolved by matching `id` against the response's `included` array.
322 #[serde(default)]
323 pub relationships: Option<OrderProductRelationships>,
324}
325
326/// JSON:API relationship references carried on an [`OrderProductItem`].
327#[derive(Clone, Debug, Deserialize, Serialize)]
328pub struct OrderProductRelationships {
329 /// Reference to the sideloaded `Publisher` resource, if present.
330 #[serde(default)]
331 pub publisher: Option<RelationshipRef>,
332 /// Reference to the sideloaded `Product` resource, if present.
333 #[serde(default)]
334 pub product: Option<RelationshipRef>,
335 /// Reference to the sideloaded `Order` resource, if present.
336 #[serde(default)]
337 pub order: Option<RelationshipRef>,
338}
339
340/// A single JSON:API relationship reference, wrapping the `data` resource identifier.
341#[derive(Clone, Debug, Deserialize, Serialize)]
342pub struct RelationshipRef {
343 /// The referenced resource's type and id, if the relationship is populated.
344 pub data: Option<RelationshipData>,
345}
346
347/// The `type`/`id` pair identifying a JSON:API resource referenced by a relationship.
348#[derive(Clone, Debug, Deserialize, Serialize)]
349pub struct RelationshipData {
350 /// The referenced resource's type string (e.g., `"Product"`).
351 #[serde(rename = "type")]
352 pub resource_type: String,
353 /// The referenced resource's id, matching an entry's `id` in the `included` array.
354 pub id: String,
355}
356
357// ── Sideloaded resources (`included`) ────────────────────────────────────────
358
359/// Attributes for a publisher resource included alongside ordered product responses.
360#[derive(Clone, Debug, Deserialize, Serialize)]
361pub struct PublisherAttributes {
362 /// The display name of the publisher.
363 #[serde(default)]
364 pub name: String,
365 /// The unique identifier of the publisher.
366 #[serde(rename = "publisherId", default)]
367 pub publisher_id: u64,
368 /// The URL slug for the publisher's storefront page.
369 #[serde(default)]
370 pub slug: String,
371}
372
373/// A publisher resource item included in ordered product responses when requested.
374///
375/// Follows the JSON:API resource object structure.
376#[derive(Clone, Debug, Deserialize, Serialize)]
377pub struct PublisherItem {
378 /// The JSON:API resource identifier.
379 pub id: String,
380 /// The JSON:API resource type string (e.g., `"publisher"`).
381 #[serde(rename = "type")]
382 pub resource_type: String,
383 /// The publisher attributes.
384 pub attributes: PublisherAttributes,
385}
386
387/// A single sideloaded resource entity from the `included` array of an ordered-products
388/// list response.
389///
390/// The `included` array mixes multiple JSON:API resource types (`Publisher`, `Product`,
391/// `Order`) in a single flat list; `resource_type` disambiguates which, and `attributes`
392/// is kept as an untyped [`serde_json::Value`] since its shape depends on `resource_type`.
393/// Decode it via [`IncludedItem::as_publisher`] or [`IncludedItem::as_product`].
394#[derive(Clone, Debug, Deserialize, Serialize)]
395pub struct IncludedItem {
396 /// The JSON:API resource identifier. Matches a [`RelationshipData::id`] referencing it.
397 pub id: String,
398 /// The JSON:API resource type string (e.g., `"Publisher"`, `"Product"`, `"Order"`).
399 #[serde(rename = "type")]
400 pub resource_type: String,
401 /// The resource's untyped attribute payload; shape depends on `resource_type`.
402 pub attributes: serde_json::Value,
403}
404
405impl IncludedItem {
406 /// Decodes `attributes` as [`PublisherAttributes`] if `resource_type == "Publisher"`.
407 ///
408 /// Returns `None` for any other resource type or if decoding fails.
409 #[must_use]
410 pub fn as_publisher(&self) -> Option<PublisherAttributes> {
411 if self.resource_type != "Publisher" {
412 return None;
413 }
414 serde_json::from_value(self.attributes.clone()).ok()
415 }
416
417 /// Decodes `attributes` as [`OrderProductInfo`] if `resource_type == "Product"`.
418 ///
419 /// Returns `None` for any other resource type or if decoding fails.
420 #[must_use]
421 pub fn as_product(&self) -> Option<OrderProductInfo> {
422 if self.resource_type != "Product" {
423 return None;
424 }
425 serde_json::from_value(self.attributes.clone()).ok()
426 }
427}
428
429// ── Response wrappers ─────────────────────────────────────────────────────────
430
431/// A paginated collection of ordered products.
432///
433/// Returned by `GET /{api_version}/order_products`.
434#[derive(Clone, Debug, Deserialize, Serialize)]
435pub struct OrderProductListResponse {
436 /// Pagination links for navigating the result set.
437 pub links: PaginationLinks,
438 /// Pagination metadata describing the current page.
439 pub meta: PaginationMeta,
440 /// The ordered product items on this page.
441 pub data: Vec<OrderProductItem>,
442 /// Publisher/Product/Order resources sideloaded alongside the ordered products.
443 pub included: Option<Vec<IncludedItem>>,
444}
445
446/// A single ordered product resource response.
447///
448/// Returned by `GET /{api_version}/order_products/{id}`.
449#[derive(Clone, Debug, Deserialize, Serialize)]
450pub struct OrderProductItemResponse {
451 /// The ordered product item.
452 pub data: OrderProductItem,
453 /// Publisher/Product/Order resources sideloaded alongside the ordered product,
454 /// resolved by matching `relationships.*.data.id` against each entry's `id`
455 /// (mirrors [`OrderProductListResponse::included`]).
456 #[serde(default)]
457 pub included: Option<Vec<IncludedItem>>,
458}
459
460// ── Product Lists ─────────────────────────────────────────────────────────────
461
462/// Attributes for a product list resource.
463#[derive(Clone, Debug, Deserialize, Serialize)]
464pub struct ProductListAttributes {
465 /// The identifier of the customer who owns this list.
466 #[serde(rename = "customerId")]
467 pub customer_id: u64,
468 /// The display name of the product list.
469 pub name: String,
470 /// The date the list was created (ISO 8601 string).
471 #[serde(rename = "dateCreated")]
472 pub date_created: String,
473 /// The unique identifier for this product list.
474 #[serde(rename = "productListId")]
475 pub product_list_id: u64,
476 /// The URL slug for this product list.
477 pub slug: String,
478 /// The number of items currently in this product list.
479 #[serde(rename = "itemCount")]
480 pub item_count: u64,
481}
482
483/// A single product list resource item.
484///
485/// Follows the JSON:API resource object structure.
486#[derive(Clone, Debug, Deserialize, Serialize)]
487pub struct ProductListItem {
488 /// The JSON:API resource identifier.
489 pub id: String,
490 /// The JSON:API resource type string (e.g., `"product_list"`).
491 #[serde(rename = "type")]
492 pub resource_type: String,
493 /// The product list attributes.
494 pub attributes: ProductListAttributes,
495}
496
497/// A paginated collection of product lists belonging to the authenticated customer.
498///
499/// Returned by `GET /{api_version}/product_lists`.
500#[derive(Clone, Debug, Deserialize, Serialize)]
501pub struct ProductListCollectionResponse {
502 /// Pagination links for navigating the result set.
503 pub links: PaginationLinks,
504 /// Pagination metadata describing the current page.
505 pub meta: PaginationMeta,
506 /// The product list items on this page.
507 pub data: Vec<ProductListItem>,
508}
509
510/// A paginated collection of items within a specific product list.
511///
512/// Returned by `GET /{api_version}/product_list_items`. Individual item schemas are
513/// not yet formally defined by the API contract, so items are represented as raw
514/// [`serde_json::Value`]s until the schema matures.
515#[derive(Clone, Debug, Deserialize, Serialize)]
516pub struct ProductListItemsResponse {
517 /// Pagination links for navigating the result set.
518 pub links: PaginationLinks,
519 /// Pagination metadata describing the current page.
520 pub meta: PaginationMeta,
521 /// The raw product list item data on this page.
522 pub data: Vec<serde_json::Value>,
523}
524
525/// Request body for adding a product to a product list.
526///
527/// Sent by `POST /{api_version}/product_list_items`.
528#[derive(Clone, Debug, Serialize)]
529pub struct ProductListItemCreateRequest {
530 /// Unique identifier of the product to add.
531 #[serde(rename = "productId")]
532 pub product_id: u64,
533 /// Unique identifier of the product list to add the product to.
534 #[serde(rename = "productListId")]
535 pub product_list_id: u64,
536}
537
538/// The created product list item.
539///
540/// Returned by `POST /{api_version}/product_list_items`. The API wraps this resource
541/// in a JSON:API-style envelope on the wire (`{"data": {"id": ..., "type": ...,
542/// "attributes": {"productId": ..., "productListId": ..., "productListItemId": ...}}}`);
543/// [`Deserialize`] unwraps that envelope so callers work with a flat struct.
544#[derive(Clone, Debug, Serialize)]
545pub struct ProductListItemCreateResponse {
546 /// Unique identifier of the product added to the list.
547 pub product_id: u64,
548 /// Unique identifier of the product list the product was added to.
549 pub product_list_id: u64,
550 /// Unique identifier assigned to this product list item. Required to remove
551 /// the item later via `DELETE /{api_version}/product_list_items/{id}`.
552 pub product_list_item_id: u64,
553}
554
555impl<'de> Deserialize<'de> for ProductListItemCreateResponse {
556 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
557 where
558 D: Deserializer<'de>,
559 {
560 #[derive(Deserialize)]
561 struct Attributes {
562 #[serde(rename = "productId")]
563 product_id: u64,
564 #[serde(rename = "productListId")]
565 product_list_id: u64,
566 #[serde(rename = "productListItemId")]
567 product_list_item_id: u64,
568 }
569 #[derive(Deserialize)]
570 struct Resource {
571 attributes: Attributes,
572 }
573 #[derive(Deserialize)]
574 struct Envelope {
575 data: Resource,
576 }
577
578 let envelope = Envelope::deserialize(deserializer)?;
579 Ok(Self {
580 product_id: envelope.data.attributes.product_id,
581 product_list_id: envelope.data.attributes.product_list_id,
582 product_list_item_id: envelope.data.attributes.product_list_item_id,
583 })
584 }
585}
586
587// ── Query parameter structs ───────────────────────────────────────────────────
588
589/// Query parameters for the `GET /order_products` (library items) endpoint.
590///
591/// All fields are optional. Set a field to `Some(value)` to include the corresponding
592/// query parameter in the request. Use [`Default::default()`] to start with no filters
593/// applied.
594///
595/// # Examples
596///
597/// ```rust
598/// use dtrpg_sdk::LibraryItemsParams;
599///
600/// let params = LibraryItemsParams {
601/// page: Some(2),
602/// page_size: Some(50),
603/// get_filters: Some(true),
604/// ..Default::default()
605/// };
606/// ```
607#[derive(Default)]
608pub struct LibraryItemsParams {
609 /// The page number to retrieve (1-based).
610 pub page: Option<u32>,
611 /// The number of items to return per page.
612 pub page_size: Option<u32>,
613 /// When `true`, includes checksum data for each product file (`getChecksum=1`).
614 pub get_checksum: Option<bool>,
615 /// When `true`, includes filter category data for each product (`getFilters=1`).
616 pub get_filters: Option<bool>,
617 /// When `true`, restricts results to library (non-archived) products (`library=true`).
618 pub library: Option<bool>,
619 /// When `true`, includes archived products; when `false`, excludes them (`archived=1/0`).
620 pub archived: Option<bool>,
621 /// ISO 8601 date string. When set, returns only products updated after this date
622 /// (`updatedDate[after]=...`).
623 pub updated_date_after: Option<String>,
624}
625
626/// Query parameters for paginated collection endpoints such as `/product_lists`.
627///
628/// All fields are optional. Use [`Default::default()`] to retrieve the first page with
629/// the server's default page size.
630///
631/// # Examples
632///
633/// ```rust
634/// use dtrpg_sdk::PageParams;
635///
636/// let params = PageParams { page: Some(3), page_size: Some(25) };
637/// ```
638#[derive(Default)]
639pub struct PageParams {
640 /// The page number to retrieve (1-based).
641 pub page: Option<u32>,
642 /// The number of items to return per page.
643 pub page_size: Option<u32>,
644}