1use serde::{Deserialize, Serialize};
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct TableOfAuthorities {
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub id: Option<String>,
38
39 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub title: Option<String>,
42
43 #[serde(default, skip_serializing_if = "Vec::is_empty")]
45 pub categories: Vec<CitationCategory>,
46
47 #[serde(default = "default_true")]
49 pub auto_generate: bool,
50
51 #[serde(default)]
53 pub format: LegalCitationFormat,
54}
55
56fn default_true() -> bool {
57 true
58}
59
60impl TableOfAuthorities {
61 #[must_use]
63 pub fn new() -> Self {
64 Self {
65 id: None,
66 title: None,
67 categories: Vec::new(),
68 auto_generate: true,
69 format: LegalCitationFormat::default(),
70 }
71 }
72
73 #[must_use]
75 pub fn with_title(mut self, title: impl Into<String>) -> Self {
76 self.title = Some(title.into());
77 self
78 }
79
80 #[must_use]
82 pub fn with_category(mut self, category: CitationCategory) -> Self {
83 self.categories.push(category);
84 self
85 }
86
87 #[must_use]
89 pub fn with_format(mut self, format: LegalCitationFormat) -> Self {
90 self.format = format;
91 self
92 }
93}
94
95impl Default for TableOfAuthorities {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct CitationCategory {
105 pub category_type: LegalCitationType,
107
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub heading: Option<String>,
111
112 #[serde(default, skip_serializing_if = "Vec::is_empty")]
114 pub entries: Vec<TableOfAuthoritiesEntry>,
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
119#[serde(rename_all = "camelCase")]
120pub struct TableOfAuthoritiesEntry {
121 pub citation: String,
123
124 pub pages: Vec<String>,
126
127 #[serde(default)]
129 pub primary: bool,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct Caption {
140 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub id: Option<String>,
143
144 pub court: String,
146
147 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub case_number: Option<String>,
150
151 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub docket: Option<String>,
154
155 pub plaintiffs: Vec<Party>,
157
158 pub defendants: Vec<Party>,
160
161 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub title: Option<String>,
164
165 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub document_type: Option<String>,
168
169 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub judge: Option<String>,
172
173 #[serde(default)]
175 pub style: CaptionStyle,
176}
177
178impl Caption {
179 #[must_use]
181 pub fn new(court: impl Into<String>) -> Self {
182 Self {
183 id: None,
184 court: court.into(),
185 case_number: None,
186 docket: None,
187 plaintiffs: Vec::new(),
188 defendants: Vec::new(),
189 title: None,
190 document_type: None,
191 judge: None,
192 style: CaptionStyle::default(),
193 }
194 }
195
196 #[must_use]
198 pub fn with_case_number(mut self, case_number: impl Into<String>) -> Self {
199 self.case_number = Some(case_number.into());
200 self
201 }
202
203 #[must_use]
205 pub fn with_plaintiff(mut self, party: Party) -> Self {
206 self.plaintiffs.push(party);
207 self
208 }
209
210 #[must_use]
212 pub fn with_defendant(mut self, party: Party) -> Self {
213 self.defendants.push(party);
214 self
215 }
216
217 #[must_use]
219 pub fn with_document_type(mut self, doc_type: impl Into<String>) -> Self {
220 self.document_type = Some(doc_type.into());
221 self
222 }
223}
224
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct Party {
229 pub name: String,
231
232 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub role: Option<String>,
235
236 #[serde(default)]
238 pub primary: bool,
239}
240
241impl Party {
242 #[must_use]
244 pub fn new(name: impl Into<String>) -> Self {
245 Self {
246 name: name.into(),
247 role: None,
248 primary: false,
249 }
250 }
251
252 #[must_use]
254 pub fn with_role(mut self, role: impl Into<String>) -> Self {
255 self.role = Some(role.into());
256 self
257 }
258
259 #[must_use]
261 pub fn primary(mut self) -> Self {
262 self.primary = true;
263 self
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
269#[serde(rename_all = "lowercase")]
270pub enum CaptionStyle {
271 #[default]
273 Federal,
274 California,
276 NewYork,
278 Texas,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct LegalSignatureBlock {
290 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub id: Option<String>,
293
294 #[serde(default, skip_serializing_if = "Option::is_none")]
296 pub role: Option<String>,
297
298 pub signer: LegalSigner,
300
301 #[serde(default, skip_serializing_if = "Option::is_none")]
303 pub date: Option<String>,
304
305 #[serde(default)]
307 pub certificate_of_service: bool,
308}
309
310impl LegalSignatureBlock {
311 #[must_use]
313 pub fn new(signer: LegalSigner) -> Self {
314 Self {
315 id: None,
316 role: None,
317 signer,
318 date: None,
319 certificate_of_service: false,
320 }
321 }
322
323 #[must_use]
325 pub fn with_role(mut self, role: impl Into<String>) -> Self {
326 self.role = Some(role.into());
327 self
328 }
329
330 #[must_use]
332 pub fn with_date(mut self, date: impl Into<String>) -> Self {
333 self.date = Some(date.into());
334 self
335 }
336
337 #[must_use]
339 pub fn with_certificate_of_service(mut self) -> Self {
340 self.certificate_of_service = true;
341 self
342 }
343}
344
345#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
347#[serde(rename_all = "camelCase")]
348pub struct LegalSigner {
349 pub name: String,
351
352 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub title: Option<String>,
355
356 #[serde(default, skip_serializing_if = "Option::is_none")]
358 pub bar_number: Option<String>,
359
360 #[serde(default, skip_serializing_if = "Option::is_none")]
362 pub firm: Option<String>,
363
364 #[serde(default, skip_serializing_if = "Option::is_none")]
366 pub address: Option<String>,
367
368 #[serde(default, skip_serializing_if = "Option::is_none")]
370 pub telephone: Option<String>,
371}
372
373impl LegalSigner {
374 #[must_use]
376 pub fn new(name: impl Into<String>) -> Self {
377 Self {
378 name: name.into(),
379 title: None,
380 bar_number: None,
381 firm: None,
382 address: None,
383 telephone: None,
384 }
385 }
386
387 #[must_use]
389 pub fn with_title(mut self, title: impl Into<String>) -> Self {
390 self.title = Some(title.into());
391 self
392 }
393
394 #[must_use]
396 pub fn with_bar_number(mut self, bar_number: impl Into<String>) -> Self {
397 self.bar_number = Some(bar_number.into());
398 self
399 }
400
401 #[must_use]
403 pub fn with_firm(mut self, firm: impl Into<String>) -> Self {
404 self.firm = Some(firm.into());
405 self
406 }
407
408 #[must_use]
410 pub fn with_address(mut self, address: impl Into<String>) -> Self {
411 self.address = Some(address.into());
412 self
413 }
414
415 #[must_use]
417 pub fn with_telephone(mut self, telephone: impl Into<String>) -> Self {
418 self.telephone = Some(telephone.into());
419 self
420 }
421}
422
423#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
429#[serde(rename_all = "camelCase")]
430pub struct LegalCitation {
431 pub citation: String,
433
434 #[serde(default, skip_serializing_if = "Option::is_none")]
436 pub short_form: Option<String>,
437
438 #[serde(default, skip_serializing_if = "Option::is_none")]
440 pub cite: Option<String>,
441
442 #[serde(default, skip_serializing_if = "Option::is_none")]
444 pub year: Option<u16>,
445
446 pub category: LegalCitationType,
448
449 #[serde(default, skip_serializing_if = "Option::is_none")]
451 pub pinpoint: Option<Pinpoint>,
452
453 #[serde(default, skip_serializing_if = "Option::is_none")]
455 pub parenthetical: Option<String>,
456
457 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub signal: Option<CitationSignal>,
460
461 #[serde(default = "default_true")]
463 pub first_reference: bool,
464}
465
466impl LegalCitation {
467 #[must_use]
469 pub fn case(citation: impl Into<String>, cite: impl Into<String>, year: u16) -> Self {
470 Self {
471 citation: citation.into(),
472 short_form: None,
473 cite: Some(cite.into()),
474 year: Some(year),
475 category: LegalCitationType::Case,
476 pinpoint: None,
477 parenthetical: None,
478 signal: None,
479 first_reference: true,
480 }
481 }
482
483 #[must_use]
485 pub fn statute(citation: impl Into<String>) -> Self {
486 Self {
487 citation: citation.into(),
488 short_form: None,
489 cite: None,
490 year: None,
491 category: LegalCitationType::Statute,
492 pinpoint: None,
493 parenthetical: None,
494 signal: None,
495 first_reference: true,
496 }
497 }
498
499 #[must_use]
501 pub fn with_short_form(mut self, short_form: impl Into<String>) -> Self {
502 self.short_form = Some(short_form.into());
503 self
504 }
505
506 #[must_use]
508 pub fn at(mut self, pinpoint: Pinpoint) -> Self {
509 self.pinpoint = Some(pinpoint);
510 self
511 }
512
513 #[must_use]
515 pub fn with_parenthetical(mut self, text: impl Into<String>) -> Self {
516 self.parenthetical = Some(text.into());
517 self
518 }
519
520 #[must_use]
522 pub fn with_signal(mut self, signal: CitationSignal) -> Self {
523 self.signal = Some(signal);
524 self
525 }
526
527 #[must_use]
529 pub fn subsequent(mut self) -> Self {
530 self.first_reference = false;
531 self
532 }
533
534 #[must_use]
536 pub fn to_extension_mark(&self) -> crate::content::ExtensionMark {
537 let attrs = serde_json::to_value(self).unwrap_or_default();
538 crate::content::ExtensionMark::new("legal", "cite").with_attributes(attrs)
539 }
540
541 #[must_use]
546 pub fn from_extension_mark(mark: &crate::content::ExtensionMark) -> Option<Self> {
547 if mark.is_type("legal", "cite") {
548 serde_json::from_value(mark.attributes.clone()).ok()
549 } else {
550 None
551 }
552 }
553}
554
555#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
557#[serde(rename_all = "lowercase")]
558pub enum LegalCitationType {
559 #[strum(serialize = "Cases")]
561 Case,
562 #[strum(serialize = "Statutes")]
564 Statute,
565 #[strum(serialize = "Regulations")]
567 Regulation,
568 #[strum(serialize = "Constitutional Provisions")]
570 Constitution,
571 #[strum(serialize = "Secondary Sources")]
573 Secondary,
574 #[strum(serialize = "Books")]
576 Book,
577 #[strum(serialize = "Law Review Articles")]
579 LawReview,
580 #[strum(serialize = "Legislative History")]
582 Legislative,
583 #[strum(serialize = "Other Authorities")]
585 Other,
586}
587
588#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
590#[serde(rename_all = "camelCase")]
591pub struct Pinpoint {
592 pub pinpoint_type: PinpointType,
594
595 pub value: String,
597}
598
599impl Pinpoint {
600 #[must_use]
602 pub fn page(page: impl Into<String>) -> Self {
603 Self {
604 pinpoint_type: PinpointType::Page,
605 value: page.into(),
606 }
607 }
608
609 #[must_use]
611 pub fn section(section: impl Into<String>) -> Self {
612 Self {
613 pinpoint_type: PinpointType::Section,
614 value: section.into(),
615 }
616 }
617
618 #[must_use]
620 pub fn paragraph(para: impl Into<String>) -> Self {
621 Self {
622 pinpoint_type: PinpointType::Paragraph,
623 value: para.into(),
624 }
625 }
626}
627
628#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
630#[serde(rename_all = "lowercase")]
631pub enum PinpointType {
632 Page,
634 Section,
636 Paragraph,
638 Footnote,
640 Clause,
642 Article,
644}
645
646#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::Display)]
648#[serde(rename_all = "lowercase")]
649pub enum CitationSignal {
650 #[strum(serialize = "")]
652 None,
653 #[strum(serialize = "E.g.")]
655 Eg,
656 Accord,
658 See,
660 #[strum(serialize = "See also")]
662 SeeAlso,
663 #[strum(serialize = "Cf.")]
665 Cf,
666 Compare,
668 Contra,
670 #[strum(serialize = "But see")]
672 ButSee,
673 #[strum(serialize = "But cf.")]
675 ButCf,
676 #[strum(serialize = "See generally")]
678 SeeGenerally,
679}
680
681#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, strum::Display)]
683#[serde(rename_all = "lowercase")]
684pub enum LegalCitationFormat {
685 #[default]
687 Bluebook,
688 #[strum(serialize = "ALWD")]
690 Alwd,
691 McGill,
693 #[strum(serialize = "OSCOLA")]
695 Oscola,
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701
702 #[test]
703 fn test_caption_new() {
704 let caption = Caption::new("United States District Court, Southern District of New York");
705 assert_eq!(
706 caption.court,
707 "United States District Court, Southern District of New York"
708 );
709 assert!(caption.plaintiffs.is_empty());
710 assert!(caption.defendants.is_empty());
711 }
712
713 #[test]
714 fn test_caption_builder() {
715 let caption = Caption::new("Supreme Court of the United States")
716 .with_case_number("No. 21-1234")
717 .with_plaintiff(Party::new("John Doe").primary())
718 .with_defendant(Party::new("Acme Corp"))
719 .with_document_type("Brief for Petitioner");
720
721 assert_eq!(caption.case_number, Some("No. 21-1234".to_string()));
722 assert_eq!(caption.plaintiffs.len(), 1);
723 assert!(caption.plaintiffs[0].primary);
724 assert_eq!(caption.defendants.len(), 1);
725 }
726
727 #[test]
728 fn test_legal_citation_case() {
729 let cite = LegalCitation::case("Brown v. Board of Education", "347 U.S. 483", 1954);
730
731 assert_eq!(cite.category, LegalCitationType::Case);
732 assert_eq!(cite.year, Some(1954));
733 assert_eq!(cite.cite, Some("347 U.S. 483".to_string()));
734 }
735
736 #[test]
737 fn test_legal_citation_builder() {
738 let cite = LegalCitation::case("Miranda v. Arizona", "384 U.S. 436", 1966)
739 .with_short_form("Miranda")
740 .at(Pinpoint::page("444"))
741 .with_parenthetical("establishing Miranda warnings");
742
743 assert_eq!(cite.short_form, Some("Miranda".to_string()));
744 assert!(cite.pinpoint.is_some());
745 assert!(cite.parenthetical.is_some());
746 }
747
748 #[test]
749 fn test_legal_signer() {
750 let signer = LegalSigner::new("Jane Doe")
751 .with_bar_number("123456")
752 .with_firm("Smith & Associates")
753 .with_address("123 Legal Way, Suite 100, New York, NY 10001")
754 .with_telephone("(555) 123-4567")
755 .with_title("Partner");
756
757 assert_eq!(signer.bar_number, Some("123456".to_string()));
758 assert_eq!(signer.firm, Some("Smith & Associates".to_string()));
759 assert_eq!(signer.telephone, Some("(555) 123-4567".to_string()));
760 assert_eq!(signer.title, Some("Partner".to_string()));
761 }
762
763 #[test]
764 fn test_legal_signer_serde_roundtrip() {
765 let signer = LegalSigner::new("John Smith")
766 .with_bar_number("789012")
767 .with_firm("Law Corp");
768 let json = serde_json::to_string(&signer).unwrap();
769 assert!(json.contains("\"barNumber\":\"789012\""));
770 assert!(json.contains("\"firm\":\"Law Corp\""));
771
772 let parsed: LegalSigner = serde_json::from_str(&json).unwrap();
773 assert_eq!(parsed.name, "John Smith");
774 assert_eq!(parsed.bar_number, Some("789012".to_string()));
775 }
776
777 #[test]
778 fn test_legal_signature_block_with_role() {
779 let block = LegalSignatureBlock::new(LegalSigner::new("Jane Doe"))
780 .with_role("Attorney for Plaintiff")
781 .with_date("2024-01-15")
782 .with_certificate_of_service();
783
784 assert_eq!(block.role, Some("Attorney for Plaintiff".to_string()));
785 assert_eq!(block.date, Some("2024-01-15".to_string()));
786 assert!(block.certificate_of_service);
787
788 let json = serde_json::to_string(&block).unwrap();
789 assert!(json.contains("\"role\":\"Attorney for Plaintiff\""));
790
791 let parsed: LegalSignatureBlock = serde_json::from_str(&json).unwrap();
792 assert_eq!(parsed.role, Some("Attorney for Plaintiff".to_string()));
793 }
794
795 #[test]
796 fn test_legal_signature_block_backward_compat() {
797 let json = r#"{
799 "signer": { "name": "Test Lawyer" },
800 "certificateOfService": false
801 }"#;
802 let block: LegalSignatureBlock = serde_json::from_str(json).unwrap();
803 assert!(block.role.is_none());
804 assert_eq!(block.signer.name, "Test Lawyer");
805 }
806
807 #[test]
808 fn test_table_of_authorities() {
809 let toa = TableOfAuthorities::new()
810 .with_title("TABLE OF AUTHORITIES")
811 .with_format(LegalCitationFormat::Bluebook);
812
813 assert!(toa.auto_generate);
814 assert_eq!(toa.format, LegalCitationFormat::Bluebook);
815 }
816
817 #[test]
818 fn test_citation_serialization() {
819 let cite = LegalCitation::statute("42 U.S.C. § 1983").at(Pinpoint::section("1983"));
820
821 let json = serde_json::to_string(&cite).unwrap();
822 assert!(json.contains("\"category\":\"statute\""));
823 assert!(json.contains("\"citation\":\"42 U.S.C. § 1983\""));
824 }
825
826 #[test]
827 fn test_citation_type_display() {
828 assert_eq!(LegalCitationType::Case.to_string(), "Cases");
829 assert_eq!(LegalCitationType::Statute.to_string(), "Statutes");
830 assert_eq!(
831 LegalCitationType::Constitution.to_string(),
832 "Constitutional Provisions"
833 );
834 }
835
836 #[test]
837 fn test_signal_display() {
838 assert_eq!(CitationSignal::See.to_string(), "See");
839 assert_eq!(CitationSignal::ButSee.to_string(), "But see");
840 assert_eq!(CitationSignal::None.to_string(), "");
841 }
842
843 #[test]
844 fn test_caption_docket_roundtrip() {
845 let caption = Caption::new("District Court").with_case_number("No. 24-1234");
846 let mut caption = caption;
848 caption.docket = Some("DKT-2024-5678".to_string());
849
850 let json = serde_json::to_string(&caption).unwrap();
851 assert!(json.contains("\"docket\":\"DKT-2024-5678\""));
852
853 let parsed: Caption = serde_json::from_str(&json).unwrap();
854 assert_eq!(parsed.docket, Some("DKT-2024-5678".to_string()));
855 }
856
857 #[test]
858 fn test_caption_without_docket_defaults_to_none() {
859 let json = r#"{
860 "court": "Supreme Court",
861 "plaintiffs": [],
862 "defendants": [],
863 "style": "federal"
864 }"#;
865 let caption: Caption = serde_json::from_str(json).unwrap();
866 assert!(caption.docket.is_none());
867 }
868
869 #[test]
870 fn test_legal_citation_to_extension_mark() {
871 let cite = LegalCitation::case("Brown v. Board of Education", "347 U.S. 483", 1954);
872 let mark = cite.to_extension_mark();
873
874 assert_eq!(mark.namespace, "legal");
875 assert_eq!(mark.mark_type, "cite");
876 assert_eq!(
877 mark.get_string_attribute("citation"),
878 Some("Brown v. Board of Education")
879 );
880 assert_eq!(mark.get_string_attribute("cite"), Some("347 U.S. 483"));
881 }
882
883 #[test]
884 fn test_legal_citation_mark_roundtrip() {
885 let original = LegalCitation::case("Miranda v. Arizona", "384 U.S. 436", 1966)
886 .with_signal(CitationSignal::See);
887 let mark = original.to_extension_mark();
888 let recovered = LegalCitation::from_extension_mark(&mark).unwrap();
889
890 assert_eq!(recovered.citation, "Miranda v. Arizona");
891 assert_eq!(recovered.cite, Some("384 U.S. 436".to_string()));
892 assert_eq!(recovered.year, Some(1966));
893 assert_eq!(recovered.signal, Some(CitationSignal::See));
894 }
895
896 #[test]
897 fn test_legal_citation_from_wrong_mark_returns_none() {
898 let mark = crate::content::ExtensionMark::new("academic", "equation-ref");
899 assert!(LegalCitation::from_extension_mark(&mark).is_none());
900 }
901}