use crate::error::Error;
use reqwest;
use serde::Deserialize;
use serde_xml_rs::from_str;
use std::{collections::VecDeque, env};
use tracing::warn;
pub const ENV_API_KEY: &str = "CONGRESS_GOV_API_KEY";
pub const ROOT_URL: &str = "https://api.congress.gov/v3";
const MEMBERS_PATH: &str = "/member";
const API_KEY_URL_PARAM: &str = "api_key=";
#[derive(Clone, Debug, Deserialize)]
pub struct Depiction {
#[serde(rename = "attribution")]
pub attribution: Option<String>,
#[serde(rename = "imageUrl")]
pub image_url: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Pagination {
#[serde(rename = "count")]
pub count: i32,
#[serde(rename = "next")]
pub next: Option<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct TermItem {
#[serde(rename = "chamber")]
pub chamber: String,
#[serde(rename = "endYear")]
pub end_year: Option<i32>,
#[serde(rename = "startYear")]
pub start_year: i32,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Terms {
pub item: Vec<TermItem>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Member {
#[serde(rename = "bioguideId")]
pub bioguide_id: String,
#[serde(rename = "depiction")]
pub depiction: Option<Depiction>,
#[serde(rename = "district")]
pub district: Option<i32>,
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "partyName")]
pub party_name: String,
#[serde(rename = "state")]
pub state: String,
#[serde(rename = "terms")]
pub terms: Terms,
#[serde(rename = "updateDate")]
pub update_date: String,
#[serde(rename = "url")]
pub url: String,
}
impl Member {
pub fn split_name(&self) -> Result<(String, Option<String>, String), Error> {
let first_name: &str;
let middle_name: Option<String>;
if !self.name.contains(",") {
return Err(Error::NameSplitError(format!(
"The name {0} is not in the expected format",
self.name
)));
}
let mut parts: VecDeque<&str> = self.name.split(',').collect();
if parts.len() < 2 {
return Err(Error::NameSplitError(format!(
"The name {0} did not split into the expected number of segments",
self.name
)));
}
let last_name_option = parts.pop_front();
let second_segment_option = parts.pop_front();
let last_name: &str = match last_name_option {
Some(last) => last,
None => {
return Err(Error::NameSplitError(String::from(
"Failed to obtain the last name.",
)));
}
};
let second_segment = match second_segment_option {
Some(second_segment) => second_segment.trim_start(),
None => {
return Err(Error::NameSplitError(format!(
"The name {0} did not contain a first or middle name",
self.name
)));
}
};
if second_segment.contains(' ') {
let space_index = second_segment.find(' ').unwrap();
let (first, middle) = second_segment.split_at(space_index);
first_name = first;
middle_name = if middle.is_empty() {
None
} else {
Some(String::from(middle))
};
} else {
first_name = second_segment;
middle_name = None;
}
if first_name.is_empty() {
Err(Error::NameSplitError(String::from(
"Failed to parse the first name segment.",
)))
} else if last_name.is_empty() {
Err(Error::NameSplitError(String::from(
"Failed to parse the last name segment.",
)))
} else {
Ok((
String::from(first_name),
middle_name,
String::from(last_name),
))
}
} }
#[derive(Clone, Debug, Deserialize)]
pub struct LegislationInfo {
#[serde(rename = "count")]
pub count: i32,
#[serde(rename = "url")]
pub url: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Leadership {
#[serde(rename = "congress")]
pub congress: i32,
#[serde(rename = "type")]
pub leadership_type: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct PartyHistory {
#[serde(rename = "partyAbbreviation")]
pub party_abbreviation: String,
#[serde(rename = "partyName")]
pub party_name: String,
#[serde(rename = "startYear")]
pub start_year: i32,
}
#[derive(Clone, Debug, Deserialize)]
pub struct TermFull {
#[serde(rename = "chamber")]
pub chamber: String,
#[serde(rename = "congress")]
pub congress: i32,
#[serde(rename = "endYear")]
pub end_year: Option<i32>,
#[serde(rename = "memberType")]
pub member_type: String,
#[serde(rename = "startYear")]
pub start_year: i32,
#[serde(rename = "stateCode")]
pub state_code: String,
#[serde(rename = "stateName")]
pub state_name: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct MemberFull {
#[serde(rename = "bioguideId")]
pub bioguide_id: String,
#[serde(rename = "birthYear")]
pub birth_year: Option<String>,
#[serde(rename = "cosponsoredLegislation")]
pub cosponsored_legislation: Option<LegislationInfo>,
#[serde(rename = "depiction")]
pub depiction: Option<Depiction>,
#[serde(rename = "directOrderName")]
pub direct_order_name: String,
#[serde(rename = "firstName")]
pub first_name: String,
#[serde(rename = "honorificName")]
pub honorific_name: Option<String>,
#[serde(rename = "invertedOrderName")]
pub inverted_order_name: String,
#[serde(rename = "lastName")]
pub last_name: String,
#[serde(rename = "leadership")]
pub leadership: Option<Vec<Leadership>>,
#[serde(rename = "partyHistory")]
pub party_history: Vec<PartyHistory>,
#[serde(rename = "sponsoredLegislation")]
pub sponsored_legislation: Option<LegislationInfo>,
#[serde(rename = "state")]
pub state: String,
#[serde(rename = "terms")]
pub terms: Vec<TermFull>,
#[serde(rename = "updateDate")]
pub update_date: String,
}
impl MemberFull {
pub fn split_direct_order_name(&self) -> Result<(String, Option<String>, String), Error> {
let parts: Vec<&str> = self.direct_order_name.split_whitespace().collect();
match parts.len() {
0 => Err(Error::NameSplitError(format!(
"The name {0} did not contain any segments",
self.direct_order_name
))),
1 => Err(Error::NameSplitError(format!(
"The name {0} only contains one segment, expected at least first and last name",
self.direct_order_name
))),
2 => Ok((parts[0].to_string(), None, parts[1].to_string())),
_ => {
let first = parts[0].to_string();
let last = parts[parts.len() - 1].to_string();
let middle_parts: Vec<&str> = parts[1..parts.len() - 1].to_vec();
let middle_str = middle_parts.join(" ").trim().to_string();
if middle_str.is_empty() {
Ok((first, None, last))
} else {
Ok((first, Some(middle_str), last))
}
}
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct MembersMessage {
#[serde(rename = "members")]
pub members: Vec<Member>,
#[serde(rename = "pagination")]
pub pagination: Pagination,
}
#[derive(Clone, Debug, Deserialize)]
pub struct MemberFullMessage {
#[serde(rename = "member")]
pub member: MemberFull,
}
pub async fn get_members(offset: i32, limit: i32) -> Result<MembersMessage, Error> {
let api_key = match env::var(ENV_API_KEY) {
Ok(key) => key,
Err(e) => return Err(Error::CongressGovApiKeyError(e.to_string())),
};
let url = format!(
"{ROOT_URL}{MEMBERS_PATH}?{API_KEY_URL_PARAM}{api_key}&offset={offset}&limit={limit}"
);
match reqwest::get(url).await {
Ok(response) => {
match response.text().await {
Ok(body) => {
match serde_json::from_str::<MembersMessage>(&body) {
Ok(member_struct) => Ok(member_struct),
Err(e) => {
Err(Error::CongressGovResponseError(
format!(
"{}\nFailed to deserialize the response body into a MembersMessage structure: {}",
body,
e
)
))
},
}
},
Err(e) => Err(Error::CongressGovResponseError(e.to_string())),
}
},
Err(e) => Err(Error::CongressGovHttpGetError(e.to_string())),
}
}
pub async fn get_all_members() -> Result<Vec<Member>, Error> {
let api_key = match env::var(ENV_API_KEY) {
Ok(key) => key,
Err(e) => return Err(Error::CongressGovApiKeyError(e.to_string())),
};
let url = format!("{ROOT_URL}{MEMBERS_PATH}?{API_KEY_URL_PARAM}{api_key}&offset=0");
let (mut members, remaining_members): (Vec<Member>, i32) = match reqwest::get(url).await {
Ok(response) => match response.text().await {
Ok(body) => match serde_json::from_str::<MembersMessage>(&body) {
Ok(member_struct) => {
let received_member_count: i32 = member_struct.members.len() as i32;
let remaining_count: i32 =
member_struct.pagination.count - received_member_count;
(member_struct.members, remaining_count)
}
Err(e) => {
return Err(Error::CongressGovResponseError(
format!(
"{}\nFailed to deserialize the response body into a MembersMessage structure: {}",
body,
e
)
));
}
},
Err(e) => return Err(Error::CongressGovResponseError(e.to_string())),
},
Err(e) => return Err(Error::CongressGovHttpGetError(e.to_string())),
};
let offset = members.len();
let url = format!(
"{ROOT_URL}{MEMBERS_PATH}?{API_KEY_URL_PARAM}{api_key}&offset={offset}&limit={remaining_members}"
);
match reqwest::get(url).await {
Ok(response) => match response.text().await {
Ok(body) => match serde_json::from_str::<MembersMessage>(&body) {
Ok(mut member_struct) => {
members.append(&mut member_struct.members);
Ok(members)
}
Err(e) => {
Err(Error::CongressGovResponseError(
format!(
"{}\nFailed to deserialize the response body into a MembersMessage structure: {}",
body,
e
)
))
}
},
Err(e) => Err(Error::CongressGovResponseError(e.to_string())),
},
Err(e) => Err(Error::CongressGovHttpGetError(e.to_string())),
}
}
pub async fn get_all_detailed_members() -> Result<Vec<MemberFull>, Error> {
let members = get_all_members().await?;
let api_key = match env::var(ENV_API_KEY) {
Ok(key) => key,
Err(e) => return Err(Error::CongressGovApiKeyError(e.to_string())),
};
let mut detailed_members = Vec::new();
for member in members {
let url = format!(
"{ROOT_URL}{MEMBERS_PATH}/{bioguide_id}?{API_KEY_URL_PARAM}{api_key}",
bioguide_id = member.bioguide_id
);
match reqwest::get(&url).await {
Ok(response) => match response.text().await {
Ok(body) => match serde_json::from_str::<MemberFullMessage>(&body) {
Ok(member_full_message) => {
detailed_members.push(member_full_message.member);
}
Err(_) => {
warn!("Failed to deserialize the response body for member {} from JSON, attempting XML.", member.bioguide_id);
match from_str::<MemberFullMessage>(&body) {
Ok(member_full) => {
detailed_members.push(member_full.member);
}
Err(e) => {
return Err(Error::CongressGovResponseError(format!(
"{}\nFailed to deserialize the response body for member {} into a MemberFullMessage structure: {}",
body,
member.bioguide_id,
e
)));
}
}
}
},
Err(e) => {
return Err(Error::CongressGovResponseError(format!(
"Failed to read response body for member {}: {}",
member.bioguide_id, e
)));
}
},
Err(e) => {
return Err(Error::CongressGovHttpGetError(format!(
"Failed to fetch detailed data for member {}: {}",
member.bioguide_id, e
)));
}
}
}
Ok(detailed_members)
}
pub async fn get_full_member(bioguide_id: String) -> Result<MemberFull, Error> {
let api_key = match env::var(ENV_API_KEY) {
Ok(key) => key,
Err(e) => return Err(Error::CongressGovApiKeyError(e.to_string())),
};
let url = format!("{ROOT_URL}{MEMBERS_PATH}/{bioguide_id}?{API_KEY_URL_PARAM}{api_key}");
match reqwest::get(&url).await {
Ok(response) => match response.text().await {
Ok(body) => match serde_json::from_str::<MemberFullMessage>(&body) {
Ok(member_full_message) => Ok(member_full_message.member),
Err(_) => {
warn!("Failed to deserialize the response body for member {} from JSON, attempting XML.", bioguide_id);
match from_str::<MemberFullMessage>(&body) {
Ok(member_full) => {
Ok(member_full.member)
}
Err(e) => {
Err(Error::CongressGovResponseError(format!(
"{}\nFailed to deserialize the response body for member {} into a MemberFullMessage structure: {}",
body,
bioguide_id,
e
)))
}
}
}
},
Err(e) => Err(Error::CongressGovResponseError(format!(
"Failed to read response body for member {}: {}",
bioguide_id, e
))),
},
Err(e) => Err(Error::CongressGovHttpGetError(format!(
"Failed to fetch detailed data for member {}: {}",
bioguide_id, e
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_get_all_members_success() {
let result = get_all_members().await;
if let Err(e) = &result {
println!("{e}");
}
assert!(
result.is_ok(),
"get_all_members should succeed with a valid API key"
);
if let Ok(members) = result {
println!("Received {0} members.", members.len());
assert!(
!members.is_empty(),
"get_all_members should return at least one member"
);
}
}
fn create_test_member_full(direct_order_name: &str) -> MemberFull {
MemberFull {
bioguide_id: String::from("T000000"),
birth_year: None,
cosponsored_legislation: Some(LegislationInfo {
count: 0,
url: String::from(""),
}),
depiction: None,
direct_order_name: String::from(direct_order_name),
first_name: String::from(""),
honorific_name: None,
inverted_order_name: String::from(""),
last_name: String::from(""),
leadership: Some(Vec::new()),
party_history: Vec::new(),
sponsored_legislation: Some(LegislationInfo {
count: 0,
url: String::from(""),
}),
state: String::from(""),
terms: Vec::new(),
update_date: String::from(""),
}
}
#[test]
fn test_split_direct_order_name_empty() {
let member = create_test_member_full("");
let result = member.split_direct_order_name();
assert!(result.is_err(), "Empty string should return an error");
if let Err(Error::NameSplitError(msg)) = result {
assert!(
msg.contains("did not contain any segments"),
"Error message should mention no segments"
);
} else {
panic!("Expected NameSplitError, got different error");
}
}
#[test]
fn test_split_direct_order_name_single_part() {
let member = create_test_member_full("John");
let result = member.split_direct_order_name();
assert!(result.is_err(), "Single name part should return an error");
if let Err(Error::NameSplitError(msg)) = result {
assert!(
msg.contains("only contains one segment"),
"Error message should mention one segment"
);
} else {
panic!("Expected NameSplitError, got different error");
}
}
#[test]
fn test_split_direct_order_name_two_parts() {
let member = create_test_member_full("John Doe");
let result = member.split_direct_order_name();
assert!(result.is_ok(), "Two-part name should succeed");
let (first, middle, last) = result.unwrap();
assert_eq!(first, "John");
assert_eq!(middle, None);
assert_eq!(last, "Doe");
}
#[test]
fn test_split_direct_order_name_three_parts() {
let member = create_test_member_full("John Michael Doe");
let result = member.split_direct_order_name();
assert!(result.is_ok(), "Three-part name should succeed");
let (first, middle, last) = result.unwrap();
assert_eq!(first, "John");
assert_eq!(middle, Some(String::from("Michael")));
assert_eq!(last, "Doe");
}
#[test]
fn test_split_direct_order_name_multiple_middle() {
let member = create_test_member_full("John Michael James Doe");
let result = member.split_direct_order_name();
assert!(
result.is_ok(),
"Name with multiple middle names should succeed"
);
let (first, middle, last) = result.unwrap();
assert_eq!(first, "John");
assert_eq!(middle, Some(String::from("Michael James")));
assert_eq!(last, "Doe");
}
#[test]
fn test_split_direct_order_name_extra_whitespace() {
let member = create_test_member_full(" John Michael Doe ");
let result = member.split_direct_order_name();
assert!(result.is_ok(), "Name with extra whitespace should succeed");
let (first, middle, last) = result.unwrap();
assert_eq!(first, "John");
assert_eq!(middle, Some(String::from("Michael")));
assert_eq!(last, "Doe");
}
#[test]
fn test_split_direct_order_name_only_spaces() {
let member = create_test_member_full(" ");
let result = member.split_direct_order_name();
assert!(
result.is_err(),
"Name with only spaces should return an error"
);
if let Err(Error::NameSplitError(msg)) = result {
assert!(
msg.contains("did not contain any segments"),
"Error message should mention no segments"
);
} else {
panic!("Expected NameSplitError, got different error");
}
}
#[test]
fn test_split_direct_order_name_real_world_example() {
let member = create_test_member_full("Patrick J. Leahy");
let result = member.split_direct_order_name();
assert!(result.is_ok(), "Real-world example should succeed");
let (first, middle, last) = result.unwrap();
assert_eq!(first, "Patrick");
assert_eq!(middle, Some(String::from("J.")));
assert_eq!(last, "Leahy");
}
#[test]
fn test_split_direct_order_name_many_parts() {
let member = create_test_member_full("John A B C D E Smith");
let result = member.split_direct_order_name();
assert!(result.is_ok(), "Name with many parts should succeed");
let (first, middle, last) = result.unwrap();
assert_eq!(first, "John");
assert_eq!(middle, Some(String::from("A B C D E")));
assert_eq!(last, "Smith");
}
}