use crate::client::Client;
use crate::error::{Error, Result};
use crate::internal::{apply_pagination, push_opt, push_opt_bool};
use crate::pagination::{FetchFn, Page, PageStream};
use crate::resources::agencies::urlencoding;
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 ListDibbsRfqsOptions {
#[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,
pub open: Option<bool>,
#[builder(into)]
pub nsn: Option<String>,
#[builder(into)]
pub fsc: Option<String>,
#[builder(into)]
pub part_number: Option<String>,
#[builder(into)]
pub solicitation: Option<String>,
#[builder(into)]
pub purchase_request: Option<String>,
#[builder(into)]
pub set_aside: Option<String>,
#[builder(into)]
pub status_code: Option<String>,
#[builder(into)]
pub organization: Option<String>,
#[builder(into)]
pub quantity_min: Option<String>,
#[builder(into)]
pub quantity_max: Option<String>,
#[builder(into)]
pub return_by_date_after: Option<String>,
#[builder(into)]
pub return_by_date_before: Option<String>,
#[builder(into)]
pub issue_date_after: Option<String>,
#[builder(into)]
pub issue_date_before: Option<String>,
#[builder(into)]
pub search: Option<String>,
#[builder(into)]
pub ordering: Option<String>,
#[builder(default)]
pub extra: BTreeMap<String, String>,
}
impl ListDibbsRfqsOptions {
pub(crate) 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_bool(&mut q, "open", self.open);
push_opt(&mut q, "nsn", self.nsn.as_deref());
push_opt(&mut q, "fsc", self.fsc.as_deref());
push_opt(&mut q, "part_number", self.part_number.as_deref());
push_opt(&mut q, "solicitation", self.solicitation.as_deref());
push_opt(&mut q, "purchase_request", self.purchase_request.as_deref());
push_opt(&mut q, "set_aside", self.set_aside.as_deref());
push_opt(&mut q, "status_code", self.status_code.as_deref());
push_opt(&mut q, "organization", self.organization.as_deref());
push_opt(&mut q, "quantity_min", self.quantity_min.as_deref());
push_opt(&mut q, "quantity_max", self.quantity_max.as_deref());
push_opt(
&mut q,
"return_by_date_after",
self.return_by_date_after.as_deref(),
);
push_opt(
&mut q,
"return_by_date_before",
self.return_by_date_before.as_deref(),
);
push_opt(&mut q, "issue_date_after", self.issue_date_after.as_deref());
push_opt(
&mut q,
"issue_date_before",
self.issue_date_before.as_deref(),
);
push_opt(&mut q, "search", self.search.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
}
}
#[derive(Debug, Clone, Default, Builder, PartialEq, Eq)]
#[non_exhaustive]
pub struct ListDibbsRfpsOptions {
#[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,
pub open: Option<bool>,
#[builder(into)]
pub nsn: Option<String>,
#[builder(into)]
pub fsc: Option<String>,
#[builder(into)]
pub part_number: Option<String>,
#[builder(into)]
pub solicitation: Option<String>,
#[builder(into)]
pub buyer_code: Option<String>,
#[builder(into)]
pub organization: Option<String>,
#[builder(into)]
pub issued_date_after: Option<String>,
#[builder(into)]
pub issued_date_before: Option<String>,
#[builder(into)]
pub closes_date_after: Option<String>,
#[builder(into)]
pub closes_date_before: Option<String>,
#[builder(into)]
pub search: Option<String>,
#[builder(into)]
pub ordering: Option<String>,
#[builder(default)]
pub extra: BTreeMap<String, String>,
}
impl ListDibbsRfpsOptions {
pub(crate) 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_bool(&mut q, "open", self.open);
push_opt(&mut q, "nsn", self.nsn.as_deref());
push_opt(&mut q, "fsc", self.fsc.as_deref());
push_opt(&mut q, "part_number", self.part_number.as_deref());
push_opt(&mut q, "solicitation", self.solicitation.as_deref());
push_opt(&mut q, "buyer_code", self.buyer_code.as_deref());
push_opt(&mut q, "organization", self.organization.as_deref());
push_opt(
&mut q,
"issued_date_after",
self.issued_date_after.as_deref(),
);
push_opt(
&mut q,
"issued_date_before",
self.issued_date_before.as_deref(),
);
push_opt(
&mut q,
"closes_date_after",
self.closes_date_after.as_deref(),
);
push_opt(
&mut q,
"closes_date_before",
self.closes_date_before.as_deref(),
);
push_opt(&mut q, "search", self.search.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
}
}
#[derive(Debug, Clone, Default, Builder, PartialEq, Eq)]
#[non_exhaustive]
pub struct ListDibbsAwardsOptions {
#[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 nsn: Option<String>,
#[builder(into)]
pub fsc: Option<String>,
#[builder(into)]
pub part_number: Option<String>,
#[builder(into)]
pub solicitation: Option<String>,
#[builder(into)]
pub award_number: Option<String>,
#[builder(into)]
pub delivery_order_number: Option<String>,
#[builder(into)]
pub purchase_request: Option<String>,
#[builder(into)]
pub awardee_cage: Option<String>,
#[builder(into)]
pub entity: Option<String>,
#[builder(into)]
pub organization: Option<String>,
#[builder(into)]
pub award_date_after: Option<String>,
#[builder(into)]
pub award_date_before: Option<String>,
#[builder(into)]
pub posted_date_after: Option<String>,
#[builder(into)]
pub posted_date_before: Option<String>,
#[builder(into)]
pub total_contract_price_min: Option<String>,
#[builder(into)]
pub total_contract_price_max: Option<String>,
#[builder(into)]
pub search: Option<String>,
#[builder(into)]
pub ordering: Option<String>,
#[builder(default)]
pub extra: BTreeMap<String, String>,
}
impl ListDibbsAwardsOptions {
pub(crate) 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, "nsn", self.nsn.as_deref());
push_opt(&mut q, "fsc", self.fsc.as_deref());
push_opt(&mut q, "part_number", self.part_number.as_deref());
push_opt(&mut q, "solicitation", self.solicitation.as_deref());
push_opt(&mut q, "award_number", self.award_number.as_deref());
push_opt(
&mut q,
"delivery_order_number",
self.delivery_order_number.as_deref(),
);
push_opt(&mut q, "purchase_request", self.purchase_request.as_deref());
push_opt(&mut q, "awardee_cage", self.awardee_cage.as_deref());
push_opt(&mut q, "entity", self.entity.as_deref());
push_opt(&mut q, "organization", self.organization.as_deref());
push_opt(&mut q, "award_date_after", self.award_date_after.as_deref());
push_opt(
&mut q,
"award_date_before",
self.award_date_before.as_deref(),
);
push_opt(
&mut q,
"posted_date_after",
self.posted_date_after.as_deref(),
);
push_opt(
&mut q,
"posted_date_before",
self.posted_date_before.as_deref(),
);
push_opt(
&mut q,
"total_contract_price_min",
self.total_contract_price_min.as_deref(),
);
push_opt(
&mut q,
"total_contract_price_max",
self.total_contract_price_max.as_deref(),
);
push_opt(&mut q, "search", self.search.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
}
}
#[derive(Debug, Clone, Default, Builder, PartialEq, Eq)]
#[non_exhaustive]
pub struct GetDibbsOptions {
#[builder(into)]
pub shape: Option<String>,
#[builder(default)]
pub flat: bool,
#[builder(default)]
pub flat_lists: bool,
}
impl GetDibbsOptions {
pub(crate) fn to_query(&self) -> Vec<(String, String)> {
let mut q = Vec::new();
push_opt(&mut q, "shape", self.shape.as_deref());
if self.flat {
q.push(("flat".into(), "true".into()));
}
if self.flat_lists {
q.push(("flat_lists".into(), "true".into()));
}
q
}
}
impl Client {
pub async fn list_dibbs_rfqs(&self, opts: ListDibbsRfqsOptions) -> Result<Page<Record>> {
let q = opts.to_query();
self.get_page("/api/dibbs/rfqs/", &q).await
}
pub async fn get_dibbs_rfq(&self, uuid: &str, opts: Option<GetDibbsOptions>) -> Result<Record> {
if uuid.is_empty() {
return Err(Error::Validation {
message: "get_dibbs_rfq: uuid is required".into(),
response: None,
});
}
let q = opts.unwrap_or_default().to_query();
let path = format!("/api/dibbs/rfqs/{}/", urlencoding(uuid));
self.get_json::<Record>(&path, &q).await
}
pub fn iterate_dibbs_rfqs(&self, opts: ListDibbsRfqsOptions) -> 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_dibbs_rfqs(next).await })
});
PageStream::new(self.clone(), fetch)
}
pub async fn list_dibbs_rfps(&self, opts: ListDibbsRfpsOptions) -> Result<Page<Record>> {
let q = opts.to_query();
self.get_page("/api/dibbs/rfps/", &q).await
}
pub async fn get_dibbs_rfp(&self, uuid: &str, opts: Option<GetDibbsOptions>) -> Result<Record> {
if uuid.is_empty() {
return Err(Error::Validation {
message: "get_dibbs_rfp: uuid is required".into(),
response: None,
});
}
let q = opts.unwrap_or_default().to_query();
let path = format!("/api/dibbs/rfps/{}/", urlencoding(uuid));
self.get_json::<Record>(&path, &q).await
}
pub fn iterate_dibbs_rfps(&self, opts: ListDibbsRfpsOptions) -> 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_dibbs_rfps(next).await })
});
PageStream::new(self.clone(), fetch)
}
pub async fn list_dibbs_awards(&self, opts: ListDibbsAwardsOptions) -> Result<Page<Record>> {
let q = opts.to_query();
self.get_page("/api/dibbs/awards/", &q).await
}
pub async fn get_dibbs_award(
&self,
uuid: &str,
opts: Option<GetDibbsOptions>,
) -> Result<Record> {
if uuid.is_empty() {
return Err(Error::Validation {
message: "get_dibbs_award: uuid is required".into(),
response: None,
});
}
let q = opts.unwrap_or_default().to_query();
let path = format!("/api/dibbs/awards/{}/", urlencoding(uuid));
self.get_json::<Record>(&path, &q).await
}
pub fn iterate_dibbs_awards(&self, opts: ListDibbsAwardsOptions) -> 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_dibbs_awards(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 list_dibbs_rfqs_all_filters_emit() {
let opts = ListDibbsRfqsOptions::builder()
.open(true)
.nsn("v-nsn")
.fsc("v-fsc")
.part_number("v-part_number")
.solicitation("v-solicitation")
.purchase_request("v-purchase_request")
.set_aside("v-set_aside")
.status_code("v-status_code")
.organization("v-organization")
.quantity_min("v-quantity_min")
.quantity_max("v-quantity_max")
.return_by_date_after("v-return_by_date_after")
.return_by_date_before("v-return_by_date_before")
.issue_date_after("v-issue_date_after")
.issue_date_before("v-issue_date_before")
.search("v-search")
.ordering("v-ordering")
.build();
let q = opts.to_query();
assert_eq!(get_q(&q, "open").as_deref(), Some("true"));
assert_eq!(get_q(&q, "nsn").as_deref(), Some("v-nsn"));
assert_eq!(get_q(&q, "fsc").as_deref(), Some("v-fsc"));
assert_eq!(get_q(&q, "part_number").as_deref(), Some("v-part_number"));
assert_eq!(get_q(&q, "solicitation").as_deref(), Some("v-solicitation"));
assert_eq!(
get_q(&q, "purchase_request").as_deref(),
Some("v-purchase_request")
);
assert_eq!(get_q(&q, "set_aside").as_deref(), Some("v-set_aside"));
assert_eq!(get_q(&q, "status_code").as_deref(), Some("v-status_code"));
assert_eq!(get_q(&q, "organization").as_deref(), Some("v-organization"));
assert_eq!(get_q(&q, "quantity_min").as_deref(), Some("v-quantity_min"));
assert_eq!(get_q(&q, "quantity_max").as_deref(), Some("v-quantity_max"));
assert_eq!(
get_q(&q, "return_by_date_after").as_deref(),
Some("v-return_by_date_after")
);
assert_eq!(
get_q(&q, "return_by_date_before").as_deref(),
Some("v-return_by_date_before")
);
assert_eq!(
get_q(&q, "issue_date_after").as_deref(),
Some("v-issue_date_after")
);
assert_eq!(
get_q(&q, "issue_date_before").as_deref(),
Some("v-issue_date_before")
);
assert_eq!(get_q(&q, "search").as_deref(), Some("v-search"));
assert_eq!(get_q(&q, "ordering").as_deref(), Some("v-ordering"));
assert_eq!(q.len(), 17);
}
#[test]
fn list_dibbs_rfqs_open_false_is_a_filter_not_an_absence() {
let opts = ListDibbsRfqsOptions::builder().open(false).build();
assert_eq!(get_q(&opts.to_query(), "open").as_deref(), Some("false"));
}
#[test]
fn list_dibbs_rfqs_zero_value_omitted() {
assert!(ListDibbsRfqsOptions::builder()
.build()
.to_query()
.is_empty());
}
#[test]
fn list_dibbs_rfqs_cursor_wins_over_page_and_shape_emits() {
let opts = ListDibbsRfqsOptions::builder()
.page(3u32)
.cursor("c0".to_string())
.shape(crate::SHAPE_DIBBS_RFQS_MINIMAL)
.build();
let q = opts.to_query();
assert_eq!(get_q(&q, "cursor").as_deref(), Some("c0"));
assert_eq!(get_q(&q, "page"), None);
assert_eq!(
get_q(&q, "shape").as_deref(),
Some(crate::SHAPE_DIBBS_RFQS_MINIMAL)
);
}
#[test]
fn list_dibbs_rfqs_extra_emits() {
let mut extra = BTreeMap::new();
extra.insert("custom_x".to_string(), "xv".to_string());
let q = ListDibbsRfqsOptions::builder()
.extra(extra)
.build()
.to_query();
assert!(q.contains(&("custom_x".into(), "xv".into())));
}
#[tokio::test]
async fn get_dibbs_rfq_validates_empty_uuid() {
let client = Client::builder().api_key("x").build().expect("client");
let err = client.get_dibbs_rfq("", None).await.unwrap_err();
match err {
Error::Validation { message, .. } => {
assert!(message.contains("uuid is required"));
}
other => panic!("expected Validation, got {other:?}"),
}
}
#[test]
fn list_dibbs_rfps_all_filters_emit() {
let opts = ListDibbsRfpsOptions::builder()
.open(true)
.nsn("v-nsn")
.fsc("v-fsc")
.part_number("v-part_number")
.solicitation("v-solicitation")
.buyer_code("v-buyer_code")
.organization("v-organization")
.issued_date_after("v-issued_date_after")
.issued_date_before("v-issued_date_before")
.closes_date_after("v-closes_date_after")
.closes_date_before("v-closes_date_before")
.search("v-search")
.ordering("v-ordering")
.build();
let q = opts.to_query();
assert_eq!(get_q(&q, "open").as_deref(), Some("true"));
assert_eq!(get_q(&q, "nsn").as_deref(), Some("v-nsn"));
assert_eq!(get_q(&q, "fsc").as_deref(), Some("v-fsc"));
assert_eq!(get_q(&q, "part_number").as_deref(), Some("v-part_number"));
assert_eq!(get_q(&q, "solicitation").as_deref(), Some("v-solicitation"));
assert_eq!(get_q(&q, "buyer_code").as_deref(), Some("v-buyer_code"));
assert_eq!(get_q(&q, "organization").as_deref(), Some("v-organization"));
assert_eq!(
get_q(&q, "issued_date_after").as_deref(),
Some("v-issued_date_after")
);
assert_eq!(
get_q(&q, "issued_date_before").as_deref(),
Some("v-issued_date_before")
);
assert_eq!(
get_q(&q, "closes_date_after").as_deref(),
Some("v-closes_date_after")
);
assert_eq!(
get_q(&q, "closes_date_before").as_deref(),
Some("v-closes_date_before")
);
assert_eq!(get_q(&q, "search").as_deref(), Some("v-search"));
assert_eq!(get_q(&q, "ordering").as_deref(), Some("v-ordering"));
assert_eq!(q.len(), 13);
}
#[test]
fn list_dibbs_rfps_open_false_is_a_filter_not_an_absence() {
let opts = ListDibbsRfpsOptions::builder().open(false).build();
assert_eq!(get_q(&opts.to_query(), "open").as_deref(), Some("false"));
}
#[test]
fn list_dibbs_rfps_zero_value_omitted() {
assert!(ListDibbsRfpsOptions::builder()
.build()
.to_query()
.is_empty());
}
#[test]
fn list_dibbs_rfps_cursor_wins_over_page_and_shape_emits() {
let opts = ListDibbsRfpsOptions::builder()
.page(3u32)
.cursor("c0".to_string())
.shape(crate::SHAPE_DIBBS_RFPS_MINIMAL)
.build();
let q = opts.to_query();
assert_eq!(get_q(&q, "cursor").as_deref(), Some("c0"));
assert_eq!(get_q(&q, "page"), None);
assert_eq!(
get_q(&q, "shape").as_deref(),
Some(crate::SHAPE_DIBBS_RFPS_MINIMAL)
);
}
#[test]
fn list_dibbs_rfps_extra_emits() {
let mut extra = BTreeMap::new();
extra.insert("custom_x".to_string(), "xv".to_string());
let q = ListDibbsRfpsOptions::builder()
.extra(extra)
.build()
.to_query();
assert!(q.contains(&("custom_x".into(), "xv".into())));
}
#[tokio::test]
async fn get_dibbs_rfp_validates_empty_uuid() {
let client = Client::builder().api_key("x").build().expect("client");
let err = client.get_dibbs_rfp("", None).await.unwrap_err();
match err {
Error::Validation { message, .. } => {
assert!(message.contains("uuid is required"));
}
other => panic!("expected Validation, got {other:?}"),
}
}
#[test]
fn list_dibbs_awards_all_filters_emit() {
let opts = ListDibbsAwardsOptions::builder()
.nsn("v-nsn")
.fsc("v-fsc")
.part_number("v-part_number")
.solicitation("v-solicitation")
.award_number("v-award_number")
.delivery_order_number("v-delivery_order_number")
.purchase_request("v-purchase_request")
.awardee_cage("v-awardee_cage")
.entity("v-entity")
.organization("v-organization")
.award_date_after("v-award_date_after")
.award_date_before("v-award_date_before")
.posted_date_after("v-posted_date_after")
.posted_date_before("v-posted_date_before")
.total_contract_price_min("v-total_contract_price_min")
.total_contract_price_max("v-total_contract_price_max")
.search("v-search")
.ordering("v-ordering")
.build();
let q = opts.to_query();
assert_eq!(get_q(&q, "nsn").as_deref(), Some("v-nsn"));
assert_eq!(get_q(&q, "fsc").as_deref(), Some("v-fsc"));
assert_eq!(get_q(&q, "part_number").as_deref(), Some("v-part_number"));
assert_eq!(get_q(&q, "solicitation").as_deref(), Some("v-solicitation"));
assert_eq!(get_q(&q, "award_number").as_deref(), Some("v-award_number"));
assert_eq!(
get_q(&q, "delivery_order_number").as_deref(),
Some("v-delivery_order_number")
);
assert_eq!(
get_q(&q, "purchase_request").as_deref(),
Some("v-purchase_request")
);
assert_eq!(get_q(&q, "awardee_cage").as_deref(), Some("v-awardee_cage"));
assert_eq!(get_q(&q, "entity").as_deref(), Some("v-entity"));
assert_eq!(get_q(&q, "organization").as_deref(), Some("v-organization"));
assert_eq!(
get_q(&q, "award_date_after").as_deref(),
Some("v-award_date_after")
);
assert_eq!(
get_q(&q, "award_date_before").as_deref(),
Some("v-award_date_before")
);
assert_eq!(
get_q(&q, "posted_date_after").as_deref(),
Some("v-posted_date_after")
);
assert_eq!(
get_q(&q, "posted_date_before").as_deref(),
Some("v-posted_date_before")
);
assert_eq!(
get_q(&q, "total_contract_price_min").as_deref(),
Some("v-total_contract_price_min")
);
assert_eq!(
get_q(&q, "total_contract_price_max").as_deref(),
Some("v-total_contract_price_max")
);
assert_eq!(get_q(&q, "search").as_deref(), Some("v-search"));
assert_eq!(get_q(&q, "ordering").as_deref(), Some("v-ordering"));
assert_eq!(q.len(), 18);
}
#[test]
fn list_dibbs_awards_zero_value_omitted() {
assert!(ListDibbsAwardsOptions::builder()
.build()
.to_query()
.is_empty());
}
#[test]
fn list_dibbs_awards_cursor_wins_over_page_and_shape_emits() {
let opts = ListDibbsAwardsOptions::builder()
.page(3u32)
.cursor("c0".to_string())
.shape(crate::SHAPE_DIBBS_AWARDS_MINIMAL)
.build();
let q = opts.to_query();
assert_eq!(get_q(&q, "cursor").as_deref(), Some("c0"));
assert_eq!(get_q(&q, "page"), None);
assert_eq!(
get_q(&q, "shape").as_deref(),
Some(crate::SHAPE_DIBBS_AWARDS_MINIMAL)
);
}
#[test]
fn list_dibbs_awards_extra_emits() {
let mut extra = BTreeMap::new();
extra.insert("custom_x".to_string(), "xv".to_string());
let q = ListDibbsAwardsOptions::builder()
.extra(extra)
.build()
.to_query();
assert!(q.contains(&("custom_x".into(), "xv".into())));
}
#[tokio::test]
async fn get_dibbs_award_validates_empty_uuid() {
let client = Client::builder().api_key("x").build().expect("client");
let err = client.get_dibbs_award("", None).await.unwrap_err();
match err {
Error::Validation { message, .. } => {
assert!(message.contains("uuid is required"));
}
other => panic!("expected Validation, got {other:?}"),
}
}
#[test]
fn get_options_emits() {
let q = GetDibbsOptions::builder()
.shape("a,b")
.flat(true)
.flat_lists(true)
.build()
.to_query();
assert_eq!(get_q(&q, "shape").as_deref(), Some("a,b"));
assert_eq!(get_q(&q, "flat").as_deref(), Some("true"));
assert_eq!(get_q(&q, "flat_lists").as_deref(), Some("true"));
}
}