rs-consul 0.13.0

This crate provides access to a set of strongly typed apis to interact with consul (https://www.consul.io/)
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
/*
MIT License

Copyright (c) 2023 Roblox

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
 */

use std::collections::HashMap;
use std::time::Duration;

use serde::{self, Deserialize, Serialize, Serializer};
use smart_default::SmartDefault;

// TODO retrofit other get APIs to use this struct
/// Query options for Consul endpoints.
#[derive(Debug, Clone)]
pub struct QueryOptions {
    /// Specifies the namespace to use.
    /// If not provided, the namespace will be inferred from the request's ACL token, or will default to the default namespace.
    /// This is specified as part of the URL as a query parameter. Added in Consul 1.7.0.
    /// NOTE: usage of this query parameter requires Consul enterprise.
    pub namespace: Option<String>,
    /// Specifies the datacenter to query.
    /// This will default to the datacenter of the agent being queried.
    /// This is specified as part of the URL as a query parameter.
    pub datacenter: Option<String>,
    /// The timeout to apply to the query, if any, defaults to 5s.
    pub timeout: Option<Duration>,
    /// The index to supply as a query parameter, if the endpoint supports blocking queries.
    pub index: Option<u64>,
    /// The time to block for, when used in association with an index, if the endpoint supports blocking queries.
    /// Server side default of 5 minute is applied if not specified, with a limit of 10 minutes and maximum granularity of seconds.
    pub wait: Option<Duration>,
}
impl Default for QueryOptions {
    fn default() -> Self {
        Self {
            namespace: None,
            datacenter: None,
            timeout: Some(Duration::from_secs(5)),
            index: None,
            wait: None,
        }
    }
}

/// Encapsulates a consul query response and the returned metadata, if any.
#[derive(Debug)]
pub struct ResponseMeta<T> {
    /// Query response.
    pub response: T,
    /// The index returned from the consul query via the X-Consul-Index header.
    pub index: u64,
}

/// Represents a request to delete a key or all keys sharing a prefix from Consul's Key Value store.
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq, bon::Builder)]
pub struct DeleteKeyRequest<'a> {
    /// Specifies the path of the key to delete.
    pub key: &'a str,
    /// Specifies the datacenter to query. This will default to the datacenter of the agent being queried.
    #[builder(default)]
    pub datacenter: &'a str,
    /// Specifies to delete all keys which have the specified prefix.
    /// Without this, only a key with an exact match will be deleted.
    #[builder(default)]
    pub recurse: bool,
    /// Specifies to use a Check-And-Set operation.
    /// This is very useful as a building block for more complex synchronization primitives.
    /// The index must be greater than 0 for Consul to take any action: a 0 index will not delete the key.
    /// If the index is non-zero, the key is only deleted if the index matches the ModifyIndex of that key.
    #[builder(default)]
    pub check_and_set: u32,
    /// Specifies the namespace to query.
    /// If not provided, the namespace will be inferred from the request's ACL token, or will default to the default namespace.
    #[builder(default)]
    pub namespace: &'a str,
}

/// Represents a request to read a key from Consul's Key Value store.
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq, bon::Builder)]
pub struct ReadKeyRequest<'a> {
    /// Specifies the path of the key to read.
    pub key: &'a str,
    /// Specifies the namespace to query.
    /// If not provided, the namespace will be inferred from the request's ACL token, or will default to the default namespace.
    /// For recursive lookups, the namespace may be specified as '*' and then results will be returned for all namespaces. Added in Consul 1.7.0.
    #[builder(default)]
    pub namespace: &'a str,
    /// Specifies the datacenter to query.
    /// This will default to the datacenter of the agent being queried.
    #[builder(default)]
    pub datacenter: &'a str,
    /// Specifies if the lookup should be recursive and key treated as a prefix instead of a literal match.
    #[builder(default)]
    pub recurse: bool,
    /// Specifies the string to use as a separator for recursive key lookups.
    /// This option is only used when paired with the keys parameter to limit the prefix of keys returned, only up to the given separator.
    #[builder(default)]
    pub separator: &'a str,
    /// The consistency mode for reads. See also [ConsistencyMode](consul::types::ConsistencyMode)
    #[builder(default)]
    pub consistency: ConsistencyMode,
    /// Endpoints that support blocking queries return an HTTP header named X-Consul-Index.
    /// This is a unique identifier representing the current state of the requested resource.
    /// On subsequent requests for this resource, the client can set the index query string parameter to the value of X-Consul-Index, indicating that the client wishes to wait for any changes subsequent to that index.
    pub index: Option<u64>,
    /// The time to wait for watching a lock in a blocking fashion.
    #[builder(default)]
    pub wait: Duration,
}

