1use crate::request::{Method, Request, ToHttpRequest};
2use serde::{Deserialize, Serialize};
3use serde_json::json;
4use std::collections::HashMap;
5
6const COUNTERS_ENDPOINT: &str = "/rest/v2/counters";
7
8type CounterVal = i64;
9
10#[derive(Debug, Serialize, Deserialize)]
11enum Counter {
12 #[serde(rename = "weak-counter")]
13 Weak(WeakCounter),
14 #[serde(rename = "strong-counter")]
15 Strong(StrongCounter),
16}
17
18#[derive(Debug, Serialize, Deserialize)]
19struct WeakCounter {
20 #[serde(rename = "initial-value", skip_serializing_if = "Option::is_none")]
21 initial_value: Option<CounterVal>,
22}
23
24impl WeakCounter {
25 pub fn set_value(&mut self, counter_val: CounterVal) {
26 self.initial_value = Some(counter_val);
27 }
28}
29
30#[derive(Debug, Serialize, Deserialize)]
31struct StrongCounter {
32 #[serde(rename = "initial-value", skip_serializing_if = "Option::is_none")]
33 initial_value: Option<CounterVal>,
34}
35
36impl StrongCounter {
37 pub fn set_value(&mut self, counter_val: CounterVal) {
38 self.initial_value = Some(counter_val);
39 }
40}
41
42#[derive(Debug, Copy, Clone)]
43pub enum CounterType {
44 Weak,
45 Strong,
46}
47
48#[derive(Debug)]
49pub struct CreateCounterReq {
50 name: String,
51 counter: Counter,
52}
53
54#[derive(Debug)]
55enum Action {
56 Add {
57 delta: CounterVal,
58 },
59 Increment,
60 Decrement,
61 Reset,
62 CompareAndSet {
63 expect: CounterVal,
64 update: CounterVal,
65 },
66 CompareAndSwap {
67 expect: CounterVal,
68 update: CounterVal,
69 },
70}
71
72impl Action {
73 pub fn to_query_args(&self) -> String {
74 match self {
75 Action::Add { delta } => {
76 format!("action=add&delta={}", delta)
77 }
78 Action::Increment => "action=increment".to_string(),
79 Action::Decrement => "action=decrement".to_string(),
80 Action::Reset => "action=reset".to_string(),
81 Action::CompareAndSet { expect, update } => {
82 format!("action=compareAndSet&expect={}&update={}", expect, update)
83 }
84 Action::CompareAndSwap { expect, update } => {
85 format!("action=compareAndSwap&expect={}&update={}", expect, update)
86 }
87 }
88 }
89}
90
91impl CreateCounterReq {
92 pub fn new(name: impl Into<String>, counter_type: CounterType) -> Self {
93 let counter = match counter_type {
94 CounterType::Weak => Counter::Weak(WeakCounter {
95 initial_value: None,
96 }),
97 CounterType::Strong => Counter::Strong(StrongCounter {
98 initial_value: None,
99 }),
100 };
101
102 Self {
103 name: name.into(),
104 counter,
105 }
106 }
107
108 pub fn with_value(mut self, value: CounterVal) -> Self {
109 match &mut self.counter {
110 Counter::Weak(counter) => counter.set_value(value),
111 Counter::Strong(counter) => counter.set_value(value),
112 }
113
114 self
115 }
116}
117
118impl From<&CreateCounterReq> for Request {
119 fn from(request: &CreateCounterReq) -> Self {
120 Self::new(
121 Method::Post,
122 counter_path(&request.name),
123 HashMap::new(),
124 Some(json!(request.counter).to_string()),
125 )
126 }
127}
128
129impl ToHttpRequest for CreateCounterReq {
130 fn to_http_req(&self, base_url: impl AsRef<str>) -> http::Request<String> {
131 Request::from(self).to_http_req(base_url)
132 }
133}
134
135#[derive(Debug)]
136pub struct IncrementCounterReq {
137 name: String,
138 delta: Option<CounterVal>,
139}
140
141impl IncrementCounterReq {
142 pub fn new(name: impl Into<String>) -> Self {
143 Self {
144 name: name.into(),
145 delta: None,
146 }
147 }
148
149 pub fn by(mut self, delta: CounterVal) -> Self {
150 self.delta = Some(delta);
151 self
152 }
153
154 fn action(&self) -> Action {
155 match self.delta {
156 Some(delta) => Action::Add { delta },
157 None => Action::Increment,
158 }
159 }
160}
161
162impl From<&IncrementCounterReq> for Request {
163 fn from(request: &IncrementCounterReq) -> Self {
164 Self::new(
165 Method::Post,
166 counter_path_with_action(&request.name, &request.action()),
167 HashMap::new(),
168 None,
169 )
170 }
171}
172
173impl ToHttpRequest for IncrementCounterReq {
174 fn to_http_req(&self, base_url: impl AsRef<str>) -> http::Request<String> {
175 Request::from(self).to_http_req(base_url)
176 }
177}
178
179pub fn create_weak(name: impl Into<String>) -> CreateCounterReq {
180 CreateCounterReq::new(name, CounterType::Weak)
181}
182
183pub fn create_strong(name: impl Into<String>) -> CreateCounterReq {
184 CreateCounterReq::new(name, CounterType::Strong)
185}
186
187pub fn get(name: impl AsRef<str>) -> Request {
188 Request::new(Method::Get, counter_path(name), HashMap::new(), None)
189}
190
191pub fn get_config(name: impl AsRef<str>) -> Request {
192 Request::new(Method::Get, counter_config_path(name), HashMap::new(), None)
193}
194
195pub fn increment(name: impl Into<String>) -> IncrementCounterReq {
196 IncrementCounterReq::new(name)
197}
198
199pub fn decrement(name: impl AsRef<str>) -> Request {
200 Request::new(
201 Method::Post,
202 counter_path_with_action(name, &Action::Decrement),
203 HashMap::new(),
204 None,
205 )
206}
207
208pub fn reset(name: impl AsRef<str>) -> Request {
209 Request::new(
210 Method::Post,
211 counter_path_with_action(name, &Action::Reset),
212 HashMap::new(),
213 None,
214 )
215}
216
217pub fn delete(name: impl AsRef<str>) -> Request {
218 Request::new(Method::Delete, counter_path(name), HashMap::new(), None)
219}
220
221pub fn compare_and_set(name: impl AsRef<str>, expect: CounterVal, update: CounterVal) -> Request {
222 Request::new(
223 Method::Post,
224 counter_path_with_action(name, &Action::CompareAndSet { expect, update }),
225 HashMap::new(),
226 None,
227 )
228}
229
230pub fn compare_and_swap(name: impl AsRef<str>, expect: CounterVal, update: CounterVal) -> Request {
231 Request::new(
232 Method::Post,
233 counter_path_with_action(name, &Action::CompareAndSwap { expect, update }),
234 HashMap::new(),
235 None,
236 )
237}
238
239pub fn list() -> Request {
240 Request::new(Method::Get, COUNTERS_ENDPOINT, HashMap::new(), None)
241}
242
243fn counter_path(name: impl AsRef<str>) -> String {
244 format!(
245 "/{counters_endpoint}/{counter_name}",
246 counters_endpoint = COUNTERS_ENDPOINT,
247 counter_name = urlencoding::encode(name.as_ref())
248 )
249}
250
251fn counter_path_with_action(name: impl AsRef<str>, action: &Action) -> String {
252 format!("{}?{}", counter_path(name), action.to_query_args())
253}
254
255fn counter_config_path(name: impl AsRef<str>) -> String {
256 format!("{}/config", counter_path(name))
257}