use crate::constants::{CDG_BASE_URL, GOVINFO_BASE_URL};
use crate::error::Error;
use crate::legislation::{Chamber, CommitteeDocumentType, Congress, MeasureType};
use crate::parser::CitationParser;
use crate::utils::{DisplayOption, Result};
use std::str::FromStr;
#[derive(Debug, PartialEq)]
pub enum Citation {
Measure {
congress: Option<Congress>,
chamber: Chamber,
measure_type: MeasureType,
number: usize,
version: Option<String>,
},
Law {
congress: Option<Congress>,
number: usize,
},
Statute {
volume: usize,
page: usize,
},
CommitteeDocument {
congress: Option<Congress>,
chamber: Chamber,
document_type: CommitteeDocumentType,
number: usize,
},
}
impl Citation {
pub fn parse(input: &str) -> Result<Self> {
CitationParser::parse(input)
}
#[must_use]
pub fn to_url(&self) -> Option<String> {
match self {
Self::Measure {
congress,
chamber,
measure_type,
number,
version,
} => {
if let Some(congress) = congress {
let collection = "bill";
let congress = congress.as_ordinal();
let mut url = format!(
"{CDG_BASE_URL}/{collection}/{congress}-congress/{chamber}-{measure_type}/{number}"
);
if let Some(ver) = version {
url.push_str("/text/");
url.push_str(ver);
}
Some(url)
} else {
None
}
}
Self::CommitteeDocument {
congress,
chamber,
document_type,
number,
} => {
if let Some(congress) = congress {
let congress = congress.as_ordinal();
let collection = match document_type {
CommitteeDocumentType::Report => "congressional-report",
CommitteeDocumentType::Print => "committee-print",
};
let short = if collection == "congressional-report" {
"report"
} else {
collection
};
let url = format!(
"{CDG_BASE_URL}/{collection}/{congress}-congress/{chamber}-{short}/{number}"
);
Some(url)
} else {
None
}
}
Self::Statute { volume, page } => Some(format!(
"{GOVINFO_BASE_URL}/app/details/STATUTE-{volume}/STATUTE-{volume}-Pg{page}"
)),
Self::Law { congress, number } => {
if let Some(Congress(congress)) = congress {
Some(format!(
"{CDG_BASE_URL}/{congress}/plaws/publ{number}/PLAW-{congress}publ{number}.pdf"
))
} else {
None
}
}
}
}
pub fn version(&self) -> Option<String> {
match self {
Self::Measure { version, .. } => version.as_ref().map(String::from),
_ => None,
}
}
#[must_use]
pub fn normalize(&self, show_version: bool) -> String {
match &self {
Self::Measure {
chamber,
congress,
measure_type,
number,
version,
} => {
let object = match (chamber, measure_type) {
(Chamber::House, MeasureType::Bill) => "hr",
(Chamber::House, MeasureType::Resolution) => "hres",
(Chamber::House, MeasureType::ConcurrentResolution) => "hconres",
(Chamber::House, MeasureType::JointResolution) => "hjres",
(Chamber::Senate, MeasureType::Bill) => "s",
(Chamber::Senate, MeasureType::Resolution) => "sres",
(Chamber::Senate, MeasureType::ConcurrentResolution) => "sconres",
(Chamber::Senate, MeasureType::JointResolution) => "sjres",
};
let mut cite = format!("{}{object}{number}", DisplayOption(*congress));
if show_version {
let version = DisplayOption(version.to_owned()).to_string();
cite.push_str(&version);
}
cite
}
Self::Law { congress, number } => {
format!("{}publ{number}", DisplayOption(*congress))
}
Self::Statute { volume, page } => format!("{volume}stat{page}"),
Self::CommitteeDocument {
congress,
chamber,
document_type,
number,
} => {
let object = match (chamber, document_type) {
(Chamber::House, CommitteeDocumentType::Report) => "hrpt",
(Chamber::Senate, CommitteeDocumentType::Report) => "srpt",
(Chamber::House, CommitteeDocumentType::Print) => "hprt",
(Chamber::Senate, CommitteeDocumentType::Print) => "sprt",
};
format!("{}{object}{number}", DisplayOption(*congress))
}
}
}
}
impl FromStr for Citation {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Self::parse(s)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_no_ver_house_bill() {
let input = "118hr8070";
let expected = Citation::Measure {
congress: Some(Congress(118)),
chamber: Chamber::House,
measure_type: MeasureType::Bill,
number: 8070,
version: None,
};
let result = input.parse();
assert_eq!(expected, result.unwrap());
}
#[test]
fn test_parse_house_report() {
let input = "118hrpt529";
let expected = Citation::CommitteeDocument {
congress: Some(Congress(118)),
chamber: Chamber::House,
document_type: CommitteeDocumentType::Report,
number: 529,
};
let result = input.parse();
assert_eq!(expected, result.unwrap());
}
#[test]
fn test_parse_house_print() {
let input = "119hprt58246";
let expected = Citation::CommitteeDocument {
congress: Some(Congress(119)),
chamber: Chamber::House,
document_type: CommitteeDocumentType::Print,
number: 58246,
};
let result = input.parse();
assert_eq!(expected, result.unwrap());
}
#[test]
fn test_parse_senate_print() {
let input = "119sprt57246";
let expected = Citation::CommitteeDocument {
congress: Some(Congress(119)),
chamber: Chamber::Senate,
document_type: CommitteeDocumentType::Print,
number: 57246,
};
let result = input.parse();
assert_eq!(expected, result.unwrap());
}
#[test]
fn test_parse_senate_report() {
let input = "118srpt17";
let expected = Citation::CommitteeDocument {
congress: Some(Congress(118)),
chamber: Chamber::Senate,
document_type: CommitteeDocumentType::Report,
number: 17,
};
let result = input.parse();
assert_eq!(expected, result.unwrap());
}
#[test]
fn test_house_bill_to_url() {
let input = "118hr529";
let expected =
Some("https://www.congress.gov/bill/118th-congress/house-bill/529".to_string());
let citation = input.parse::<Citation>().unwrap();
let result = citation.to_url();
assert_eq!(expected, result);
}
#[test]
fn test_house_bill_with_ver_to_url() {
let input = "118hr529ih";
let expected =
Some("https://www.congress.gov/bill/118th-congress/house-bill/529/text/ih".to_string());
let citation = input.parse::<Citation>().unwrap();
let result = citation.to_url();
assert_eq!(expected, result);
}
#[test]
fn test_house_report_to_url() {
let input = "118hrpt529";
let expected = Some(
"https://www.congress.gov/congressional-report/118th-congress/house-report/529"
.to_string(),
);
let citation = input.parse::<Citation>().unwrap();
let result = citation.to_url();
assert_eq!(expected, result);
}
#[test]
fn test_house_print_to_url() {
let input = "119hprt58246";
let expected = Some(
"https://www.congress.gov/committee-print/119th-congress/house-committee-print/58246"
.to_string(),
);
let citation = input.parse::<Citation>().unwrap();
let result = citation.to_url();
assert_eq!(expected, result);
}
#[test]
fn test_senate_print_to_url() {
let input = "119sprt57246";
let expected = Some(
"https://www.congress.gov/committee-print/119th-congress/senate-committee-print/57246"
.to_string(),
);
let citation = input.parse::<Citation>().unwrap();
let result = citation.to_url();
assert_eq!(expected, result);
}
#[test]
fn test_get_version() {
let input = "118hr529ih";
let expected = Some("ih");
let citation = input.parse::<Citation>().unwrap();
let result = citation.version();
assert_eq!(expected, result.as_deref());
}
#[test]
fn test_no_congress_to_url_returns_none() {
let input = "hr529";
let expected = None;
let citation = input.parse::<Citation>().unwrap();
let result = citation.to_url();
assert_eq!(expected, result);
}
#[test]
fn test_uppercase_input() {
let input = "118HR529IH";
let expected =
Some("https://www.congress.gov/bill/118th-congress/house-bill/529/text/ih".to_string());
let citation = input.parse::<Citation>().unwrap();
let result = citation.to_url();
assert_eq!(expected, result);
}
#[test]
fn test_parse_house_bill_with_dots() {
let input = "118h.r.529";
let expected = Citation::Measure {
congress: Some(Congress(118)),
chamber: Chamber::House,
measure_type: MeasureType::Bill,
number: 529,
version: None,
};
let result = input.parse();
assert_eq!(expected, result.unwrap());
}
#[test]
fn test_parse_house_bill_with_version_no_congress() {
let input = "hr529ih";
let expected = Citation::Measure {
congress: None,
chamber: Chamber::House,
measure_type: MeasureType::Bill,
number: 529,
version: Some("ih".to_string()),
};
let result = input.parse();
assert_eq!(expected, result.unwrap());
}
#[test]
fn test_print_citation_without_version() {
let input = "118hr529ih";
let citation: Citation = input.parse().unwrap();
let expected = "118hr529";
let result = citation.normalize(false);
assert_eq!(expected, result);
}
#[test]
fn test_print_no_congress_citation_without_version() {
let input = "H.R.529.IH";
let citation: Citation = input.parse().unwrap();
let expected = "hr529".to_string();
let result = citation.normalize(false);
assert_eq!(expected, result);
}
#[test]
fn test_normalize_public_law() {
let input = "Public Law No: 119-68";
let citation: Citation = input.parse().unwrap();
let expected = "119publ68";
let result = citation.normalize(true);
assert_eq!(expected, result);
}
#[test]
fn test_normalize_statute() {
let input = "86Stat1326";
let citation: Citation = input.parse().unwrap();
let expected = "86stat1326";
let result = citation.normalize(true);
assert_eq!(expected, result);
}
#[test]
fn test_normalize_house_report() {
let input = "119.H.Rpt.23";
let citation: Citation = input.parse().unwrap();
let expected = "119hrpt23";
let result = citation.normalize(true);
assert_eq!(expected, result);
}
#[test]
fn test_normalize_senate_print() {
let input = "119.S.Prt.23";
let citation: Citation = input.parse().unwrap();
let expected = "119sprt23";
let result = citation.normalize(true);
assert_eq!(expected, result);
}
#[test]
fn test_statute_to_url() {
let input = "135stat2454";
let citation: Citation = input.parse().unwrap();
let expected =
Some("https://www.govinfo.gov/app/details/STATUTE-135/STATUTE-135-Pg2454".to_string());
let result = citation.to_url();
assert_eq!(expected, result);
}
#[test]
fn test_law_to_url() {
let input = "119pl68";
let citation: Citation = input.parse().unwrap();
let expected =
Some("https://www.congress.gov/119/plaws/publ68/PLAW-119publ68.pdf".to_string());
let result = citation.to_url();
assert_eq!(expected, result);
let input = "119publ68";
let citation: Citation = input.parse().unwrap();
let result = citation.to_url();
assert_eq!(expected, result);
}
#[test]
fn test_citations_with_spaces() {
let input = "135 Stat. 2454";
let citation: Citation = input.parse().unwrap();
let expected = "135stat2454";
let result = citation.normalize(false);
assert_eq!(expected, result);
}
}