/// Represents a request to read a key from Consul's Key Value store.
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq, bon::Builder)]
pub struct LockWatchRequest<'a> {
    /// Specifies the path of the key to read.
    pub key: &'a str,
    /// Specifies the datacenter to query.
    /// This will default to the datacenter of the agent being queried.
    #[builder(default)]
    pub datacenter: &'a str,
    /// Specifies the namespace to query.
    /// If not provided, the namespace will be inferred from the request's ACL token, or will default to the default namespace.
    /// For recursive lookups, the namespace may be specified as '*' and then results will be returned for all namespaces. Added in Consul 1.7.0.
    #[builder(default)]
    pub namespace: &'a str,
    /// The consistency mode for reads. See also [ConsistencyMode](consul::types::ConsistencyMode)
    #[builder(default)]
    pub consistency: ConsistencyMode,
    /// Endpoints that support blocking queries return an HTTP header named X-Consul-Index.
    /// This is a unique identifier representing the current state of the requested resource.
    /// On subsequent requests for this resource, the client can set the index query string parameter to the value of X-Consul-Index, indicating that the client wishes to wait for any changes subsequent to that index.
    pub index: Option<u64>,
    /// The time to wait for watching a lock in a blocking fashion.
    #[builder(default)]
    pub wait: Duration,
}

/// Represents a request to read a key from Consul Key Value store.
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq, bon::Builder)]
pub struct CreateOrUpdateKeyRequest<'a> {
    /// Specifies the path of the key.
    pub key: &'a str,
    /// Specifies the namespace to query.
    /// If not provided, the namespace will be inferred from the request's ACL token, or will default to the default namespace.
    /// This is specified as part of the URL as a query parameter. Added in Consul 1.7.0.
    #[builder(default)]
    pub namespace: &'a str,
    /// Specifies the datacenter to query.
    /// This will default to the datacenter of the agent being queried.
    #[builder(default)]
    pub datacenter: &'a str,
    /// Specifies an unsigned value between 0 and (2^64)-1.
    /// Clients can choose to use this however makes sense for their application.
    #[builder(default)]
    pub flags: u64,
    /// Specifies to use a Check-And-Set operation.
    /// This is very useful as a building block for more complex synchronization primitives.
    /// If the index is 0, Consul will only put the key if it does not already exist.
    /// If the index is non-zero, the key is only set if the index matches the ModifyIndex of that key.
    pub check_and_set: Option<i64>,
    /// Supply a session ID to use in a lock acquisition operation.
    /// This is useful as it allows leader election to be built on top of Consul.
    /// If the lock is not held and the session is valid, this increments the LockIndex and sets the Session value of the key in addition to updating the key contents.
    /// A key does not need to exist to be acquired. If the lock is already held by the given session, then the LockIndex is not incremented but the key contents are updated.
    /// This lets the current lock holder update the key contents without having to give up the lock and reacquire it.
    /// Note that an update that does not include the acquire parameter will proceed normally even if another session has locked the key.
    #[builder(default)]
    pub acquire: &'a str,
    /// Supply a session ID to use in a release operation.
    /// This is useful when paired with ?acquire= as it allows clients to yield a lock.
    /// This will leave the LockIndex unmodified but will clear the associated Session of the key.
    /// The key must be held by this session to be unlocked.
    #[builder(default)]
    pub release: &'a str,
}

