kube_core/
params.rs

1//! A port of request parameter *Optionals from apimachinery/types.go
2use crate::{request::Error, Selector};
3use serde::Serialize;
4
5/// Controls how the resource version parameter is applied for list calls
6///
7/// Not specifying a `VersionMatch` strategy will give you different semantics
8/// depending on what `resource_version`, `limit`, `continue_token` you include with the list request.
9///
10/// See <https://kubernetes.io/docs/reference/using-api/api-concepts/#semantics-for-get-and-list> for details.
11#[derive(Clone, Debug, PartialEq)]
12pub enum VersionMatch {
13    /// Returns data at least as new as the provided resource version.
14    ///
15    /// The newest available data is preferred, but any data not older than the provided resource version may be served.
16    /// This guarantees that the collection's resource version is not older than the requested resource version,
17    /// but does not make any guarantee about the resource version of any of the items in that collection.
18    ///
19    /// ### Any Version
20    /// A degenerate, but common sub-case of `NotOlderThan` is when used together with `resource_version` "0".
21    ///
22    /// It is possible for a "0" resource version request to return data at a much older resource version
23    /// than the client has previously observed, particularly in HA configurations, due to partitions or stale caches.
24    /// Clients that cannot tolerate this should not use this semantic.
25    NotOlderThan,
26
27    /// Return data at the exact resource version provided.
28    ///
29    /// If the provided resource version  is unavailable, the server responds with HTTP 410 "Gone".
30    /// For list requests to servers that honor the resource version Match parameter, this guarantees that the collection's
31    /// resource version  is the same as the resource version  you requested in the query string.
32    /// That guarantee does not apply to the resource version  of any items within that collection.
33    ///
34    /// Note that `Exact` cannot be used with resource version "0". For the most up-to-date list; use `Unset`.
35    Exact,
36}
37
38/// Common query parameters used in list/delete calls on collections
39#[derive(Clone, Debug, Default, PartialEq)]
40pub struct ListParams {
41    /// A selector to restrict the list of returned objects by their labels.
42    ///
43    /// Defaults to everything if `None`.
44    pub label_selector: Option<String>,
45
46    /// A selector to restrict the list of returned objects by their fields.
47    ///
48    /// Defaults to everything if `None`.
49    pub field_selector: Option<String>,
50
51    /// Timeout for the list/watch call.
52    ///
53    /// This limits the duration of the call, regardless of any activity or inactivity.
54    pub timeout: Option<u32>,
55
56    /// Limit the number of results.
57    ///
58    /// If there are more results, the server will respond with a continue token which can be used to fetch another page
59    /// of results. See the [Kubernetes API docs](https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks)
60    /// for pagination details.
61    pub limit: Option<u32>,
62
63    /// Fetch a second page of results.
64    ///
65    /// After listing results with a limit, a continue token can be used to fetch another page of results.
66    pub continue_token: Option<String>,
67
68    /// Determines how resourceVersion is matched applied to list calls.
69    pub version_match: Option<VersionMatch>,
70
71    /// An explicit resourceVersion using the given `VersionMatch` strategy
72    ///
73    /// See <https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions> for details.
74    pub resource_version: Option<String>,
75}
76
77impl ListParams {
78    pub(crate) fn validate(&self) -> Result<(), Error> {
79        if let Some(rv) = &self.resource_version {
80            if self.version_match == Some(VersionMatch::Exact) && rv == "0" {
81                return Err(Error::Validation(
82                    "A non-zero resource_version is required when using an Exact match".into(),
83                ));
84            }
85        } else if self.version_match.is_some() {
86            return Err(Error::Validation(
87                "A resource_version is required when using an explicit match".into(),
88            ));
89        }
90        Ok(())
91    }
92
93    // Partially populate query parameters (needs resourceVersion out of band)
94    pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
95        if let Some(fields) = &self.field_selector {
96            qp.append_pair("fieldSelector", fields);
97        }
98        if let Some(labels) = &self.label_selector {
99            qp.append_pair("labelSelector", labels);
100        }
101        if let Some(limit) = &self.limit {
102            qp.append_pair("limit", &limit.to_string());
103        }
104        if let Some(continue_token) = &self.continue_token {
105            qp.append_pair("continue", continue_token);
106        } else {
107            // When there's a continue token, we don't want to set resourceVersion
108            if let Some(rv) = &self.resource_version {
109                if rv != "0" || self.limit.is_none() {
110                    qp.append_pair("resourceVersion", rv.as_str());
111
112                    match &self.version_match {
113                        None => {}
114                        Some(VersionMatch::NotOlderThan) => {
115                            qp.append_pair("resourceVersionMatch", "NotOlderThan");
116                        }
117                        Some(VersionMatch::Exact) => {
118                            qp.append_pair("resourceVersionMatch", "Exact");
119                        }
120                    }
121                }
122            }
123        }
124    }
125}
126
127/// Builder interface to ListParams
128///
129/// Usage:
130/// ```
131/// use kube::api::ListParams;
132/// let lp = ListParams::default()
133///     .match_any()
134///     .timeout(60)
135///     .labels("kubernetes.io/lifecycle=spot");
136/// ```
137impl ListParams {
138    /// Configure the timeout for list/watch calls
139    ///
140    /// This limits the duration of the call, regardless of any activity or inactivity.
141    /// Defaults to 290s
142    #[must_use]
143    pub fn timeout(mut self, timeout_secs: u32) -> Self {
144        self.timeout = Some(timeout_secs);
145        self
146    }
147
148    /// Configure the selector to restrict the list of returned objects by their fields.
149    ///
150    /// Defaults to everything.
151    /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
152    /// The server only supports a limited number of field queries per type.
153    #[must_use]
154    pub fn fields(mut self, field_selector: &str) -> Self {
155        self.field_selector = Some(field_selector.to_string());
156        self
157    }
158
159    /// Configure the selector to restrict the list of returned objects by their labels.
160    ///
161    /// Defaults to everything.
162    /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
163    #[must_use]
164    pub fn labels(mut self, label_selector: &str) -> Self {
165        self.label_selector = Some(label_selector.to_string());
166        self
167    }
168
169    /// Configure typed label selectors
170    ///
171    /// Configure typed selectors from [`Selector`](crate::Selector) and [`Expression`](crate::Expression) lists.
172    ///
173    /// ```
174    /// use kube::core::{Expression, Selector, ParseExpressionError};
175    /// # use kube::core::params::ListParams;
176    /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
177    ///
178    /// // From expressions
179    /// let selector: Selector = Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
180    /// let lp = ListParams::default().labels_from(&selector);
181    /// let lp = ListParams::default().labels_from(&Expression::Exists("foo".into()).into());
182    ///
183    /// // Native LabelSelector
184    /// let selector: Selector = LabelSelector::default().try_into()?;
185    /// let lp = ListParams::default().labels_from(&selector);
186    /// # Ok::<(), ParseExpressionError>(())
187    ///```
188    #[must_use]
189    pub fn labels_from(mut self, selector: &Selector) -> Self {
190        self.label_selector = Some(selector.to_string());
191        self
192    }
193
194    /// Sets a result limit.
195    #[must_use]
196    pub fn limit(mut self, limit: u32) -> Self {
197        self.limit = Some(limit);
198        self
199    }
200
201    /// Sets a continue token.
202    #[must_use]
203    pub fn continue_token(mut self, token: &str) -> Self {
204        self.continue_token = Some(token.to_string());
205        self
206    }
207
208    /// Sets the resource version
209    #[must_use]
210    pub fn at(mut self, resource_version: &str) -> Self {
211        self.resource_version = Some(resource_version.into());
212        self
213    }
214
215    /// Sets an arbitary resource version match strategy
216    ///
217    /// A non-default strategy such as `VersionMatch::Exact` or `VersionMatch::NotOlderThan`
218    /// requires an explicit `resource_version` set to pass request validation.
219    #[must_use]
220    pub fn matching(mut self, version_match: VersionMatch) -> Self {
221        self.version_match = Some(version_match);
222        self
223    }
224
225    /// Use the semantic "any" resource version strategy
226    ///
227    /// This is a less taxing variant of the default list, returning data at any resource version.
228    /// It will prefer the newest avialable resource version, but strong consistency is not required;
229    /// data at any resource version may be served.
230    /// It is possible for the request to return data at a much older resource version than the client
231    /// has previously observed, particularly in high availability configurations, due to partitions or stale caches.
232    /// Clients that cannot tolerate this should not use this semantic.
233    #[must_use]
234    pub fn match_any(self) -> Self {
235        self.matching(VersionMatch::NotOlderThan).at("0")
236    }
237}
238
239/// Common query parameters used in get calls
240#[derive(Clone, Debug, Default, PartialEq)]
241pub struct GetParams {
242    /// An explicit resourceVersion with implicit version matching strategies
243    ///
244    /// Default (unset) gives the most recent version. "0" gives a less
245    /// consistent, but more performant "Any" version. Specifing a version is
246    /// like providing a `VersionMatch::NotOlderThan`.
247    /// See <https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions> for details.
248    pub resource_version: Option<String>,
249}
250
251/// Helper interface to GetParams
252///
253/// Usage:
254/// ```
255/// use kube::api::GetParams;
256/// let gp = GetParams::at("6664");
257/// ```
258impl GetParams {
259    /// Sets the resource version, implicitly applying a 'NotOlderThan' match
260    #[must_use]
261    pub fn at(resource_version: &str) -> Self {
262        Self {
263            resource_version: Some(resource_version.into()),
264        }
265    }
266
267    /// Sets the resource version to "0"
268    #[must_use]
269    pub fn any() -> Self {
270        Self::at("0")
271    }
272}
273
274/// The validation directive to use for `fieldValidation` when using server-side apply.
275#[derive(Clone, Debug)]
276pub enum ValidationDirective {
277    /// Strict mode will fail any invalid manifests.
278    ///
279    /// This will fail the request with a BadRequest error if any unknown fields would be dropped from the
280    /// object, or if any duplicate fields are present. The error returned from the server will contain
281    /// all unknown and duplicate fields encountered.
282    Strict,
283    /// Warn mode will return a warning for invalid manifests.
284    ///
285    /// This will send a warning via the standard warning response header for each unknown field that
286    /// is dropped from the object, and for each duplicate field that is encountered. The request will
287    /// still succeed if there are no other errors, and will only persist the last of any duplicate fields.
288    Warn,
289    /// Ignore mode will silently ignore any problems.
290    ///
291    /// This will ignore any unknown fields that are silently dropped from the object, and will ignore
292    /// all but the last duplicate field that the decoder encounters.
293    Ignore,
294}
295
296impl ValidationDirective {
297    /// Returns the string format of the directive
298    pub fn as_str(&self) -> &str {
299        match self {
300            Self::Strict => "Strict",
301            Self::Warn => "Warn",
302            Self::Ignore => "Ignore",
303        }
304    }
305}
306
307/// Common query parameters used in watch calls on collections
308#[derive(Clone, Debug, PartialEq)]
309pub struct WatchParams {
310    /// A selector to restrict returned objects by their labels.
311    ///
312    /// Defaults to everything if `None`.
313    pub label_selector: Option<String>,
314
315    /// A selector to restrict returned objects by their fields.
316    ///
317    /// Defaults to everything if `None`.
318    pub field_selector: Option<String>,
319
320    /// Timeout for the watch call.
321    ///
322    /// This limits the duration of the call, regardless of any activity or inactivity.
323    /// If unset for a watch call, we will use 290s.
324    /// We limit this to 295s due to [inherent watch limitations](https://github.com/kubernetes/kubernetes/issues/6513).
325    pub timeout: Option<u32>,
326
327    /// Enables watch events with type "BOOKMARK".
328    ///
329    /// Servers that do not implement bookmarks ignore this flag and
330    /// bookmarks are sent at the server's discretion. Clients should not
331    /// assume bookmarks are returned at any specific interval, nor may they
332    /// assume the server will send any BOOKMARK event during a session.
333    /// If this is not a watch, this field is ignored.
334    /// If the feature gate WatchBookmarks is not enabled in apiserver,
335    /// this field is ignored.
336    pub bookmarks: bool,
337
338    /// Kubernetes 1.27 Streaming Lists
339    /// `sendInitialEvents=true` may be set together with `watch=true`.
340    /// In that case, the watch stream will begin with synthetic events to
341    /// produce the current state of objects in the collection. Once all such
342    /// events have been sent, a synthetic "Bookmark" event  will be sent.
343    /// The bookmark will report the ResourceVersion (RV) corresponding to the
344    /// set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation.
345    /// Afterwards, the watch stream will proceed as usual, sending watch events
346    /// corresponding to changes (subsequent to the RV) to objects watched.
347    ///
348    /// When `sendInitialEvents` option is set, we require `resourceVersionMatch`
349    /// option to also be set. The semantic of the watch request is as following:
350    /// - `resourceVersionMatch` = NotOlderThan
351    ///   is interpreted as "data at least as new as the provided `resourceVersion`"
352    ///   and the bookmark event is send when the state is synced
353    ///   to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
354    ///   If `resourceVersion` is unset, this is interpreted as "consistent read" and the
355    ///   bookmark event is send when the state is synced at least to the moment
356    ///   when request started being processed.
357    /// - `resourceVersionMatch` set to any other value or unset
358    ///   Invalid error is returned.
359    pub send_initial_events: bool,
360}
361
362impl WatchParams {
363    pub(crate) fn validate(&self) -> Result<(), Error> {
364        if let Some(to) = &self.timeout {
365            // https://github.com/kubernetes/kubernetes/issues/6513
366            if *to >= 295 {
367                return Err(Error::Validation("WatchParams::timeout must be < 295s".into()));
368            }
369        }
370        if self.send_initial_events && !self.bookmarks {
371            return Err(Error::Validation(
372                "WatchParams::bookmarks must be set when using send_initial_events".into(),
373            ));
374        }
375        Ok(())
376    }
377
378    // Partially populate query parameters (needs resourceVersion out of band)
379    pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
380        qp.append_pair("watch", "true");
381
382        // https://github.com/kubernetes/kubernetes/issues/6513
383        qp.append_pair("timeoutSeconds", &self.timeout.unwrap_or(290).to_string());
384
385        if let Some(fields) = &self.field_selector {
386            qp.append_pair("fieldSelector", fields);
387        }
388        if let Some(labels) = &self.label_selector {
389            qp.append_pair("labelSelector", labels);
390        }
391        if self.bookmarks {
392            qp.append_pair("allowWatchBookmarks", "true");
393        }
394        if self.send_initial_events {
395            qp.append_pair("sendInitialEvents", "true");
396            qp.append_pair("resourceVersionMatch", "NotOlderThan");
397        }
398    }
399}
400
401impl Default for WatchParams {
402    /// Default `WatchParams` without any constricting selectors
403    fn default() -> Self {
404        Self {
405            // bookmarks stable since 1.17, and backwards compatible
406            bookmarks: true,
407
408            label_selector: None,
409            field_selector: None,
410            timeout: None,
411            send_initial_events: false,
412        }
413    }
414}
415
416/// Builder interface to WatchParams
417///
418/// Usage:
419/// ```
420/// use kube::api::WatchParams;
421/// let lp = WatchParams::default()
422///     .timeout(60)
423///     .labels("kubernetes.io/lifecycle=spot");
424/// ```
425impl WatchParams {
426    /// Configure the timeout for watch calls
427    ///
428    /// This limits the duration of the call, regardless of any activity or inactivity.
429    /// Defaults to 290s
430    #[must_use]
431    pub fn timeout(mut self, timeout_secs: u32) -> Self {
432        self.timeout = Some(timeout_secs);
433        self
434    }
435
436    /// Configure the selector to restrict the list of returned objects by their fields.
437    ///
438    /// Defaults to everything.
439    /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
440    /// The server only supports a limited number of field queries per type.
441    #[must_use]
442    pub fn fields(mut self, field_selector: &str) -> Self {
443        self.field_selector = Some(field_selector.to_string());
444        self
445    }
446
447    /// Configure the selector to restrict the list of returned objects by their labels.
448    ///
449    /// Defaults to everything.
450    /// Supports `=`, `==`, `!=`, and can be comma separated: `key1=value1,key2=value2`.
451    #[must_use]
452    pub fn labels(mut self, label_selector: &str) -> Self {
453        self.label_selector = Some(label_selector.to_string());
454        self
455    }
456
457    /// Configure typed label selectors
458    ///
459    /// Configure typed selectors from [`Selector`](crate::Selector) and [`Expression`](crate::Expression) lists.
460    ///
461    /// ```
462    /// use kube::core::{Expression, Selector, ParseExpressionError};
463    /// # use kube::core::params::WatchParams;
464    /// use k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector;
465    ///
466    /// // From expressions
467    /// let selector: Selector = Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
468    /// let wp = WatchParams::default().labels_from(&selector);
469    /// let wp = WatchParams::default().labels_from(&Expression::Exists("foo".into()).into());
470    ///
471    /// // Native LabelSelector
472    /// let selector: Selector = LabelSelector::default().try_into()?;
473    /// let wp = WatchParams::default().labels_from(&selector);
474    /// # Ok::<(), ParseExpressionError>(())
475    ///```
476    #[must_use]
477    pub fn labels_from(mut self, selector: &Selector) -> Self {
478        self.label_selector = Some(selector.to_string());
479        self
480    }
481
482    /// Disables watch bookmarks to simplify watch handling
483    ///
484    /// This is not recommended to use with production watchers as it can cause desyncs.
485    /// See [#219](https://github.com/kube-rs/kube/issues/219) for details.
486    #[must_use]
487    pub fn disable_bookmarks(mut self) -> Self {
488        self.bookmarks = false;
489        self
490    }
491
492    /// Kubernetes 1.27 Streaming Lists
493    /// `sendInitialEvents=true` may be set together with `watch=true`.
494    /// In that case, the watch stream will begin with synthetic events to
495    /// produce the current state of objects in the collection. Once all such
496    /// events have been sent, a synthetic "Bookmark" event  will be sent.
497    /// The bookmark will report the ResourceVersion (RV) corresponding to the
498    /// set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation.
499    /// Afterwards, the watch stream will proceed as usual, sending watch events
500    /// corresponding to changes (subsequent to the RV) to objects watched.
501    ///
502    /// When `sendInitialEvents` option is set, we require `resourceVersionMatch`
503    /// option to also be set. The semantic of the watch request is as following:
504    /// - `resourceVersionMatch` = NotOlderThan
505    ///   is interpreted as "data at least as new as the provided `resourceVersion`"
506    ///   and the bookmark event is send when the state is synced
507    ///   to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
508    ///   If `resourceVersion` is unset, this is interpreted as "consistent read" and the
509    ///   bookmark event is send when the state is synced at least to the moment
510    ///   when request started being processed.
511    /// - `resourceVersionMatch` set to any other value or unset
512    ///   Invalid error is returned.
513    ///
514    /// Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward
515    /// compatibility reasons) and to false otherwise.
516    #[must_use]
517    pub fn initial_events(mut self) -> Self {
518        self.send_initial_events = true;
519
520        self
521    }
522
523    /// Constructor for doing Kubernetes 1.27 Streaming List watches
524    ///
525    /// Enables [`VersionMatch::NotOlderThan`] semantics and [`WatchParams::send_initial_events`].
526    pub fn streaming_lists() -> Self {
527        Self {
528            send_initial_events: true,
529            bookmarks: true, // required
530            ..WatchParams::default()
531        }
532    }
533}
534
535/// Common query parameters for put/post calls
536#[derive(Default, Clone, Debug, PartialEq)]
537pub struct PostParams {
538    /// Whether to run this as a dry run
539    pub dry_run: bool,
540    /// fieldManager is a name of the actor that is making changes
541    pub field_manager: Option<String>,
542}
543
544impl PostParams {
545    pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
546        if self.dry_run {
547            qp.append_pair("dryRun", "All");
548        }
549        if let Some(ref fm) = self.field_manager {
550            qp.append_pair("fieldManager", fm);
551        }
552    }
553
554    pub(crate) fn validate(&self) -> Result<(), Error> {
555        if let Some(field_manager) = &self.field_manager {
556            // Implement the easy part of validation, in future this may be extended to provide validation as in go code
557            // For now it's fine, because k8s API server will return an error
558            if field_manager.len() > 128 {
559                return Err(Error::Validation(
560                    "Failed to validate PostParams::field_manager!".into(),
561                ));
562            }
563        }
564        Ok(())
565    }
566}
567
568/// Describes changes that should be applied to a resource
569///
570/// Takes arbitrary serializable data for all strategies except `Json`.
571///
572/// We recommend using ([server-side](https://kubernetes.io/blog/2020/04/01/kubernetes-1.18-feature-server-side-apply-beta-2)) `Apply` patches on new kubernetes releases.
573///
574/// See [kubernetes patch docs](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment) for the older patch types.
575///
576/// Note that patches have different effects on different fields depending on their merge strategies.
577/// These strategies are configurable when deriving your [`CustomResource`](https://docs.rs/kube-derive/*/kube_derive/derive.CustomResource.html#customizing-schemas).
578///
579/// # Creating a patch via serde_json
580/// ```
581/// use kube::api::Patch;
582/// let patch = serde_json::json!({
583///     "apiVersion": "v1",
584///     "kind": "Pod",
585///     "metadata": {
586///         "name": "blog"
587///     },
588///     "spec": {
589///         "activeDeadlineSeconds": 5
590///     }
591/// });
592/// let patch = Patch::Apply(&patch);
593/// ```
594/// # Creating a patch from a type
595/// ```
596/// use kube::api::Patch;
597/// use k8s_openapi::api::rbac::v1::Role;
598/// use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
599/// let r = Role {
600///     metadata: ObjectMeta { name: Some("user".into()), ..ObjectMeta::default() },
601///     rules: Some(vec![])
602/// };
603/// let patch = Patch::Apply(&r);
604/// ```
605#[non_exhaustive]
606#[derive(Debug, PartialEq, Clone)]
607pub enum Patch<T: Serialize> {
608    /// [Server side apply](https://kubernetes.io/docs/reference/using-api/api-concepts/#server-side-apply)
609    ///
610    /// Requires kubernetes >= 1.16
611    Apply(T),
612
613    /// [JSON patch](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment)
614    ///
615    /// Using this variant will require you to explicitly provide a type for `T` at the moment.
616    ///
617    /// # Example
618    ///
619    /// ```
620    /// use kube::api::Patch;
621    /// let json_patch = json_patch::Patch(vec![]);
622    /// let patch = Patch::Json::<()>(json_patch);
623    /// ```
624    #[cfg(feature = "jsonpatch")]
625    #[cfg_attr(docsrs, doc(cfg(feature = "jsonpatch")))]
626    Json(json_patch::Patch),
627
628    /// [JSON Merge patch](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment)
629    Merge(T),
630    /// [Strategic JSON Merge patch](https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/#use-a-strategic-merge-patch-to-update-a-deployment)
631    Strategic(T),
632}
633
634impl<T: Serialize> Patch<T> {
635    pub(crate) fn is_apply(&self) -> bool {
636        matches!(self, Patch::Apply(_))
637    }
638
639    pub(crate) fn content_type(&self) -> &'static str {
640        match &self {
641            Self::Apply(_) => "application/apply-patch+yaml",
642            #[cfg(feature = "jsonpatch")]
643            #[cfg_attr(docsrs, doc(cfg(feature = "jsonpatch")))]
644            Self::Json(_) => "application/json-patch+json",
645            Self::Merge(_) => "application/merge-patch+json",
646            Self::Strategic(_) => "application/strategic-merge-patch+json",
647        }
648    }
649}
650
651impl<T: Serialize> Patch<T> {
652    pub(crate) fn serialize(&self) -> Result<Vec<u8>, serde_json::Error> {
653        match self {
654            Self::Apply(p) => serde_json::to_vec(p),
655            #[cfg(feature = "jsonpatch")]
656            #[cfg_attr(docsrs, doc(cfg(feature = "jsonpatch")))]
657            Self::Json(p) => serde_json::to_vec(p),
658            Self::Strategic(p) => serde_json::to_vec(p),
659            Self::Merge(p) => serde_json::to_vec(p),
660        }
661    }
662}
663
664/// Common query parameters for patch calls
665#[derive(Default, Clone, Debug)]
666pub struct PatchParams {
667    /// Whether to run this as a dry run
668    pub dry_run: bool,
669    /// force Apply requests. Applicable only to [`Patch::Apply`].
670    pub force: bool,
671    /// fieldManager is a name of the actor that is making changes. Required for [`Patch::Apply`]
672    /// optional for everything else.
673    pub field_manager: Option<String>,
674    /// The server-side validation directive to use. Applicable only to [`Patch::Apply`].
675    pub field_validation: Option<ValidationDirective>,
676}
677
678impl PatchParams {
679    pub(crate) fn validate<P: Serialize>(&self, patch: &Patch<P>) -> Result<(), Error> {
680        if let Some(field_manager) = &self.field_manager {
681            // Implement the easy part of validation, in future this may be extended to provide validation as in go code
682            // For now it's fine, because k8s API server will return an error
683            if field_manager.len() > 128 {
684                return Err(Error::Validation(
685                    "Failed to validate PatchParams::field_manager!".into(),
686                ));
687            }
688        }
689        if self.force && !patch.is_apply() {
690            return Err(Error::Validation(
691                "PatchParams::force only works with Patch::Apply".into(),
692            ));
693        }
694        Ok(())
695    }
696
697    pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
698        if self.dry_run {
699            qp.append_pair("dryRun", "All");
700        }
701        if self.force {
702            qp.append_pair("force", "true");
703        }
704        if let Some(ref fm) = self.field_manager {
705            qp.append_pair("fieldManager", fm);
706        }
707        if let Some(sv) = &self.field_validation {
708            qp.append_pair("fieldValidation", sv.as_str());
709        }
710    }
711
712    /// Construct `PatchParams` for server-side apply
713    #[must_use]
714    pub fn apply(manager: &str) -> Self {
715        Self {
716            field_manager: Some(manager.into()),
717            ..Self::default()
718        }
719    }
720
721    /// Force the result through on conflicts
722    ///
723    /// NB: Force is a concept restricted to the server-side [`Patch::Apply`].
724    #[must_use]
725    pub fn force(mut self) -> Self {
726        self.force = true;
727        self
728    }
729
730    /// Perform a dryRun only
731    #[must_use]
732    pub fn dry_run(mut self) -> Self {
733        self.dry_run = true;
734        self
735    }
736
737    /// Set the validation directive for `fieldValidation` during server-side apply.
738    pub fn validation(mut self, vd: ValidationDirective) -> Self {
739        self.field_validation = Some(vd);
740        self
741    }
742
743    /// Set the validation directive to `Ignore`
744    #[must_use]
745    pub fn validation_ignore(self) -> Self {
746        self.validation(ValidationDirective::Ignore)
747    }
748
749    /// Set the validation directive to `Warn`
750    #[must_use]
751    pub fn validation_warn(self) -> Self {
752        self.validation(ValidationDirective::Warn)
753    }
754
755    /// Set the validation directive to `Strict`
756    #[must_use]
757    pub fn validation_strict(self) -> Self {
758        self.validation(ValidationDirective::Strict)
759    }
760}
761
762/// Common query parameters for delete calls
763#[derive(Default, Clone, Serialize, Debug, PartialEq)]
764#[serde(rename_all = "camelCase")]
765pub struct DeleteParams {
766    /// When present, indicates that modifications should not be persisted.
767    #[serde(
768        serialize_with = "dry_run_all_ser",
769        skip_serializing_if = "std::ops::Not::not"
770    )]
771    pub dry_run: bool,
772
773    /// The duration in seconds before the object should be deleted.
774    ///
775    /// Value must be non-negative integer. The value zero indicates delete immediately.
776    /// If this value is `None`, the default grace period for the specified type will be used.
777    /// Defaults to a per object value if not specified. Zero means delete immediately.
778    #[serde(skip_serializing_if = "Option::is_none")]
779    pub grace_period_seconds: Option<u32>,
780
781    /// Whether or how garbage collection is performed.
782    ///
783    /// The default policy is decided by the existing finalizer set in
784    /// `metadata.finalizers`, and the resource-specific default policy.
785    #[serde(skip_serializing_if = "Option::is_none")]
786    pub propagation_policy: Option<PropagationPolicy>,
787
788    /// Condtions that must be fulfilled before a deletion is carried out
789    ///
790    /// If not possible, a `409 Conflict` status will be returned.
791    #[serde(skip_serializing_if = "Option::is_none")]
792    pub preconditions: Option<Preconditions>,
793}
794
795impl DeleteParams {
796    /// Construct `DeleteParams` with `PropagationPolicy::Background`.
797    ///
798    /// This allows the garbage collector to delete the dependents in the background.
799    pub fn background() -> Self {
800        Self {
801            propagation_policy: Some(PropagationPolicy::Background),
802            ..Self::default()
803        }
804    }
805
806    /// Construct `DeleteParams` with `PropagationPolicy::Foreground`.
807    ///
808    /// This is a cascading policy that deletes all dependents in the foreground.
809    pub fn foreground() -> Self {
810        Self {
811            propagation_policy: Some(PropagationPolicy::Foreground),
812            ..Self::default()
813        }
814    }
815
816    /// Construct `DeleteParams` with `PropagationPolicy::Orphan`.
817    ///
818    ///
819    /// This orpans the dependents.
820    pub fn orphan() -> Self {
821        Self {
822            propagation_policy: Some(PropagationPolicy::Orphan),
823            ..Self::default()
824        }
825    }
826
827    /// Perform a dryRun only
828    #[must_use]
829    pub fn dry_run(mut self) -> Self {
830        self.dry_run = true;
831        self
832    }
833
834    /// Set the duration in seconds before the object should be deleted.
835    #[must_use]
836    pub fn grace_period(mut self, secs: u32) -> Self {
837        self.grace_period_seconds = Some(secs);
838        self
839    }
840
841    /// Set the condtions that must be fulfilled before a deletion is carried out.
842    #[must_use]
843    pub fn preconditions(mut self, preconditions: Preconditions) -> Self {
844        self.preconditions = Some(preconditions);
845        self
846    }
847
848    pub(crate) fn is_default(&self) -> bool {
849        !self.dry_run
850            && self.grace_period_seconds.is_none()
851            && self.propagation_policy.is_none()
852            && self.preconditions.is_none()
853    }
854}
855
856// dryRun serialization differ when used as body parameters and query strings:
857// query strings are either true/false
858// body params allow only: missing field, or ["All"]
859// The latter is a very awkward API causing users to do to
860// dp.dry_run = vec!["All".into()];
861// just to turn on dry_run..
862// so we hide this detail for now.
863fn dry_run_all_ser<S>(t: &bool, s: S) -> std::result::Result<S::Ok, S::Error>
864where
865    S: serde::ser::Serializer,
866{
867    use serde::ser::SerializeTuple;
868    match t {
869        true => {
870            let mut map = s.serialize_tuple(1)?;
871            map.serialize_element("All")?;
872            map.end()
873        }
874        false => s.serialize_none(),
875    }
876}
877#[cfg(test)]
878mod test {
879    use crate::{params::WatchParams, Expression, Selector};
880
881    use super::{DeleteParams, ListParams, PatchParams};
882    #[test]
883    fn delete_param_serialize() {
884        let mut dp = DeleteParams::default();
885        let emptyser = serde_json::to_string(&dp).unwrap();
886        //println!("emptyser is: {}", emptyser);
887        assert_eq!(emptyser, "{}");
888
889        dp.dry_run = true;
890        let ser = serde_json::to_string(&dp).unwrap();
891        //println!("ser is: {}", ser);
892        assert_eq!(ser, "{\"dryRun\":[\"All\"]}");
893    }
894
895    #[test]
896    fn delete_param_constructors() {
897        let dp_background = DeleteParams::background();
898        let ser = serde_json::to_value(dp_background).unwrap();
899        assert_eq!(ser, serde_json::json!({"propagationPolicy": "Background"}));
900
901        let dp_foreground = DeleteParams::foreground();
902        let ser = serde_json::to_value(dp_foreground).unwrap();
903        assert_eq!(ser, serde_json::json!({"propagationPolicy": "Foreground"}));
904
905        let dp_orphan = DeleteParams::orphan();
906        let ser = serde_json::to_value(dp_orphan).unwrap();
907        assert_eq!(ser, serde_json::json!({"propagationPolicy": "Orphan"}));
908    }
909
910    #[test]
911    fn patch_param_serializes_field_validation() {
912        let pp = PatchParams::default().validation_ignore();
913        let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
914        pp.populate_qp(&mut qp);
915        let urlstr = qp.finish();
916        assert_eq!(String::from("some/resource?&fieldValidation=Ignore"), urlstr);
917
918        let pp = PatchParams::default().validation_warn();
919        let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
920        pp.populate_qp(&mut qp);
921        let urlstr = qp.finish();
922        assert_eq!(String::from("some/resource?&fieldValidation=Warn"), urlstr);
923
924        let pp = PatchParams::default().validation_strict();
925        let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
926        pp.populate_qp(&mut qp);
927        let urlstr = qp.finish();
928        assert_eq!(String::from("some/resource?&fieldValidation=Strict"), urlstr);
929    }
930
931    #[test]
932    fn list_params_serialize() {
933        let selector: Selector =
934            Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
935        let lp = ListParams::default().labels_from(&selector);
936        let labels = lp.label_selector.unwrap();
937        assert_eq!(labels, "env in (development,sandbox)");
938    }
939
940    #[test]
941    fn watch_params_serialize() {
942        let selector: Selector =
943            Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
944        let wp = WatchParams::default().labels_from(&selector);
945        let labels = wp.label_selector.unwrap();
946        assert_eq!(labels, "env in (development,sandbox)");
947    }
948}
949
950/// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
951#[derive(Default, Clone, Serialize, Debug, PartialEq)]
952#[serde(rename_all = "camelCase")]
953pub struct Preconditions {
954    /// Specifies the target ResourceVersion
955    #[serde(skip_serializing_if = "Option::is_none")]
956    pub resource_version: Option<String>,
957    /// Specifies the target UID
958    #[serde(skip_serializing_if = "Option::is_none")]
959    pub uid: Option<String>,
960}
961
962/// Propagation policy when deleting single objects
963#[derive(Clone, Debug, Serialize, PartialEq)]
964pub enum PropagationPolicy {
965    /// Orphan dependents
966    Orphan,
967    /// Allow the garbage collector to delete the dependents in the background
968    Background,
969    /// A cascading policy that deletes all dependents in the foreground
970    Foreground,
971}