1use serde::{Deserialize, Serialize};
4use std::net::IpAddr;
5
6#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
8pub enum ValidationState {
9 Speculative,
11 Confirmed,
13 Rejected,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
19pub struct OriginCandidate {
20 pub ip: IpAddr,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
31 pub port: Option<u16>,
32 pub method: String,
34 pub confidence: u8,
36 pub validated: ValidationState,
38}
39
40impl OriginCandidate {
41 pub fn new(ip: IpAddr, method: impl Into<String>, confidence: u8) -> Self {
43 Self {
44 ip,
45 port: None,
46 method: method.into(),
47 confidence,
48 validated: ValidationState::Speculative,
49 }
50 }
51
52 pub fn new_with_port(ip: IpAddr, port: u16, method: impl Into<String>, confidence: u8) -> Self {
57 Self {
58 ip,
59 port: Some(port),
60 method: method.into(),
61 confidence,
62 validated: ValidationState::Speculative,
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use serde_json::json;
71
72 #[test]
73 fn origin_candidate_orders_by_fields() {
74 let low = OriginCandidate {
75 ip: "192.0.2.1".parse().unwrap(),
76 port: None,
77 method: "dns".into(),
78 confidence: 20,
79 validated: ValidationState::Speculative,
80 };
81 let high = OriginCandidate {
82 ip: "192.0.2.2".parse().unwrap(),
83 port: None,
84 method: "ssl".into(),
85 confidence: 90,
86 validated: ValidationState::Confirmed,
87 };
88 assert!(high > low);
89 }
90
91 #[test]
92 fn origin_candidate_serializes_cleanly() {
93 let candidate = OriginCandidate::new("192.0.2.10".parse().unwrap(), "http_header", 75);
94 let value = serde_json::to_value(candidate).unwrap();
95 assert_eq!(value["ip"], json!("192.0.2.10"));
96 assert_eq!(value["method"], json!("http_header"));
97 assert_eq!(value["confidence"], json!(75));
98 assert_eq!(value["validated"], json!("Speculative"));
99 }
100}