Skip to main content

tango/
lib.rs

1//! # Tango — Rust SDK for the Tango federal-contracting API
2//!
3//! The Tango API gives programmatic access to federal-contracting data —
4//! contracts, IDVs, entities, opportunities, grants, vehicles, protests, and
5//! more — with dynamic response shaping so callers fetch only the fields they
6//! need.
7//!
8//! This crate is the official, async-first Rust SDK. It mirrors the surface of
9//! the [Node](https://github.com/makegov/tango-node),
10//! [Python](https://github.com/makegov/tango-python), and
11//! [Go](https://github.com/makegov/tango-go) SDKs while leaning into Rust
12//! idioms (async streams, typed errors, compile-time-checked builders).
13//!
14//! ## Quick start
15//!
16//! ```no_run
17//! use tango::Client;
18//!
19//! # async fn run() -> tango::Result<()> {
20//! let client = Client::builder().api_key("your-api-key").build()?;
21//!
22//! let page = client
23//!     .list_contracts(
24//!         tango::ListContractsOptions::builder()
25//!             .awarding_agency("9700")
26//!             .shape(tango::SHAPE_CONTRACTS_MINIMAL)
27//!             .limit(25u32)
28//!             .build(),
29//!     )
30//!     .await?;
31//!
32//! for record in page.results {
33//!     println!("{:?}", record.get("piid"));
34//! }
35//! # Ok(()) }
36//! ```
37//!
38//! Or walk every page with the async stream API:
39//!
40//! ```no_run
41//! # use tango::Client;
42//! use futures::TryStreamExt;
43//! # async fn run() -> tango::Result<()> {
44//! # let client = Client::builder().api_key("x").build()?;
45//! let mut stream = client.iterate_contracts(
46//!     tango::ListContractsOptions::builder()
47//!         .awarding_agency("9700")
48//!         .build(),
49//! );
50//! while let Some(record) = stream.try_next().await? {
51//!     // process one contract at a time
52//!     let _ = record;
53//! }
54//! # Ok(()) }
55//! ```
56//!
57//! ## Authentication
58//!
59//! Pass an API key via `Client::builder().api_key(...)`, or set
60//! `TANGO_API_KEY` in the environment and call [`Client::from_env`].
61//!
62//! ## Error handling
63//!
64//! Every fallible call returns [`Result<T, Error>`]. Match on the variant to
65//! dispatch on the failure mode:
66//!
67//! ```no_run
68//! # use tango::{Client, Error};
69//! # async fn run() -> tango::Result<()> {
70//! # let client = Client::builder().api_key("x").build()?;
71//! match client.get_agency("9700", None).await {
72//!     Ok(agency) => println!("{}", agency.name.unwrap_or_default()),
73//!     Err(Error::NotFound { .. }) => println!("not found"),
74//!     Err(Error::RateLimit { retry_after, .. }) => println!("retry in {retry_after}s"),
75//!     Err(e) => return Err(e),
76//! }
77//! # Ok(()) }
78//! ```
79//!
80//! ## Response shaping
81//!
82//! List endpoints accept a `shape` parameter that picks which fields the API
83//! returns. The SDK exposes ready-made presets — e.g.
84//! [`SHAPE_CONTRACTS_MINIMAL`] — and you can also pass a custom field list.
85//! See the [`shapes`] module.
86//!
87//! ## Webhook signing
88//!
89//! Webhook delivery verification is in the separate [`makegov-tango-webhooks`]
90//! crate so a verifier service doesn't have to pull in the full SDK. See
91//! `<https://docs.rs/makegov-tango-webhooks>` for HMAC-SHA256 signing and
92//! verification.
93//!
94//! [`makegov-tango-webhooks`]: https://docs.rs/makegov-tango-webhooks
95
96#![forbid(unsafe_code)]
97#![warn(missing_docs)]
98#![warn(rust_2018_idioms)]
99#![cfg_attr(docsrs, feature(doc_cfg))]
100// Internal helpers used by Phase 2 resource modules. Suppressed for now so
101// foundation work compiles cleanly before Wave-2 agents fill in the surface.
102#![allow(dead_code)]
103
104mod client;
105mod error;
106mod internal;
107mod pagination;
108mod resources;
109mod transport;
110mod version;
111
112pub mod models;
113pub mod shapes;
114
115// ---------- Public surface ----------
116
117pub use client::{Client, DEFAULT_RETRIES, DEFAULT_RETRY_BACKOFF, DEFAULT_TIMEOUT};
118pub use error::{Error, ErrorBody, Result};
119pub use internal::ListOptions;
120pub use pagination::{Page, PageStream};
121pub use shapes::{
122    DEFAULT_BASE_URL, SHAPE_CONTRACTS_MINIMAL, SHAPE_ENTITIES_COMPREHENSIVE,
123    SHAPE_ENTITIES_MINIMAL, SHAPE_FORECASTS_MINIMAL, SHAPE_GRANTS_MINIMAL,
124    SHAPE_GSA_ELIBRARY_CONTRACTS_MINIMAL, SHAPE_IDVS_COMPREHENSIVE, SHAPE_IDVS_MINIMAL,
125    SHAPE_ITDASHBOARD_INVESTMENTS_COMPREHENSIVE, SHAPE_ITDASHBOARD_INVESTMENTS_MINIMAL,
126    SHAPE_NOTICES_MINIMAL, SHAPE_OPPORTUNITIES_MINIMAL, SHAPE_ORGANIZATIONS_MINIMAL,
127    SHAPE_OTAS_MINIMAL, SHAPE_OTIDVS_MINIMAL, SHAPE_PROTESTS_MINIMAL, SHAPE_SUBAWARDS_MINIMAL,
128    SHAPE_VEHICLES_COMPREHENSIVE, SHAPE_VEHICLES_MINIMAL, SHAPE_VEHICLE_AWARDEES_MINIMAL,
129    SHAPE_VEHICLE_ORDERS_MINIMAL,
130};
131pub use transport::RateLimitInfo;
132pub use version::VERSION;
133
134// Re-export the resource option types so callers can `use tango::Foo;`
135// without diving into module paths.
136pub use resources::*;
137
138/// A free-form list result record. Used as the `T` for shape-driven list
139/// endpoints whose response schema depends on the requested `shape`.
140///
141/// `Record` is just a [`serde_json::Map`]. Use `record.get("field_name")` to
142/// pull a field, or `serde_json::from_value(Value::Object(record))` to
143/// deserialize into your own typed struct.
144pub type Record = serde_json::Map<String, serde_json::Value>;