/// Represents a request to read a key from Consul Key Value store.
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub struct ReadKeyResponse {
    /// CreateIndex is the internal index value that represents when the entry was created.
    pub create_index: i64,
    /// ModifyIndex is the last index that modified this key.
    /// It can be used to establish blocking queries by setting the ?index query parameter.
    /// You can even perform blocking queries against entire subtrees of the KV store: if ?recurse is provided, the returned X-Consul-Index corresponds to the latest ModifyIndex within the prefix, and a blocking query using that ?index will wait until any key within that prefix is updated.
    pub modify_index: i64,
    /// LockIndex is the number of times this key has successfully been acquired in a lock.
    /// If the lock is held, the Session key provides the session that owns the lock.
    pub lock_index: i64,
    /// Key is simply the full path of the entry.
    pub key: String,
    /// Flags is an opaque unsigned integer that can be attached to each entry.
    /// Clients can choose to use this however makes sense for their application.
    pub flags: u64,
    /// Value is a base64-encoded blob of data.
    pub value: Option<String>,
    /// If a lock is held, the Session key provides the session that owns the lock.
    pub session: Option<String>,
}

/// Represents a request to create a lock .
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq, Copy, bon::Builder)]
#[serde(rename_all = "PascalCase")]
pub struct LockRequest<'a> {
    /// The key to use for locking.
    pub key: &'a str,
    /// The name of the session to use.
    #[builder(default)]
    pub session_id: &'a str,
    /// Specifies the namespace to use.
    /// If not provided, the namespace will be inferred from the request's ACL token, or will default to the default namespace.
    /// This is specified as part of the URL as a query parameter. Added in Consul 1.7.0.
    #[builder(default)]
    pub namespace: &'a str,
    /// Specifies the datacenter to query.
    /// This will default to the datacenter of the agent being queried.
    /// This is specified as part of the URL as a query parameter.
    #[builder(default)]
    pub datacenter: &'a str,
    /// Specifies the duration of a session (between 10s and 86400s).
    /// If provided, the session is invalidated if it is not renewed before the TTL expires.
    /// The lowest practical TTL should be used to keep the number of managed sessions low.
    /// When locks are forcibly expired, such as when following the leader election pattern in an application, sessions may not be reaped for up to double this TTL, so long TTL values (> 1 hour) should be avoided.
    /// Defaults to 10 seconds.
    #[default(_code = "Duration::from_secs(10)")]
    #[builder(default = Duration::from_secs(10))]
    pub timeout: Duration,
    /// Controls the behavior to take when a session is invalidated. See also [LockExpirationBehavior](consul::types::LockExpirationBehavior)
    #[builder(default)]
    pub behavior: LockExpirationBehavior,
    /// Specifies the duration for the lock delay.
    /// Defaults to 1 second.
    #[default(_code = "Duration::from_secs(1)")]
    #[builder(default = Duration::from_secs(1))]
    pub lock_delay: Duration,
}

/// Controls the behavior of locks when a session is invalidated. See [consul docs](https://www.consul.io/api-docs/session#behavior) for more information.
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq, Copy)]
#[serde(rename_all = "snake_case")]
pub enum LockExpirationBehavior {
    #[default]
    /// Causes any locks that are held to be released when a session is invalidated.
    Release,
    /// Causes any locks that are held to be deleted when a session is invalidated.
    Delete,
}

/// Most of the read query endpoints support multiple levels of consistency.
/// Since no policy will suit all clients' needs, these consistency modes allow the user to have the ultimate say in how to balance the trade-offs inherent in a distributed system.
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub enum ConsistencyMode {
    /// If not specified, the default is strongly consistent in almost all cases.
    /// However, there is a small window in which a new leader may be elected during which the old leader may service stale values.
    /// The trade-off is fast reads but potentially stale values.
    /// The condition resulting in stale reads is hard to trigger, and most clients should not need to worry about this case.
    /// Also, note that this race condition only applies to reads, not writes.
    #[default]
    Default,
    /// This mode is strongly consistent without caveats.
    /// It requires that a leader verify with a quorum of peers that it is still leader.
    /// This introduces an additional round-trip to all server nodes. The trade-off is increased latency due to an extra round trip.
    /// Most clients should not use this unless they cannot tolerate a stale read.
    Consistent,
    /// This mode allows any server to service the read regardless of whether it is the leader.
    /// This means reads can be arbitrarily stale; however, results are generally consistent to within 50 milliseconds of the leader.
    /// The trade-off is very fast and scalable reads with a higher likelihood of stale values.
    /// Since this mode allows reads without a leader, a cluster that is unavailable will still be able to respond to queries.
    Stale,
}

