use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TableOfAuthorities {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub categories: Vec<CitationCategory>,
#[serde(default = "default_true")]
pub auto_generate: bool,
#[serde(default)]
pub format: LegalCitationFormat,
}
fn default_true() -> bool {
true
}
impl TableOfAuthorities {
#[must_use]
pub fn new() -> Self {
Self {
id: None,
title: None,
categories: Vec::new(),
auto_generate: true,
format: LegalCitationFormat::default(),
}
}
#[must_use]
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
#[must_use]
pub fn with_category(mut self, category: CitationCategory) -> Self {
self.categories.push(category);
self
}
#[must_use]
pub fn with_format(mut self, format: LegalCitationFormat) -> Self {
self.format = format;
self
}
}
impl Default for TableOfAuthorities {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CitationCategory {
pub category_type: LegalCitationType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub heading: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub entries: Vec<TableOfAuthoritiesEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TableOfAuthoritiesEntry {
pub citation: String,
pub pages: Vec<String>,
#[serde(default)]
pub primary: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Caption {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub court: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub case_number: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub docket: Option<String>,
pub plaintiffs: Vec<Party>,
pub defendants: Vec<Party>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub document_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub judge: Option<String>,
#[serde(default)]
pub style: CaptionStyle,
}
impl Caption {
#[must_use]
pub fn new(court: impl Into<String>) -> Self {
Self {
id: None,
court: court.into(),
case_number: None,
docket: None,
plaintiffs: Vec::new(),
defendants: Vec::new(),
title: None,
document_type: None,
judge: None,
style: CaptionStyle::default(),
}
}
#[must_use]
pub fn with_case_number(mut self, case_number: impl Into<String>) -> Self {
self.case_number = Some(case_number.into());
self
}
#[must_use]
pub fn with_plaintiff(mut self, party: Party) -> Self {
self.plaintiffs.push(party);
self
}
#[must_use]
pub fn with_defendant(mut self, party: Party) -> Self {
self.defendants.push(party);
self
}
#[must_use]
pub fn with_document_type(mut self, doc_type: impl Into<String>) -> Self {
self.document_type = Some(doc_type.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Party {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(default)]
pub primary: bool,
}
impl Party {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
role: None,
primary: false,
}
}
#[must_use]
pub fn with_role(mut self, role: impl Into<String>) -> Self {
self.role = Some(role.into());
self
}
#[must_use]
pub fn primary(mut self) -> Self {
self.primary = true;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum CaptionStyle {
#[default]
Federal,
California,
NewYork,
Texas,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LegalSignatureBlock {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
pub signer: LegalSigner,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub date: Option<String>,
#[serde(default)]
pub certificate_of_service: bool,
}
impl LegalSignatureBlock {
#[must_use]
pub fn new(signer: LegalSigner) -> Self {
Self {
id: None,
role: None,
signer,
date: None,
certificate_of_service: false,
}
}
#[must_use]
pub fn with_role(mut self, role: impl Into<String>) -> Self {
self.role = Some(role.into());
self
}
#[must_use]
pub fn with_date(mut self, date: impl Into<String>) -> Self {
self.date = Some(date.into());
self
}
#[must_use]
pub fn with_certificate_of_service(mut self) -> Self {
self.certificate_of_service = true;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LegalSigner {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bar_number: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub firm: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub telephone: Option<String>,
}
impl LegalSigner {
#[must_use]
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
title: None,
bar_number: None,
firm: None,
address: None,
telephone: None,
}
}
#[must_use]
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
#[must_use]
pub fn with_bar_number(mut self, bar_number: impl Into<String>) -> Self {
self.bar_number = Some(bar_number.into());
self
}
#[must_use]
pub fn with_firm(mut self, firm: impl Into<String>) -> Self {
self.firm = Some(firm.into());
self
}
#[must_use]
pub fn with_address(mut self, address: impl Into<String>) -> Self {
self.address = Some(address.into());
self
}
#[must_use]
pub fn with_telephone(mut self, telephone: impl Into<String>) -> Self {
self.telephone = Some(telephone.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LegalCitation {
pub citation: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub short_form: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cite: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub year: Option<u16>,
pub category: LegalCitationType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pinpoint: Option<Pinpoint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parenthetical: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signal: Option<CitationSignal>,
#[serde(default = "default_true")]
pub first_reference: bool,
}
impl LegalCitation {
#[must_use]
pub fn case(citation: impl Into<String>, cite: impl Into<String>, year: u16) -> Self {
Self {
citation: citation.into(),
short_form: None,
cite: Some(cite.into()),
year: Some(year),
category: LegalCitationType::Case,
pinpoint: None,
parenthetical: None,
signal: None,
first_reference: true,
}
}
#[must_use]
pub fn statute(citation: impl Into<String>) -> Self {
Self {
citation: citation.into(),
short_form: None,
cite: None,
year: None,
category: LegalCitationType::Statute,
pinpoint: None,
parenthetical: None,
signal: None,
first_reference: true,
}
}
#[must_use]
pub fn with_short_form(mut self, short_form: impl Into<String>) -> Self {
self.short_form = Some(short_form.into());
self
}
#[must_use]
pub fn at(mut self, pinpoint: Pinpoint) -> Self {
self.pinpoint = Some(pinpoint);
self
}
#[must_use]
pub fn with_parenthetical(mut self, text: impl Into<String>) -> Self {
self.parenthetical = Some(text.into());
self
}
#[must_use]
pub fn with_signal(mut self, signal: CitationSignal) -> Self {
self.signal = Some(signal);
self
}
#[must_use]
pub fn subsequent(mut self) -> Self {
self.first_reference = false;
self
}
#[must_use]
pub fn to_extension_mark(&self) -> crate::content::ExtensionMark {
let attrs = serde_json::to_value(self).unwrap_or_default();
crate::content::ExtensionMark::new("legal", "cite").with_attributes(attrs)
}
#[must_use]
pub fn from_extension_mark(mark: &crate::content::ExtensionMark) -> Option<Self> {
if mark.is_type("legal", "cite") {
serde_json::from_value(mark.attributes.clone()).ok()
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
#[serde(rename_all = "lowercase")]
pub enum LegalCitationType {
#[strum(serialize = "Cases")]
Case,
#[strum(serialize = "Statutes")]
Statute,
#[strum(serialize = "Regulations")]
Regulation,
#[strum(serialize = "Constitutional Provisions")]
Constitution,
#[strum(serialize = "Secondary Sources")]
Secondary,
#[strum(serialize = "Books")]
Book,
#[strum(serialize = "Law Review Articles")]
LawReview,
#[strum(serialize = "Legislative History")]
Legislative,
#[strum(serialize = "Other Authorities")]
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Pinpoint {
pub pinpoint_type: PinpointType,
pub value: String,
}
impl Pinpoint {
#[must_use]
pub fn page(page: impl Into<String>) -> Self {
Self {
pinpoint_type: PinpointType::Page,
value: page.into(),
}
}
#[must_use]
pub fn section(section: impl Into<String>) -> Self {
Self {
pinpoint_type: PinpointType::Section,
value: section.into(),
}
}
#[must_use]
pub fn paragraph(para: impl Into<String>) -> Self {
Self {
pinpoint_type: PinpointType::Paragraph,
value: para.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PinpointType {
Page,
Section,
Paragraph,
Footnote,
Clause,
Article,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
#[serde(rename_all = "lowercase")]
pub enum CitationSignal {
#[strum(serialize = "")]
None,
#[strum(serialize = "E.g.")]
Eg,
Accord,
See,
#[strum(serialize = "See also")]
SeeAlso,
#[strum(serialize = "Cf.")]
Cf,
Compare,
Contra,
#[strum(serialize = "But see")]
ButSee,
#[strum(serialize = "But cf.")]
ButCf,
#[strum(serialize = "See generally")]
SeeGenerally,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, strum::Display)]
#[serde(rename_all = "lowercase")]
pub enum LegalCitationFormat {
#[default]
Bluebook,
#[strum(serialize = "ALWD")]
Alwd,
McGill,
#[strum(serialize = "OSCOLA")]
Oscola,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_caption_new() {
let caption = Caption::new("United States District Court, Southern District of New York");
assert_eq!(
caption.court,
"United States District Court, Southern District of New York"
);
assert!(caption.plaintiffs.is_empty());
assert!(caption.defendants.is_empty());
}
#[test]
fn test_caption_builder() {
let caption = Caption::new("Supreme Court of the United States")
.with_case_number("No. 21-1234")
.with_plaintiff(Party::new("John Doe").primary())
.with_defendant(Party::new("Acme Corp"))
.with_document_type("Brief for Petitioner");
assert_eq!(caption.case_number, Some("No. 21-1234".to_string()));
assert_eq!(caption.plaintiffs.len(), 1);
assert!(caption.plaintiffs[0].primary);
assert_eq!(caption.defendants.len(), 1);
}
#[test]
fn test_legal_citation_case() {
let cite = LegalCitation::case("Brown v. Board of Education", "347 U.S. 483", 1954);
assert_eq!(cite.category, LegalCitationType::Case);
assert_eq!(cite.year, Some(1954));
assert_eq!(cite.cite, Some("347 U.S. 483".to_string()));
}
#[test]
fn test_legal_citation_builder() {
let cite = LegalCitation::case("Miranda v. Arizona", "384 U.S. 436", 1966)
.with_short_form("Miranda")
.at(Pinpoint::page("444"))
.with_parenthetical("establishing Miranda warnings");
assert_eq!(cite.short_form, Some("Miranda".to_string()));
assert!(cite.pinpoint.is_some());
assert!(cite.parenthetical.is_some());
}
#[test]
fn test_legal_signer() {
let signer = LegalSigner::new("Jane Doe")
.with_bar_number("123456")
.with_firm("Smith & Associates")
.with_address("123 Legal Way, Suite 100, New York, NY 10001")
.with_telephone("(555) 123-4567")
.with_title("Partner");
assert_eq!(signer.bar_number, Some("123456".to_string()));
assert_eq!(signer.firm, Some("Smith & Associates".to_string()));
assert_eq!(signer.telephone, Some("(555) 123-4567".to_string()));
assert_eq!(signer.title, Some("Partner".to_string()));
}
#[test]
fn test_legal_signer_serde_roundtrip() {
let signer = LegalSigner::new("John Smith")
.with_bar_number("789012")
.with_firm("Law Corp");
let json = serde_json::to_string(&signer).unwrap();
assert!(json.contains("\"barNumber\":\"789012\""));
assert!(json.contains("\"firm\":\"Law Corp\""));
let parsed: LegalSigner = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.name, "John Smith");
assert_eq!(parsed.bar_number, Some("789012".to_string()));
}
#[test]
fn test_legal_signature_block_with_role() {
let block = LegalSignatureBlock::new(LegalSigner::new("Jane Doe"))
.with_role("Attorney for Plaintiff")
.with_date("2024-01-15")
.with_certificate_of_service();
assert_eq!(block.role, Some("Attorney for Plaintiff".to_string()));
assert_eq!(block.date, Some("2024-01-15".to_string()));
assert!(block.certificate_of_service);
let json = serde_json::to_string(&block).unwrap();
assert!(json.contains("\"role\":\"Attorney for Plaintiff\""));
let parsed: LegalSignatureBlock = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.role, Some("Attorney for Plaintiff".to_string()));
}
#[test]
fn test_legal_signature_block_backward_compat() {
let json = r#"{
"signer": { "name": "Test Lawyer" },
"certificateOfService": false
}"#;
let block: LegalSignatureBlock = serde_json::from_str(json).unwrap();
assert!(block.role.is_none());
assert_eq!(block.signer.name, "Test Lawyer");
}
#[test]
fn test_table_of_authorities() {
let toa = TableOfAuthorities::new()
.with_title("TABLE OF AUTHORITIES")
.with_format(LegalCitationFormat::Bluebook);
assert!(toa.auto_generate);
assert_eq!(toa.format, LegalCitationFormat::Bluebook);
}
#[test]
fn test_citation_serialization() {
let cite = LegalCitation::statute("42 U.S.C. § 1983").at(Pinpoint::section("1983"));
let json = serde_json::to_string(&cite).unwrap();
assert!(json.contains("\"category\":\"statute\""));
assert!(json.contains("\"citation\":\"42 U.S.C. § 1983\""));
}
#[test]
fn test_citation_type_display() {
assert_eq!(LegalCitationType::Case.to_string(), "Cases");
assert_eq!(LegalCitationType::Statute.to_string(), "Statutes");
assert_eq!(
LegalCitationType::Constitution.to_string(),
"Constitutional Provisions"
);
}
#[test]
fn test_signal_display() {
assert_eq!(CitationSignal::See.to_string(), "See");
assert_eq!(CitationSignal::ButSee.to_string(), "But see");
assert_eq!(CitationSignal::None.to_string(), "");
}
#[test]
fn test_caption_docket_roundtrip() {
let caption = Caption::new("District Court").with_case_number("No. 24-1234");
let mut caption = caption;
caption.docket = Some("DKT-2024-5678".to_string());
let json = serde_json::to_string(&caption).unwrap();
assert!(json.contains("\"docket\":\"DKT-2024-5678\""));
let parsed: Caption = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.docket, Some("DKT-2024-5678".to_string()));
}
#[test]
fn test_caption_without_docket_defaults_to_none() {
let json = r#"{
"court": "Supreme Court",
"plaintiffs": [],
"defendants": [],
"style": "federal"
}"#;
let caption: Caption = serde_json::from_str(json).unwrap();
assert!(caption.docket.is_none());
}
#[test]
fn test_legal_citation_to_extension_mark() {
let cite = LegalCitation::case("Brown v. Board of Education", "347 U.S. 483", 1954);
let mark = cite.to_extension_mark();
assert_eq!(mark.namespace, "legal");
assert_eq!(mark.mark_type, "cite");
assert_eq!(
mark.get_string_attribute("citation"),
Some("Brown v. Board of Education")
);
assert_eq!(mark.get_string_attribute("cite"), Some("347 U.S. 483"));
}
#[test]
fn test_legal_citation_mark_roundtrip() {
let original = LegalCitation::case("Miranda v. Arizona", "384 U.S. 436", 1966)
.with_signal(CitationSignal::See);
let mark = original.to_extension_mark();
let recovered = LegalCitation::from_extension_mark(&mark).unwrap();
assert_eq!(recovered.citation, "Miranda v. Arizona");
assert_eq!(recovered.cite, Some("384 U.S. 436".to_string()));
assert_eq!(recovered.year, Some(1966));
assert_eq!(recovered.signal, Some(CitationSignal::See));
}
#[test]
fn test_legal_citation_from_wrong_mark_returns_none() {
let mark = crate::content::ExtensionMark::new("academic", "equation-ref");
assert!(LegalCitation::from_extension_mark(&mark).is_none());
}
}