use crate::bundle_deployment::{RevenueShareEntry, validate_revenue_share_total};
use crate::error::SpecError;
use crate::ids::{BundleId, CustomerId, DeploymentId};
use crate::version::SchemaVersion;
use chrono::{DateTime, Utc};
use greentic_types::EnvId;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RevenuePolicyDocument {
pub schema: SchemaVersion,
pub version: u64,
pub deployment_id: DeploymentId,
pub env_id: EnvId,
pub bundle_id: BundleId,
pub customer_id: CustomerId,
pub revenue_share: Vec<RevenueShareEntry>,
pub created_at: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub previous_version_ref: Option<PathBuf>,
}
impl RevenuePolicyDocument {
pub fn schema_str() -> &'static str {
SchemaVersion::REVENUE_POLICY_V1
}
pub fn validate(&self) -> Result<(), SpecError> {
if self.schema.as_str() != SchemaVersion::REVENUE_POLICY_V1 {
return Err(SpecError::SchemaMismatch {
expected: SchemaVersion::REVENUE_POLICY_V1,
actual: self.schema.as_str().to_string(),
});
}
if self.version == 0 {
return Err(SpecError::RevenuePolicyVersionZero);
}
validate_revenue_share_total(&self.revenue_share)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ids::PartyId;
fn doc(version: u64, shares: Vec<(&str, u32)>) -> RevenuePolicyDocument {
RevenuePolicyDocument {
schema: SchemaVersion::new(SchemaVersion::REVENUE_POLICY_V1),
version,
deployment_id: DeploymentId::new(),
env_id: EnvId::try_from("local").unwrap(),
bundle_id: BundleId::new("fast2flow"),
customer_id: CustomerId::new("local-dev"),
revenue_share: shares
.into_iter()
.map(|(p, bps)| RevenueShareEntry {
party_id: PartyId::new(p),
basis_points: bps,
})
.collect(),
created_at: Utc::now(),
previous_version_ref: None,
}
}
#[test]
fn valid_document_passes() {
assert!(doc(1, vec![("greentic", 10_000)]).validate().is_ok());
assert!(
doc(2, vec![("agency-a", 3_000), ("greentic", 7_000)])
.validate()
.is_ok()
);
}
#[test]
fn wrong_schema_rejected() {
let mut d = doc(1, vec![("greentic", 10_000)]);
d.schema = SchemaVersion::new("greentic.revenue-policy.v2");
assert!(matches!(
d.validate(),
Err(SpecError::SchemaMismatch { .. })
));
}
#[test]
fn version_zero_rejected() {
assert!(matches!(
doc(0, vec![("greentic", 10_000)]).validate(),
Err(SpecError::RevenuePolicyVersionZero)
));
}
#[test]
fn basis_points_sum_enforced() {
assert!(matches!(
doc(1, vec![("greentic", 9_999)]).validate(),
Err(SpecError::BasisPointsSum { sum: 9_999 })
));
}
#[test]
fn per_entry_overflow_rejected() {
assert!(matches!(
doc(1, vec![("a", u32::MAX), ("b", 10_001)]).validate(),
Err(SpecError::BasisPointsEntryTooLarge { .. })
));
}
}