use crate::client::Client;
use crate::error::{Error, Result};
use crate::internal::apply_pagination;
use crate::pagination::{FetchFn, Page, PageStream};
use crate::resources::agencies::urlencoding;
use crate::Record;
use bon::Builder;
use std::sync::Arc;
#[derive(Debug, Clone, Default, Builder, PartialEq, Eq)]
#[non_exhaustive]
pub struct ListVehicleAwardeesOptions {
#[builder(into)]
pub page: Option<u32>,
#[builder(into)]
pub limit: Option<u32>,
#[builder(into)]
pub cursor: Option<String>,
#[builder(into)]
pub shape: Option<String>,
#[builder(default)]
pub flat: bool,
#[builder(default)]
pub flat_lists: bool,
}
impl ListVehicleAwardeesOptions {
fn to_query(&self) -> Vec<(String, String)> {
let mut q = Vec::new();
apply_pagination(
&mut q,
self.page,
self.limit,
self.cursor.as_deref(),
self.shape.as_deref(),
self.flat,
self.flat_lists,
);
q
}
}
#[derive(Debug, Clone, Default, Builder, PartialEq, Eq)]
#[non_exhaustive]
pub struct ListVehicleOrdersOptions {
#[builder(into)]
pub page: Option<u32>,
#[builder(into)]
pub limit: Option<u32>,
#[builder(into)]
pub cursor: Option<String>,
#[builder(into)]
pub shape: Option<String>,
#[builder(default)]
pub flat: bool,
#[builder(default)]
pub flat_lists: bool,
}
impl ListVehicleOrdersOptions {
fn to_query(&self) -> Vec<(String, String)> {
let mut q = Vec::new();
apply_pagination(
&mut q,
self.page,
self.limit,
self.cursor.as_deref(),
self.shape.as_deref(),
self.flat,
self.flat_lists,
);
q
}
}
impl Client {
pub async fn list_vehicle_awardees(
&self,
uuid: &str,
opts: ListVehicleAwardeesOptions,
) -> Result<Page<Record>> {
list_vehicle_awardees_inner(self, uuid, opts).await
}
pub async fn list_vehicle_orders(
&self,
uuid: &str,
opts: ListVehicleOrdersOptions,
) -> Result<Page<Record>> {
list_vehicle_orders_inner(self, uuid, opts).await
}
pub fn iterate_vehicle_awardees(
&self,
uuid: &str,
opts: ListVehicleAwardeesOptions,
) -> PageStream<Record> {
let opts = Arc::new(opts);
let uuid = Arc::new(uuid.to_string());
let fetch: FetchFn<Record> = Box::new(move |client, page, cursor| {
let mut next = (*opts).clone();
next.page = page;
next.cursor = cursor;
let uuid = uuid.clone();
Box::pin(async move { list_vehicle_awardees_inner(&client, &uuid, next).await })
});
PageStream::new(self.clone(), fetch)
}
pub fn iterate_vehicle_orders(
&self,
uuid: &str,
opts: ListVehicleOrdersOptions,
) -> PageStream<Record> {
let opts = Arc::new(opts);
let uuid = Arc::new(uuid.to_string());
let fetch: FetchFn<Record> = Box::new(move |client, page, cursor| {
let mut next = (*opts).clone();
next.page = page;
next.cursor = cursor;
let uuid = uuid.clone();
Box::pin(async move { list_vehicle_orders_inner(&client, &uuid, next).await })
});
PageStream::new(self.clone(), fetch)
}
}
async fn list_vehicle_awardees_inner(
client: &Client,
uuid: &str,
opts: ListVehicleAwardeesOptions,
) -> Result<Page<Record>> {
if uuid.is_empty() {
return Err(Error::Validation {
message: "list_vehicle_awardees: uuid is required".into(),
response: None,
});
}
let q = opts.to_query();
let path = format!("/api/vehicles/{}/awardees/", urlencoding(uuid));
let bytes = client.get_bytes(&path, &q).await?;
Page::decode(&bytes)
}
async fn list_vehicle_orders_inner(
client: &Client,
uuid: &str,
opts: ListVehicleOrdersOptions,
) -> Result<Page<Record>> {
if uuid.is_empty() {
return Err(Error::Validation {
message: "list_vehicle_orders: uuid is required".into(),
response: None,
});
}
let q = opts.to_query();
let path = format!("/api/vehicles/{}/orders/", urlencoding(uuid));
let bytes = client.get_bytes(&path, &q).await?;
Page::decode(&bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn awardees_opts_emit_pagination() {
let opts = ListVehicleAwardeesOptions::builder()
.page(2u32)
.limit(25u32)
.shape("vehicle_awardees(minimal)")
.build();
let q = opts.to_query();
assert!(q.contains(&("page".into(), "2".into())));
assert!(q.contains(&("limit".into(), "25".into())));
assert!(q.contains(&("shape".into(), "vehicle_awardees(minimal)".into())));
}
#[test]
fn orders_opts_emit_flat() {
let opts = ListVehicleOrdersOptions::builder()
.flat(true)
.flat_lists(true)
.build();
let q = opts.to_query();
assert!(q.contains(&("flat".into(), "true".into())));
assert!(q.contains(&("flat_lists".into(), "true".into())));
}
#[tokio::test]
async fn list_vehicle_awardees_empty_uuid_returns_validation() {
let client = Client::builder().api_key("x").build().expect("build");
let err = client
.list_vehicle_awardees("", ListVehicleAwardeesOptions::default())
.await
.expect_err("must error");
match err {
Error::Validation { message, .. } => {
assert!(message.contains("uuid"));
}
other => panic!("expected Validation, got {other:?}"),
}
}
#[tokio::test]
async fn list_vehicle_orders_empty_uuid_returns_validation() {
let client = Client::builder().api_key("x").build().expect("build");
let err = client
.list_vehicle_orders("", ListVehicleOrdersOptions::default())
.await
.expect_err("must error");
match err {
Error::Validation { message, .. } => {
assert!(message.contains("uuid"));
}
other => panic!("expected Validation, got {other:?}"),
}
}
}