tauri_plugin_widgets/
apply.rs1use serde::{Deserialize, Serialize};
4use std::collections::hash_map::DefaultHasher;
5use std::hash::{Hash, Hasher};
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(tag = "reason", rename_all = "camelCase")]
10pub enum SkipReason {
11 Unchanged {
13 hash: u64,
15 },
16 NoInstances,
18 TransportUnavailable {
20 name: String,
22 },
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(tag = "outcome", rename_all = "camelCase")]
28pub enum ReloadOutcome {
29 Ok,
31 Throttled {
33 #[serde(rename = "remainingSecs")]
35 remaining_secs: u64,
36 },
37 Skipped {
39 why: String,
41 },
42 Failed {
44 error: String,
46 },
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct ApplyOutcome {
54 pub written: bool,
56 pub reload: ReloadOutcome,
58 pub transports: Vec<String>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub skip: Option<SkipReason>,
63}
64
65impl ApplyOutcome {
66 pub fn unchanged(hash: u64) -> Self {
68 Self {
69 written: false,
70 reload: ReloadOutcome::Skipped {
71 why: "unchanged".into(),
72 },
73 transports: Vec::new(),
74 skip: Some(SkipReason::Unchanged { hash }),
75 }
76 }
77}
78
79pub fn config_content_hash(json: &str) -> u64 {
81 let mut hasher = DefaultHasher::new();
82 json.hash(&mut hasher);
83 hasher.finish()
84}
85
86pub fn throttle_remaining_secs(elapsed_secs: u64, min_interval: u64) -> Option<u64> {
88 if min_interval == 0 || elapsed_secs >= min_interval {
89 None
90 } else {
91 Some(min_interval.saturating_sub(elapsed_secs))
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use proptest::prelude::*;
99
100 proptest! {
101 #[test]
104 fn throttle_and_hash_follow_the_model(elapsed in 0u64..2_000, interval in 0u64..2_000, json in ".{0,40}") {
105 let want = (interval != 0 && elapsed < interval).then(|| interval - elapsed);
106 prop_assert_eq!(throttle_remaining_secs(elapsed, interval), want);
107 prop_assert_eq!(config_content_hash(&json), config_content_hash(&json.clone()));
108 }
109 }
110
111 #[test]
114 fn throttled_reload_on_the_wire() {
115 assert_eq!(throttle_remaining_secs(900, 900), None);
116 let remaining_secs = throttle_remaining_secs(60, 900).unwrap();
117 assert_eq!(remaining_secs, 840);
118 let v = serde_json::to_value(ReloadOutcome::Throttled { remaining_secs }).unwrap();
119 assert_eq!(v["outcome"], "throttled");
120 assert_eq!(v["remainingSecs"], 840);
121 assert_ne!(
122 config_content_hash(r#"{"version":1}"#),
123 config_content_hash(r#"{"version":2}"#)
124 );
125 }
126}