#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq, bon::Builder)]
pub(crate) struct SessionResponse {
    #[serde(rename = "ID")]
    pub(crate) id: String,
}

#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct CreateSessionRequest {
    #[default(_code = "Duration::from_secs(0)")]
    #[serde(serialize_with = "serialize_duration_as_string")]
    pub(crate) lock_delay: Duration,
    #[serde(skip_serializing_if = "std::string::String::is_empty")]
    pub(crate) name: String,
    #[serde(skip_serializing_if = "std::string::String::is_empty")]
    pub(crate) node: String,
    #[serde(skip_serializing_if = "std::vec::Vec::is_empty")]
    pub(crate) checks: Vec<String>,
    pub(crate) behavior: LockExpirationBehavior,
    #[serde(rename = "TTL")]
    #[default(_code = "Duration::from_secs(10)")]
    #[serde(serialize_with = "serialize_duration_as_string")]
    pub(crate) ttl: Duration,
}

/// Payload struct to register or update entries in consul's catalog.
/// See https://www.consul.io/api-docs/catalog#register-entity for more information.
#[allow(non_snake_case)]
#[derive(Clone, Debug, Serialize, Deserialize, bon::Builder)]
pub struct RegisterEntityPayload {
    /// Optional UUID to assign to the node. This string is required to be 36-characters and UUID formatted.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ID: Option<String>,
    /// Node ID to register.
    pub Node: String,
    /// The address to register.
    pub Address: String,
    /// The datacenter to register in, defaults to the agent's datacenter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Datacenter: Option<String>,
    /// Tagged addressed to register with.
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    #[builder(default)]
    pub TaggedAddresses: HashMap<String, String>,
    /// KV metadata paris to register with.
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    #[builder(default)]
    pub NodeMeta: HashMap<String, String>,
    /// Optional service to register.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Service: Option<RegisterEntityService>,
    /// Checks to register.
    #[serde(skip_serializing_if = "Vec::is_empty")]
    #[builder(default)]
    pub Checks: Vec<RegisterEntityCheck>,
    /// Whether to skip updating the nodes information in the registration.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub SkipNodeUpdate: Option<bool>,
}

/// The service to deregister with consul's global catalog.
/// See https://www.consul.io/api/agent/service for more information.
#[allow(non_snake_case)]
#[derive(Clone, Debug, Serialize, Deserialize, bon::Builder)]
pub struct DeregisterEntityPayload {
    /// The node to execute the check on.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Node: Option<String>,
    /// The datacenter to register in, defaults to the agent's datacenter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Datacenter: Option<String>,
    /// Specifies the ID of the check to remove.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub CheckID: Option<String>,
    /// Specifies the ID of the service to remove. The service and all associated checks will be removed.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ServiceID: Option<String>,
    /// Specifies the namespace of the service and checks you deregister.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Namespace: Option<String>,
}

/// The service to register with consul's global catalog.
/// See https://www.consul.io/api/agent/service for more information.
#[allow(non_snake_case)]
#[derive(Clone, Debug, Serialize, Deserialize, bon::Builder)]
pub struct RegisterEntityService {
    /// ID to register service will, defaults to Service.Service property.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ID: Option<String>,
    /// The name of the service.
    pub Service: String,
    /// Optional tags associated with the service.
    #[serde(skip_serializing_if = "Vec::is_empty")]
    #[builder(default)]
    pub Tags: Vec<String>,
    /// Optional map of explicit LAN and WAN addresses for the service.
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    #[builder(default)]
    pub TaggedAddresses: HashMap<String, String>,
    /// Optional key value meta associated with the service.
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    #[builder(default)]
    pub Meta: HashMap<String, String>,
    /// The port of the service
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Port: Option<u16>,
    /// The consul namespace to register the service in.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Namespace: Option<String>,
}

/// Information related to registering a check.
/// See https://www.consul.io/docs/discovery/checks for more information.
#[allow(non_snake_case)]
#[derive(Clone, Debug, Serialize, Deserialize, bon::Builder)]
pub struct RegisterEntityCheck {
    /// The node to execute the check on.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Node: Option<String>,
    /// Optional check id, defaults to the name of the check.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub CheckID: Option<String>,
    /// The name associated with the check
    pub Name: String,
    /// Opaque field encapsulating human-readable text.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Notes: Option<String>,
    /// The status of the check. Must be one of 'passing', 'warning', or 'critical'.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Status: Option<String>,
    /// ID of the service this check is for. If no ID of a service running on the node is provided,
    /// the check is treated as a node level check
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ServiceID: Option<String>,
    /// Details for a TCP or HTTP health check.
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    #[builder(default)]
    pub Definition: HashMap<String, String>,
}

