use std::fmt;
use std::str::FromStr;
use super::entity_ref::EntityRef;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DecisionRecordRef(EntityRef);
impl serde::Serialize for DecisionRecordRef {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.0.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct IssueRef(EntityRef);
impl serde::Serialize for IssueRef {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.0.as_str())
}
}
impl DecisionRecordRef {
pub fn new(s: impl Into<String>) -> anyhow::Result<Self> {
EntityRef::new(s).map(DecisionRecordRef)
}
pub fn parse_v3(s: &str) -> anyhow::Result<Self> {
EntityRef::parse_v3(s).map(DecisionRecordRef)
}
pub fn parse_v4(s: &str) -> anyhow::Result<Self> {
EntityRef::parse_v4(s).map(DecisionRecordRef)
}
pub fn parse_v5(s: &str) -> anyhow::Result<Self> {
EntityRef::parse_v5(s).map(DecisionRecordRef)
}
pub fn parse_any(s: &str) -> anyhow::Result<Self> {
EntityRef::parse_any(s).map(DecisionRecordRef)
}
pub fn as_entity_ref(&self) -> &EntityRef {
&self.0
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn numeric_id(&self) -> u64 {
self.0.numeric_id()
}
pub fn suffix(&self) -> &str {
self.0.suffix()
}
pub fn prefix(&self) -> &str {
self.0.prefix()
}
}
impl fmt::Display for DecisionRecordRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for DecisionRecordRef {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
DecisionRecordRef::new(s)
}
}
impl IssueRef {
pub fn new(s: impl Into<String>) -> anyhow::Result<Self> {
EntityRef::new(s).map(IssueRef)
}
pub fn parse_v3(s: &str) -> anyhow::Result<Self> {
EntityRef::parse_v3(s).map(IssueRef)
}
pub fn parse_v4(s: &str) -> anyhow::Result<Self> {
EntityRef::parse_v4(s).map(IssueRef)
}
pub fn parse_v5(s: &str) -> anyhow::Result<Self> {
EntityRef::parse_v5(s).map(IssueRef)
}
pub fn parse_any(s: &str) -> anyhow::Result<Self> {
EntityRef::parse_any(s).map(IssueRef)
}
pub fn as_entity_ref(&self) -> &EntityRef {
&self.0
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn numeric_id(&self) -> u64 {
self.0.numeric_id()
}
pub fn suffix(&self) -> &str {
self.0.suffix()
}
pub fn prefix(&self) -> &str {
self.0.prefix()
}
}
impl fmt::Display for IssueRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for IssueRef {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
IssueRef::new(s)
}
}
#[cfg(test)]
pub mod strategy {
use super::*;
use proptest::prelude::*;
pub fn decision_record_ref() -> impl Strategy<Value = DecisionRecordRef> {
(
proptest::sample::select(vec!["ADR", "DDR", "GDDR"]),
1u32..10_000,
)
.prop_map(|(prefix, n)| DecisionRecordRef::new(format!("{prefix}-{n:04}")).unwrap())
}
pub fn issue_ref() -> impl Strategy<Value = IssueRef> {
(1u32..10_000).prop_map(|n| IssueRef::new(format!("ISSUE-{n:04}")).unwrap())
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn decision_record_ref_accepts_valid() {
let r = DecisionRecordRef::new("ADR-0001").unwrap();
assert_eq!(r.as_str(), "ADR-0001");
assert_eq!(r.prefix(), "ADR");
assert_eq!(r.numeric_id(), 1);
}
#[test]
fn issue_ref_accepts_valid() {
let r = IssueRef::new("ISSUE-0042").unwrap();
assert_eq!(r.as_str(), "ISSUE-0042");
assert_eq!(r.prefix(), "ISSUE");
assert_eq!(r.numeric_id(), 42);
}
#[test]
fn decision_record_ref_rejects_bare_integer() {
assert!(DecisionRecordRef::new("42").is_err());
assert!(DecisionRecordRef::new("0001").is_err());
}
#[test]
fn issue_ref_rejects_bare_integer() {
assert!(IssueRef::new("42").is_err());
}
#[test]
fn display_roundtrips_decision_record_ref() {
let r = DecisionRecordRef::new("ADR-0007").unwrap();
assert_eq!(r.to_string(), "ADR-0007");
}
#[test]
fn display_roundtrips_issue_ref() {
let r = IssueRef::new("ISSUE-0007").unwrap();
assert_eq!(r.to_string(), "ISSUE-0007");
}
#[test]
fn decision_record_ref_and_issue_ref_are_distinct_types() {
let _dr: DecisionRecordRef = DecisionRecordRef::new("ADR-0001").unwrap();
let _ir: IssueRef = IssueRef::new("ISSUE-0001").unwrap();
}
#[test]
fn ordering_is_by_full_string() {
let a = DecisionRecordRef::new("ADR-0001").unwrap();
let b = DecisionRecordRef::new("ADR-0002").unwrap();
assert!(a < b);
}
proptest! {
#[test]
fn prop_decision_record_ref_display_roundtrips(
r in strategy::decision_record_ref()
) {
prop_assert_eq!(r.to_string().parse::<DecisionRecordRef>().unwrap(), r);
}
#[test]
fn prop_issue_ref_display_roundtrips(r in strategy::issue_ref()) {
prop_assert_eq!(r.to_string().parse::<IssueRef>().unwrap(), r);
}
}
}