1use crate::client::Client;
6use crate::error::{Error, Result};
7use crate::internal::{apply_pagination, push_opt};
8use crate::pagination::{FetchFn, Page, PageStream};
9use crate::resources::agencies::urlencoding;
10use crate::Record;
11use bon::Builder;
12use std::sync::Arc;
13
14#[derive(Debug, Clone, Default, Builder, PartialEq, Eq)]
17#[non_exhaustive]
18pub struct ListVehicleAwardeesOptions {
19 #[builder(into)]
21 pub page: Option<u32>,
22 #[builder(into)]
24 pub limit: Option<u32>,
25 #[builder(into)]
27 pub cursor: Option<String>,
28 #[builder(into)]
31 pub shape: Option<String>,
32 #[builder(default)]
34 pub flat: bool,
35 #[builder(default)]
37 pub flat_lists: bool,
38 #[builder(into)]
40 pub search: Option<String>,
41}
42
43impl ListVehicleAwardeesOptions {
44 fn to_query(&self) -> Vec<(String, String)> {
45 let mut q = Vec::new();
46 apply_pagination(
47 &mut q,
48 self.page,
49 self.limit,
50 self.cursor.as_deref(),
51 self.shape.as_deref(),
52 self.flat,
53 self.flat_lists,
54 );
55 push_opt(&mut q, "search", self.search.as_deref());
56 q
57 }
58}
59
60#[derive(Debug, Clone, Default, Builder, PartialEq, Eq)]
63#[non_exhaustive]
64pub struct ListVehicleOrdersOptions {
65 #[builder(into)]
67 pub page: Option<u32>,
68 #[builder(into)]
70 pub limit: Option<u32>,
71 #[builder(into)]
73 pub cursor: Option<String>,
74 #[builder(into)]
76 pub shape: Option<String>,
77 #[builder(default)]
79 pub flat: bool,
80 #[builder(default)]
82 pub flat_lists: bool,
83 #[builder(into)]
85 pub ordering: Option<String>,
86}
87
88impl ListVehicleOrdersOptions {
89 fn to_query(&self) -> Vec<(String, String)> {
90 let mut q = Vec::new();
91 apply_pagination(
92 &mut q,
93 self.page,
94 self.limit,
95 self.cursor.as_deref(),
96 self.shape.as_deref(),
97 self.flat,
98 self.flat_lists,
99 );
100 push_opt(&mut q, "ordering", self.ordering.as_deref());
101 q
102 }
103}
104
105impl Client {
106 pub async fn list_vehicle_awardees(
109 &self,
110 uuid: &str,
111 opts: ListVehicleAwardeesOptions,
112 ) -> Result<Page<Record>> {
113 list_vehicle_awardees_inner(self, uuid, opts).await
114 }
115
116 pub async fn list_vehicle_orders(
119 &self,
120 uuid: &str,
121 opts: ListVehicleOrdersOptions,
122 ) -> Result<Page<Record>> {
123 list_vehicle_orders_inner(self, uuid, opts).await
124 }
125
126 pub fn iterate_vehicle_awardees(
128 &self,
129 uuid: &str,
130 opts: ListVehicleAwardeesOptions,
131 ) -> PageStream<Record> {
132 let opts = Arc::new(opts);
133 let uuid = Arc::new(uuid.to_string());
134 let fetch: FetchFn<Record> = Box::new(move |client, page, cursor| {
135 let mut next = (*opts).clone();
136 next.page = page;
137 next.cursor = cursor;
138 let uuid = uuid.clone();
139 Box::pin(async move { list_vehicle_awardees_inner(&client, &uuid, next).await })
140 });
141 PageStream::new(self.clone(), fetch)
142 }
143
144 pub fn iterate_vehicle_orders(
146 &self,
147 uuid: &str,
148 opts: ListVehicleOrdersOptions,
149 ) -> PageStream<Record> {
150 let opts = Arc::new(opts);
151 let uuid = Arc::new(uuid.to_string());
152 let fetch: FetchFn<Record> = Box::new(move |client, page, cursor| {
153 let mut next = (*opts).clone();
154 next.page = page;
155 next.cursor = cursor;
156 let uuid = uuid.clone();
157 Box::pin(async move { list_vehicle_orders_inner(&client, &uuid, next).await })
158 });
159 PageStream::new(self.clone(), fetch)
160 }
161}
162
163async fn list_vehicle_awardees_inner(
164 client: &Client,
165 uuid: &str,
166 opts: ListVehicleAwardeesOptions,
167) -> Result<Page<Record>> {
168 if uuid.is_empty() {
169 return Err(Error::Validation {
170 message: "list_vehicle_awardees: uuid is required".into(),
171 response: None,
172 });
173 }
174 let q = opts.to_query();
175 let path = format!("/api/vehicles/{}/awardees/", urlencoding(uuid));
176 client.get_page(&path, &q).await
177}
178
179async fn list_vehicle_orders_inner(
180 client: &Client,
181 uuid: &str,
182 opts: ListVehicleOrdersOptions,
183) -> Result<Page<Record>> {
184 if uuid.is_empty() {
185 return Err(Error::Validation {
186 message: "list_vehicle_orders: uuid is required".into(),
187 response: None,
188 });
189 }
190 let q = opts.to_query();
191 let path = format!("/api/vehicles/{}/orders/", urlencoding(uuid));
192 client.get_page(&path, &q).await
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn awardees_opts_emit_pagination() {
201 let opts = ListVehicleAwardeesOptions::builder()
202 .page(2u32)
203 .limit(25u32)
204 .shape("vehicle_awardees(minimal)")
205 .build();
206 let q = opts.to_query();
207 assert!(q.contains(&("page".into(), "2".into())));
208 assert!(q.contains(&("limit".into(), "25".into())));
209 assert!(q.contains(&("shape".into(), "vehicle_awardees(minimal)".into())));
210 }
211
212 #[test]
213 fn awardees_search_and_orders_ordering_emit() {
214 let q = ListVehicleAwardeesOptions::builder()
215 .search("cyber")
216 .build()
217 .to_query();
218 assert!(q.contains(&("search".into(), "cyber".into())));
219 let q = ListVehicleOrdersOptions::builder()
220 .ordering("-obligated")
221 .build()
222 .to_query();
223 assert!(q.contains(&("ordering".into(), "-obligated".into())));
224 }
225
226 #[test]
227 fn orders_opts_emit_flat() {
228 let opts = ListVehicleOrdersOptions::builder()
229 .flat(true)
230 .flat_lists(true)
231 .build();
232 let q = opts.to_query();
233 assert!(q.contains(&("flat".into(), "true".into())));
234 assert!(q.contains(&("flat_lists".into(), "true".into())));
235 }
236
237 #[tokio::test]
238 async fn list_vehicle_awardees_empty_uuid_returns_validation() {
239 let client = Client::builder().api_key("x").build().expect("build");
240 let err = client
241 .list_vehicle_awardees("", ListVehicleAwardeesOptions::default())
242 .await
243 .expect_err("must error");
244 match err {
245 Error::Validation { message, .. } => {
246 assert!(message.contains("uuid"));
247 }
248 other => panic!("expected Validation, got {other:?}"),
249 }
250 }
251
252 #[tokio::test]
253 async fn list_vehicle_orders_empty_uuid_returns_validation() {
254 let client = Client::builder().api_key("x").build().expect("build");
255 let err = client
256 .list_vehicle_orders("", ListVehicleOrdersOptions::default())
257 .await
258 .expect_err("must error");
259 match err {
260 Error::Validation { message, .. } => {
261 assert!(message.contains("uuid"));
262 }
263 other => panic!("expected Validation, got {other:?}"),
264 }
265 }
266}