use crate::client::Client;
use crate::error::Result;
use crate::internal::{apply_pagination, push_opt};
use crate::pagination::{FetchFn, Page, PageStream};
use crate::Record;
use bon::Builder;
use std::collections::BTreeMap;
use std::sync::Arc;
#[derive(Debug, Clone, Default, Builder, PartialEq, Eq)]
#[non_exhaustive]
pub struct ListSubawardsOptions {
#[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,
#[builder(into)]
pub award_key: Option<String>,
#[builder(into)]
pub prime_uei: Option<String>,
#[builder(into)]
pub sub_uei: Option<String>,
#[builder(into)]
pub awarding_agency: Option<String>,
#[builder(into)]
pub funding_agency: Option<String>,
#[builder(into)]
pub fiscal_year: Option<String>,
#[builder(into)]
pub fiscal_year_gte: Option<String>,
#[builder(into)]
pub fiscal_year_lte: Option<String>,
#[builder(into)]
pub recipient: Option<String>,
#[builder(into)]
pub ordering: Option<String>,
#[builder(default)]
pub extra: BTreeMap<String, String>,
}
impl ListSubawardsOptions {
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,
);
push_opt(&mut q, "award_key", self.award_key.as_deref());
push_opt(&mut q, "prime_uei", self.prime_uei.as_deref());
push_opt(&mut q, "sub_uei", self.sub_uei.as_deref());
push_opt(&mut q, "awarding_agency", self.awarding_agency.as_deref());
push_opt(&mut q, "funding_agency", self.funding_agency.as_deref());
push_opt(&mut q, "fiscal_year", self.fiscal_year.as_deref());
push_opt(&mut q, "fiscal_year_gte", self.fiscal_year_gte.as_deref());
push_opt(&mut q, "fiscal_year_lte", self.fiscal_year_lte.as_deref());
push_opt(&mut q, "recipient", self.recipient.as_deref());
push_opt(&mut q, "ordering", self.ordering.as_deref());
for (k, v) in &self.extra {
if !v.is_empty() {
q.push((k.clone(), v.clone()));
}
}
q
}
}
impl Client {
pub async fn list_subawards(&self, opts: ListSubawardsOptions) -> Result<Page<Record>> {
let q = opts.to_query();
let bytes = self.get_bytes("/api/subawards/", &q).await?;
Page::decode(&bytes)
}
pub fn iterate_subawards(&self, opts: ListSubawardsOptions) -> PageStream<Record> {
let opts = Arc::new(opts);
let fetch: FetchFn<Record> = Box::new(move |client, page, cursor| {
let mut next = (*opts).clone();
next.page = page;
next.cursor = cursor;
Box::pin(async move { client.list_subawards(next).await })
});
PageStream::new(self.clone(), fetch)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn get_q(q: &[(String, String)], k: &str) -> Option<String> {
q.iter().find(|(kk, _)| kk == k).map(|(_, v)| v.clone())
}
#[test]
fn subawards_emits_all_filters() {
let opts = ListSubawardsOptions::builder()
.award_key("PIID-123")
.prime_uei("UEI-PRIME")
.sub_uei("UEI-SUB")
.awarding_agency("9700")
.funding_agency("9700")
.fiscal_year("2024")
.fiscal_year_gte("2023")
.fiscal_year_lte("2025")
.recipient("Acme Inc")
.ordering("-last_modified_date")
.build();
let q = opts.to_query();
assert_eq!(get_q(&q, "award_key").as_deref(), Some("PIID-123"));
assert_eq!(get_q(&q, "prime_uei").as_deref(), Some("UEI-PRIME"));
assert_eq!(get_q(&q, "sub_uei").as_deref(), Some("UEI-SUB"));
assert_eq!(get_q(&q, "awarding_agency").as_deref(), Some("9700"));
assert_eq!(get_q(&q, "funding_agency").as_deref(), Some("9700"));
assert_eq!(get_q(&q, "fiscal_year").as_deref(), Some("2024"));
assert_eq!(get_q(&q, "fiscal_year_gte").as_deref(), Some("2023"));
assert_eq!(get_q(&q, "fiscal_year_lte").as_deref(), Some("2025"));
assert_eq!(get_q(&q, "recipient").as_deref(), Some("Acme Inc"));
assert_eq!(
get_q(&q, "ordering").as_deref(),
Some("-last_modified_date")
);
}
#[test]
fn subawards_pagination_emits() {
let opts = ListSubawardsOptions::builder()
.page(2u32)
.limit(50u32)
.shape(crate::SHAPE_SUBAWARDS_MINIMAL)
.build();
let q = opts.to_query();
assert_eq!(get_q(&q, "page").as_deref(), Some("2"));
assert_eq!(get_q(&q, "limit").as_deref(), Some("50"));
assert_eq!(
get_q(&q, "shape").as_deref(),
Some(crate::SHAPE_SUBAWARDS_MINIMAL)
);
}
#[test]
fn subawards_extra_map() {
let mut extra = BTreeMap::new();
extra.insert("x".to_string(), "y".to_string());
let opts = ListSubawardsOptions::builder().extra(extra).build();
let q = opts.to_query();
assert_eq!(get_q(&q, "x").as_deref(), Some("y"));
}
}