1use crate::org_membership::MembershipOutcome;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum InboundMode {
23 Auto,
25 Notify,
27}
28
29pub trait OrgPolicy {
33 fn inbound_mode(&self, org_did: &str) -> Option<InboundMode>;
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum PairAction {
39 AutoOrgVerified { org_did: String },
41 NotifyOrgEligible { org_did: String },
44 Manual,
47}
48
49pub fn decide(outcome: &MembershipOutcome, policy: &dyn OrgPolicy) -> PairAction {
56 let org_dids = match outcome {
57 MembershipOutcome::Verified { org_dids, .. } => org_dids,
58 MembershipOutcome::NoClaim | MembershipOutcome::Rejected { .. } => {
60 return PairAction::Manual;
61 }
62 };
63
64 if let Some(org_did) = org_dids
67 .iter()
68 .find(|&od| policy.inbound_mode(od) == Some(InboundMode::Auto))
69 {
70 return PairAction::AutoOrgVerified {
71 org_did: org_did.clone(),
72 };
73 }
74 if let Some(org_did) = org_dids
75 .iter()
76 .find(|&od| policy.inbound_mode(od) == Some(InboundMode::Notify))
77 {
78 return PairAction::NotifyOrgEligible {
79 org_did: org_did.clone(),
80 };
81 }
82 PairAction::Manual
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use std::collections::HashMap;
89
90 struct MapPolicy(HashMap<String, InboundMode>);
91 impl OrgPolicy for MapPolicy {
92 fn inbound_mode(&self, org_did: &str) -> Option<InboundMode> {
93 self.0.get(org_did).copied()
94 }
95 }
96
97 fn verified(orgs: &[&str]) -> MembershipOutcome {
98 MembershipOutcome::Verified {
99 op_did:
100 "did:wire:op:darby-0000000000000000000000000000000000000000000000000000000000000000"
101 .into(),
102 org_dids: orgs.iter().map(|s| s.to_string()).collect(),
103 }
104 }
105
106 fn policy(entries: &[(&str, InboundMode)]) -> MapPolicy {
107 MapPolicy(entries.iter().map(|(k, v)| (k.to_string(), *v)).collect())
108 }
109
110 #[test]
111 fn auto_when_org_opted_into_auto() {
112 let out = verified(&["did:wire:org:slanchaai-1"]);
113 let pol = policy(&[("did:wire:org:slanchaai-1", InboundMode::Auto)]);
114 assert_eq!(
115 decide(&out, &pol),
116 PairAction::AutoOrgVerified {
117 org_did: "did:wire:org:slanchaai-1".into()
118 }
119 );
120 }
121
122 #[test]
123 fn notify_when_org_is_notify() {
124 let out = verified(&["did:wire:org:slanchaai-1"]);
125 let pol = policy(&[("did:wire:org:slanchaai-1", InboundMode::Notify)]);
126 assert_eq!(
127 decide(&out, &pol),
128 PairAction::NotifyOrgEligible {
129 org_did: "did:wire:org:slanchaai-1".into()
130 }
131 );
132 }
133
134 #[test]
135 fn manual_when_org_untrusted() {
136 let out = verified(&["did:wire:org:stranger-1"]);
137 let pol = policy(&[("did:wire:org:slanchaai-1", InboundMode::Auto)]);
138 assert_eq!(decide(&out, &pol), PairAction::Manual);
139 }
140
141 #[test]
142 fn auto_beats_notify_across_orgs() {
143 let out = verified(&["did:wire:org:notifyco-1", "did:wire:org:autoco-1"]);
145 let pol = policy(&[
146 ("did:wire:org:notifyco-1", InboundMode::Notify),
147 ("did:wire:org:autoco-1", InboundMode::Auto),
148 ]);
149 assert_eq!(
150 decide(&out, &pol),
151 PairAction::AutoOrgVerified {
152 org_did: "did:wire:org:autoco-1".into()
153 }
154 );
155 }
156
157 #[test]
158 fn manual_on_no_claim() {
159 let pol = policy(&[("did:wire:org:slanchaai-1", InboundMode::Auto)]);
160 assert_eq!(
161 decide(&MembershipOutcome::NoClaim, &pol),
162 PairAction::Manual
163 );
164 }
165
166 #[test]
167 fn manual_on_rejected() {
168 let pol = policy(&[("did:wire:org:slanchaai-1", InboundMode::Auto)]);
169 let rejected = MembershipOutcome::Rejected {
170 reason: "forged".into(),
171 };
172 assert_eq!(decide(&rejected, &pol), PairAction::Manual);
173 }
174}