uptrakit_web_api_types/
autodiscovery.rs1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3use uuid::Uuid;
4
5use crate::validation::{Validate, ValidationError};
6
7#[derive(Serialize, Deserialize)]
9#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
10pub struct TriggerDiscoveryResponse {
11 pub plugins_queued: u32,
13 pub message: String,
15}
16
17#[derive(Serialize, Deserialize)]
19#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
20pub struct SoftwareIgnoreResponse {
21 pub id: Uuid,
23 pub name: String,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub host_id: Option<Uuid>,
29 #[serde(with = "time::serde::rfc3339")]
30 #[cfg_attr(feature = "openapi", schema(value_type = String, format = DateTime))]
31 pub created_at: OffsetDateTime,
32}
33
34#[derive(Serialize, Deserialize)]
36#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
37pub struct CreateSoftwareIgnoreRequest {
38 pub name: String,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
43 pub host_id: Option<Uuid>,
44}
45
46impl Validate for CreateSoftwareIgnoreRequest {
47 fn validate(&self) -> Result<(), ValidationError> {
48 if self.name.trim().is_empty() {
49 return Err(ValidationError {
50 field: "name",
51 message: "name must not be empty".to_string(),
52 });
53 }
54 Ok(())
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 #![expect(
61 clippy::assertions_on_result_states,
62 reason = "test assertions — is_ok/is_err provides readable failure messages"
63 )]
64 use super::*;
65
66 fn make_request(name: &str) -> CreateSoftwareIgnoreRequest {
67 CreateSoftwareIgnoreRequest {
68 name: name.to_string(),
69 host_id: None,
70 }
71 }
72
73 #[test]
74 fn validate_accepts_non_empty_name() {
75 assert!(make_request("FreshRSS").validate().is_ok());
76 }
77
78 #[test]
79 fn validate_rejects_empty_name() {
80 let err = make_request("").validate().unwrap_err();
81 assert_eq!(err.field, "name");
82 }
83
84 #[test]
85 fn validate_rejects_whitespace_only_name() {
86 assert!(make_request(" ").validate().is_err());
87 }
88
89 #[test]
90 fn software_ignore_response_round_trip_tenant_wide() {
91 use time::macros::datetime;
92 let resp = SoftwareIgnoreResponse {
93 id: Uuid::nil(),
94 name: "FreshRSS".to_string(),
95 host_id: None,
96 created_at: datetime!(2025-01-01 0:00:00 UTC),
97 };
98 let json = serde_json::to_string(&resp).expect("serialization should succeed");
99 let deserialized: SoftwareIgnoreResponse =
100 serde_json::from_str(&json).expect("deserialization should succeed");
101 assert_eq!(deserialized.name, "FreshRSS");
102 assert!(deserialized.host_id.is_none());
103 assert!(!json.contains("host_id"));
105 }
106
107 #[test]
108 fn software_ignore_response_round_trip_per_host() {
109 use time::macros::datetime;
110 let host = Uuid::parse_str("a1a2a3a4-b1b2-c1c2-d1d2-e1e2e3e4e5e6").expect("valid uuid");
111 let resp = SoftwareIgnoreResponse {
112 id: Uuid::nil(),
113 name: "FreshRSS".to_string(),
114 host_id: Some(host),
115 created_at: datetime!(2025-01-01 0:00:00 UTC),
116 };
117 let json = serde_json::to_string(&resp).expect("serialization should succeed");
118 let deserialized: SoftwareIgnoreResponse =
119 serde_json::from_str(&json).expect("deserialization should succeed");
120 assert_eq!(deserialized.host_id, Some(host));
121 }
122
123 #[test]
124 fn create_request_with_host_id() {
125 let host = Uuid::parse_str("a1a2a3a4-b1b2-c1c2-d1d2-e1e2e3e4e5e6").expect("valid uuid");
126 let req = CreateSoftwareIgnoreRequest {
127 name: "FreshRSS".to_string(),
128 host_id: Some(host),
129 };
130 assert!(req.validate().is_ok());
131 let json = serde_json::to_string(&req).expect("serialization should succeed");
132 let deserialized: CreateSoftwareIgnoreRequest =
133 serde_json::from_str(&json).expect("deserialization should succeed");
134 assert_eq!(deserialized.host_id, Some(host));
135 }
136}