/// Request for the nodes providing a specified service registered in Consul.
/// See https://developer.hashicorp.com/consul/api-docs/catalog#list-nodes for more information.
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq, bon::Builder)]
pub struct GetNodesRequest<'a> {
    /// Specifies a node name to sort the node list in ascending order based on the estimated round trip time from that node.
    /// Passing `?near=_agent` will use the agent's node for the sort. This is specified as part of the URL as a query parameter.
    /// Note that using `near` will ignore `use_streaming_backend` and always use blocking queries, because the data required to
    /// sort the results is not available to the streaming backend.
    pub near: Option<&'a str>,
    /// (string: "") Specifies the expression used to filter the queries results prior to returning the data.
    pub filter: Option<&'a str>,
}

#[allow(non_snake_case)]
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
/// The node information of an instance providing a Consul service.
pub struct CatalogNode {
    /// The ID of the service node.
    #[serde(rename = "ID")]
    pub id: String,
    /// The name of the Consul node on which the service is registered
    pub node: String,
    /// The IP address of the Consul node on which the service is registered.
    pub address: String,
    /// The datacenter where this node is running on.
    pub datacenter: String,
    /// Tagged addressed to register with.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub TaggedAddresses: Option<HashMap<String, String>>,
    /// Optional key value meta associated with the service.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub Meta: Option<HashMap<String, String>>,
}

pub(crate) type GetNodesResponse = Vec<CatalogNode>;

/// Request to retrieve information about nodes int the Consul catalog.
#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq, bon::Builder)]
pub struct GetServiceNodesRequest<'a> {
    /// Specifies the service to list services for. This is provided as part of the URL.
    pub service: &'a str,
    /// Specifies a node name to sort the node list in ascending order based on the estimated round trip time from that node.
    /// Passing `?near=_agent` will use the agent's node for the sort. This is specified as part of the URL as a query parameter.
    /// Note that using `near` will ignore `use_streaming_backend` and always use blocking queries, because the data required to
    /// sort the results is not available to the streaming backend.
    pub near: Option<&'a str>,
    /// (bool: false) Specifies that the server should return only nodes with all checks in the passing state.
    /// This can be used to avoid additional filtering on the client side.
    #[builder(default)]
    pub passing: bool,
    /// (string: "") Specifies the expression used to filter the queries results prior to returning the data.
    pub filter: Option<&'a str>,
}

pub(crate) type GetServiceNodesResponse = Vec<ServiceNode>;

#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
/// An instance of a node providing a Consul service.
pub struct ServiceNode {
    /// The Node information for this service
    pub node: Node,
    /// The Service information
    pub service: Service,
}

#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
/// The node information of an instance providing a Consul service.
pub struct Node {
    /// The ID of the service node.
    #[serde(rename = "ID")]
    pub id: String,
    /// The name of the Consul node on which the service is registered
    pub node: String,
    /// The IP address of the Consul node on which the service is registered.
    pub address: String,
    /// The datacenter where this node is running on.
    pub datacenter: String,
}

#[derive(Clone, Debug, SmartDefault, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
/// The service information of an instance providing a Consul service.
pub struct Service {
    /// The ID of the service instance, i.e. redis-1.
    #[serde(rename = "ID")]
    pub id: String,
    /// The name of the service, i.e. redis.
    pub service: String,
    /// The address of the instance.
    pub address: String,
    /// The port of the instance.
    pub port: u16,
    /// Tags assigned to the service instance.
    pub tags: Vec<String>,
}

pub(crate) fn serialize_duration_as_string<S>(
    duration: &Duration,
    serializer: S,
) -> std::result::Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let mut res = duration.as_secs().to_string();
    res.push('s');
    serializer.serialize_str(&res)
}

pub(crate) fn duration_as_string(duration: &Duration) -> String {
    let mut res = duration.as_secs().to_string();
    res.push('s');
    res
}