pub mod retention;
pub mod storage;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use uuid::Uuid;
pub use retention::{JoinRequestsConfig, RetentionSweeper, default_retention_days};
pub use storage::{
JOIN_REQUEST_EXTENSIONS_MAX_BYTES, JOIN_REQUEST_VP_MAX_BYTES, delete_join_request,
get_join_request, list_join_requests, list_join_requests_paginated, store_join_request,
};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum JoinStatus {
Pending,
Approved,
Rejected,
Withdrawn,
Deferred,
}
impl JoinStatus {
pub fn is_terminal_retainable(self) -> bool {
matches!(self, JoinStatus::Rejected | JoinStatus::Withdrawn)
}
}
impl std::fmt::Display for JoinStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
JoinStatus::Pending => f.write_str("pending"),
JoinStatus::Approved => f.write_str("approved"),
JoinStatus::Rejected => f.write_str("rejected"),
JoinStatus::Withdrawn => f.write_str("withdrawn"),
JoinStatus::Deferred => f.write_str("deferred"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct JoinRequest {
pub id: Uuid,
pub applicant_did: String,
pub vp: JsonValue,
#[serde(default)]
pub vp_claims: JsonValue,
pub submitted_at: DateTime<Utc>,
pub status: JoinStatus,
#[serde(default)]
pub policy_decision: Option<JsonValue>,
#[serde(default)]
pub registry_consent: bool,
#[serde(default)]
pub extensions: JsonValue,
}
impl JoinRequest {
pub fn new(applicant_did: impl Into<String>, vp: JsonValue) -> Self {
Self {
id: Uuid::new_v4(),
applicant_did: applicant_did.into(),
vp,
vp_claims: JsonValue::Null,
submitted_at: Utc::now(),
status: JoinStatus::Pending,
policy_decision: None,
registry_consent: false,
extensions: JsonValue::Null,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JoinTransport {
Rest,
DIDComm,
}
impl JoinTransport {
pub fn as_str(self) -> &'static str {
match self {
JoinTransport::Rest => "rest",
JoinTransport::DIDComm => "didcomm",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn join_status_terminal_retainable_only_for_rejected_and_withdrawn() {
for (status, expected) in [
(JoinStatus::Pending, false),
(JoinStatus::Approved, false),
(JoinStatus::Rejected, true),
(JoinStatus::Withdrawn, true),
(JoinStatus::Deferred, false),
] {
assert_eq!(status.is_terminal_retainable(), expected, "{status:?}");
}
}
#[test]
fn join_status_display_is_lowercase() {
assert_eq!(JoinStatus::Pending.to_string(), "pending");
assert_eq!(JoinStatus::Approved.to_string(), "approved");
assert_eq!(JoinStatus::Deferred.to_string(), "deferred");
}
#[test]
fn join_request_new_uses_pending_status() {
let r = JoinRequest::new("did:key:z", serde_json::json!({}));
assert_eq!(r.status, JoinStatus::Pending);
assert_eq!(r.policy_decision, None);
assert!(!r.registry_consent);
}
#[test]
fn join_transport_str_round_trip() {
assert_eq!(JoinTransport::Rest.as_str(), "rest");
assert_eq!(JoinTransport::DIDComm.as_str(), "didcomm");
}
}