use std::time::Duration;
use crate::sbdh::{SbdhHeader, peppol_scope};
pub const SPIS_ICD: &str = "0242";
pub const SPIS_PARTICIPANT_SCHEME: &str = super::PEPPOL_PARTICIPANT_SCHEME;
pub const MLS_TYPE_FAILURE_ONLY: &str = "FAILURE_ONLY";
pub fn spis_participant_id(seat_id: &str) -> String {
format!("{SPIS_ICD}:{}", seat_id.trim())
}
pub fn mls_type(header: &SbdhHeader) -> &str {
header
.scope_value(peppol_scope::MLS_TYPE)
.filter(|v| !v.trim().is_empty())
.unwrap_or(MLS_TYPE_FAILURE_ONLY)
}
pub fn positive_mls_requested(header: &SbdhHeader) -> bool {
mls_type(header) != MLS_TYPE_FAILURE_ONLY
}
pub fn explicit_mls_receiver(header: &SbdhHeader) -> Option<MlsReceiver<'_>> {
header.scope(peppol_scope::MLS_TO).map(|scope| MlsReceiver {
participant_id: scope.instance_identifier.as_str(),
scheme: scope
.identifier
.as_deref()
.unwrap_or(SPIS_PARTICIPANT_SCHEME),
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MlsReceiver<'a> {
pub participant_id: &'a str,
pub scheme: &'a str,
}
pub const SLR_LATEST_MLS_SENDING: Duration = Duration::from_secs(20 * 60);
pub const SLR_LATEST_MLS_RECEPTION: Duration = Duration::from_secs(25 * 60);
pub const SLR_PAYLOAD_SIZE_LIMIT_BYTES: u64 = 10 * 1_000_000;
pub fn milestone_m1(user_message: &crate::as4::ParsedAs4UserMessage) -> Option<&str> {
user_message
.timestamp
.as_deref()
.map(str::trim)
.filter(|s| !s.is_empty())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sbdh::{SbdhDocumentIdentification, SbdhParty, SbdhScope};
fn header(scopes: Vec<SbdhScope>) -> SbdhHeader {
SbdhHeader {
header_version: "1.0".into(),
sender: SbdhParty {
identifier: "0088:1".into(),
authority: SPIS_PARTICIPANT_SCHEME.into(),
},
receiver: SbdhParty {
identifier: "0088:2".into(),
authority: SPIS_PARTICIPANT_SCHEME.into(),
},
business_scope: scopes,
document_identification: SbdhDocumentIdentification {
standard: "s".into(),
type_version: "2.1".into(),
instance_identifier: "i".into(),
r#type: "Invoice".into(),
multiple_type: false,
creation_date_and_time: "2026-01-01T00:00:00Z".into(),
},
}
}
#[test]
fn spis_identifier_matches_the_policy_example() {
assert_eq!(spis_participant_id("987654"), "0242:987654");
assert_eq!(spis_participant_id(" 987654 "), "0242:987654");
}
#[test]
fn an_absent_mls_type_defaults_to_failure_only() {
let h = header(Vec::new());
assert_eq!(mls_type(&h), MLS_TYPE_FAILURE_ONLY);
assert!(!positive_mls_requested(&h));
}
#[test]
fn an_empty_mls_type_defaults_to_failure_only() {
let h = header(vec![SbdhScope::new(peppol_scope::MLS_TYPE, " ")]);
assert_eq!(mls_type(&h), MLS_TYPE_FAILURE_ONLY);
assert!(!positive_mls_requested(&h));
}
#[test]
fn an_explicit_mls_type_is_returned_verbatim() {
let h = header(vec![SbdhScope::new(peppol_scope::MLS_TYPE, "ALL")]);
assert_eq!(mls_type(&h), "ALL");
assert!(positive_mls_requested(&h));
}
#[test]
fn an_explicit_mls_receiver_is_read_with_its_scheme() {
let h = header(vec![SbdhScope::with_scheme(
peppol_scope::MLS_TO,
"0242:987654-TEST",
"iso6523-actorid-upis",
)]);
let to = explicit_mls_receiver(&h).expect("MLS_TO present");
assert_eq!(to.participant_id, "0242:987654-TEST");
assert_eq!(to.scheme, "iso6523-actorid-upis");
}
#[test]
fn an_mls_receiver_without_a_scheme_falls_back_to_the_participant_scheme() {
let h = header(vec![SbdhScope::new(peppol_scope::MLS_TO, "0242:987654")]);
let to = explicit_mls_receiver(&h).expect("MLS_TO present");
assert_eq!(to.scheme, SPIS_PARTICIPANT_SCHEME);
}
#[test]
fn no_mls_to_means_the_sending_service_providers_own_registration() {
assert!(explicit_mls_receiver(&header(Vec::new())).is_none());
}
#[test]
fn service_level_windows_match_the_policy() {
assert_eq!(SLR_LATEST_MLS_SENDING.as_secs(), 1_200);
assert_eq!(SLR_LATEST_MLS_RECEPTION.as_secs(), 1_500);
assert_eq!(SLR_PAYLOAD_SIZE_LIMIT_BYTES, 10_000_000);
}
}