use crate::{request::Error, Selector};
use serde::Serialize;
#[derive(Clone, Debug, PartialEq)]
pub enum VersionMatch {
NotOlderThan,
Exact,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ListParams {
pub label_selector: Option<String>,
pub field_selector: Option<String>,
pub timeout: Option<u32>,
pub limit: Option<u32>,
pub continue_token: Option<String>,
pub version_match: Option<VersionMatch>,
pub resource_version: Option<String>,
}
impl ListParams {
pub(crate) fn validate(&self) -> Result<(), Error> {
if let Some(rv) = &self.resource_version {
if self.version_match == Some(VersionMatch::Exact) && rv == "0" {
return Err(Error::Validation(
"A non-zero resource_version is required when using an Exact match".into(),
));
}
} else if self.version_match.is_some() {
return Err(Error::Validation(
"A resource_version is required when using an explicit match".into(),
));
}
Ok(())
}
pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
if let Some(fields) = &self.field_selector {
qp.append_pair("fieldSelector", fields);
}
if let Some(labels) = &self.label_selector {
qp.append_pair("labelSelector", labels);
}
if let Some(limit) = &self.limit {
qp.append_pair("limit", &limit.to_string());
}
if let Some(continue_token) = &self.continue_token {
qp.append_pair("continue", continue_token);
} else {
if let Some(rv) = &self.resource_version {
if rv != "0" || self.limit.is_none() {
qp.append_pair("resourceVersion", rv.as_str());
match &self.version_match {
None => {}
Some(VersionMatch::NotOlderThan) => {
qp.append_pair("resourceVersionMatch", "NotOlderThan");
}
Some(VersionMatch::Exact) => {
qp.append_pair("resourceVersionMatch", "Exact");
}
}
}
}
}
}
}
impl ListParams {
#[must_use]
pub fn timeout(mut self, timeout_secs: u32) -> Self {
self.timeout = Some(timeout_secs);
self
}
#[must_use]
pub fn fields(mut self, field_selector: &str) -> Self {
self.field_selector = Some(field_selector.to_string());
self
}
#[must_use]
pub fn labels(mut self, label_selector: &str) -> Self {
self.label_selector = Some(label_selector.to_string());
self
}
#[must_use]
pub fn labels_from(mut self, selector: &Selector) -> Self {
self.label_selector = Some(selector.to_string());
self
}
#[must_use]
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
#[must_use]
pub fn continue_token(mut self, token: &str) -> Self {
self.continue_token = Some(token.to_string());
self
}
#[must_use]
pub fn at(mut self, resource_version: &str) -> Self {
self.resource_version = Some(resource_version.into());
self
}
#[must_use]
pub fn matching(mut self, version_match: VersionMatch) -> Self {
self.version_match = Some(version_match);
self
}
#[must_use]
pub fn match_any(self) -> Self {
self.matching(VersionMatch::NotOlderThan).at("0")
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct GetParams {
pub resource_version: Option<String>,
}
impl GetParams {
#[must_use]
pub fn at(resource_version: &str) -> Self {
Self {
resource_version: Some(resource_version.into()),
}
}
#[must_use]
pub fn any() -> Self {
Self::at("0")
}
}
#[derive(Clone, Debug)]
pub enum ValidationDirective {
Strict,
Warn,
Ignore,
}
impl ValidationDirective {
pub fn as_str(&self) -> &str {
match self {
Self::Strict => "Strict",
Self::Warn => "Warn",
Self::Ignore => "Ignore",
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct WatchParams {
pub label_selector: Option<String>,
pub field_selector: Option<String>,
pub timeout: Option<u32>,
pub bookmarks: bool,
pub send_initial_events: bool,
}
impl WatchParams {
pub(crate) fn validate(&self) -> Result<(), Error> {
if let Some(to) = &self.timeout {
if *to >= 295 {
return Err(Error::Validation("WatchParams::timeout must be < 295s".into()));
}
}
if self.send_initial_events && !self.bookmarks {
return Err(Error::Validation(
"WatchParams::bookmarks must be set when using send_initial_events".into(),
));
}
Ok(())
}
pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
qp.append_pair("watch", "true");
qp.append_pair("timeoutSeconds", &self.timeout.unwrap_or(290).to_string());
if let Some(fields) = &self.field_selector {
qp.append_pair("fieldSelector", fields);
}
if let Some(labels) = &self.label_selector {
qp.append_pair("labelSelector", labels);
}
if self.bookmarks {
qp.append_pair("allowWatchBookmarks", "true");
}
if self.send_initial_events {
qp.append_pair("sendInitialEvents", "true");
qp.append_pair("resourceVersionMatch", "NotOlderThan");
}
}
}
impl Default for WatchParams {
fn default() -> Self {
Self {
bookmarks: true,
label_selector: None,
field_selector: None,
timeout: None,
send_initial_events: false,
}
}
}
impl WatchParams {
#[must_use]
pub fn timeout(mut self, timeout_secs: u32) -> Self {
self.timeout = Some(timeout_secs);
self
}
#[must_use]
pub fn fields(mut self, field_selector: &str) -> Self {
self.field_selector = Some(field_selector.to_string());
self
}
#[must_use]
pub fn labels(mut self, label_selector: &str) -> Self {
self.label_selector = Some(label_selector.to_string());
self
}
#[must_use]
pub fn labels_from(mut self, selector: &Selector) -> Self {
self.label_selector = Some(selector.to_string());
self
}
#[must_use]
pub fn disable_bookmarks(mut self) -> Self {
self.bookmarks = false;
self
}
#[must_use]
pub fn initial_events(mut self) -> Self {
self.send_initial_events = true;
self
}
pub fn streaming_lists() -> Self {
Self {
send_initial_events: true,
bookmarks: true, ..WatchParams::default()
}
}
}
#[derive(Default, Clone, Debug, PartialEq)]
pub struct PostParams {
pub dry_run: bool,
pub field_manager: Option<String>,
}
impl PostParams {
pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
if self.dry_run {
qp.append_pair("dryRun", "All");
}
if let Some(ref fm) = self.field_manager {
qp.append_pair("fieldManager", fm);
}
}
pub(crate) fn validate(&self) -> Result<(), Error> {
if let Some(field_manager) = &self.field_manager {
if field_manager.len() > 128 {
return Err(Error::Validation(
"Failed to validate PostParams::field_manager!".into(),
));
}
}
Ok(())
}
}
#[non_exhaustive]
#[derive(Debug, PartialEq, Clone)]
pub enum Patch<T: Serialize> {
Apply(T),
#[cfg(feature = "jsonpatch")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonpatch")))]
Json(json_patch::Patch),
Merge(T),
Strategic(T),
}
impl<T: Serialize> Patch<T> {
pub(crate) fn is_apply(&self) -> bool {
matches!(self, Patch::Apply(_))
}
pub(crate) fn content_type(&self) -> &'static str {
match &self {
Self::Apply(_) => "application/apply-patch+yaml",
#[cfg(feature = "jsonpatch")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonpatch")))]
Self::Json(_) => "application/json-patch+json",
Self::Merge(_) => "application/merge-patch+json",
Self::Strategic(_) => "application/strategic-merge-patch+json",
}
}
}
impl<T: Serialize> Patch<T> {
pub(crate) fn serialize(&self) -> Result<Vec<u8>, serde_json::Error> {
match self {
Self::Apply(p) => serde_json::to_vec(p),
#[cfg(feature = "jsonpatch")]
#[cfg_attr(docsrs, doc(cfg(feature = "jsonpatch")))]
Self::Json(p) => serde_json::to_vec(p),
Self::Strategic(p) => serde_json::to_vec(p),
Self::Merge(p) => serde_json::to_vec(p),
}
}
}
#[derive(Default, Clone, Debug)]
pub struct PatchParams {
pub dry_run: bool,
pub force: bool,
pub field_manager: Option<String>,
pub field_validation: Option<ValidationDirective>,
}
impl PatchParams {
pub(crate) fn validate<P: Serialize>(&self, patch: &Patch<P>) -> Result<(), Error> {
if let Some(field_manager) = &self.field_manager {
if field_manager.len() > 128 {
return Err(Error::Validation(
"Failed to validate PatchParams::field_manager!".into(),
));
}
}
if self.force && !patch.is_apply() {
return Err(Error::Validation(
"PatchParams::force only works with Patch::Apply".into(),
));
}
Ok(())
}
pub(crate) fn populate_qp(&self, qp: &mut form_urlencoded::Serializer<String>) {
if self.dry_run {
qp.append_pair("dryRun", "All");
}
if self.force {
qp.append_pair("force", "true");
}
if let Some(ref fm) = self.field_manager {
qp.append_pair("fieldManager", fm);
}
if let Some(sv) = &self.field_validation {
qp.append_pair("fieldValidation", sv.as_str());
}
}
#[must_use]
pub fn apply(manager: &str) -> Self {
Self {
field_manager: Some(manager.into()),
..Self::default()
}
}
#[must_use]
pub fn force(mut self) -> Self {
self.force = true;
self
}
#[must_use]
pub fn dry_run(mut self) -> Self {
self.dry_run = true;
self
}
pub fn validation(mut self, vd: ValidationDirective) -> Self {
self.field_validation = Some(vd);
self
}
#[must_use]
pub fn validation_ignore(self) -> Self {
self.validation(ValidationDirective::Ignore)
}
#[must_use]
pub fn validation_warn(self) -> Self {
self.validation(ValidationDirective::Warn)
}
#[must_use]
pub fn validation_strict(self) -> Self {
self.validation(ValidationDirective::Strict)
}
}
#[derive(Default, Clone, Serialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteParams {
#[serde(
serialize_with = "dry_run_all_ser",
skip_serializing_if = "std::ops::Not::not"
)]
pub dry_run: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub grace_period_seconds: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub propagation_policy: Option<PropagationPolicy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preconditions: Option<Preconditions>,
}
impl DeleteParams {
pub fn background() -> Self {
Self {
propagation_policy: Some(PropagationPolicy::Background),
..Self::default()
}
}
pub fn foreground() -> Self {
Self {
propagation_policy: Some(PropagationPolicy::Foreground),
..Self::default()
}
}
pub fn orphan() -> Self {
Self {
propagation_policy: Some(PropagationPolicy::Orphan),
..Self::default()
}
}
#[must_use]
pub fn dry_run(mut self) -> Self {
self.dry_run = true;
self
}
#[must_use]
pub fn grace_period(mut self, secs: u32) -> Self {
self.grace_period_seconds = Some(secs);
self
}
#[must_use]
pub fn preconditions(mut self, preconditions: Preconditions) -> Self {
self.preconditions = Some(preconditions);
self
}
pub(crate) fn is_default(&self) -> bool {
!self.dry_run
&& self.grace_period_seconds.is_none()
&& self.propagation_policy.is_none()
&& self.preconditions.is_none()
}
}
fn dry_run_all_ser<S>(t: &bool, s: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
use serde::ser::SerializeTuple;
match t {
true => {
let mut map = s.serialize_tuple(1)?;
map.serialize_element("All")?;
map.end()
}
false => s.serialize_none(),
}
}
#[cfg(test)]
mod test {
use crate::{params::WatchParams, Expression, Selector};
use super::{DeleteParams, ListParams, PatchParams};
#[test]
fn delete_param_serialize() {
let mut dp = DeleteParams::default();
let emptyser = serde_json::to_string(&dp).unwrap();
assert_eq!(emptyser, "{}");
dp.dry_run = true;
let ser = serde_json::to_string(&dp).unwrap();
assert_eq!(ser, "{\"dryRun\":[\"All\"]}");
}
#[test]
fn delete_param_constructors() {
let dp_background = DeleteParams::background();
let ser = serde_json::to_value(dp_background).unwrap();
assert_eq!(ser, serde_json::json!({"propagationPolicy": "Background"}));
let dp_foreground = DeleteParams::foreground();
let ser = serde_json::to_value(dp_foreground).unwrap();
assert_eq!(ser, serde_json::json!({"propagationPolicy": "Foreground"}));
let dp_orphan = DeleteParams::orphan();
let ser = serde_json::to_value(dp_orphan).unwrap();
assert_eq!(ser, serde_json::json!({"propagationPolicy": "Orphan"}));
}
#[test]
fn patch_param_serializes_field_validation() {
let pp = PatchParams::default().validation_ignore();
let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
pp.populate_qp(&mut qp);
let urlstr = qp.finish();
assert_eq!(String::from("some/resource?&fieldValidation=Ignore"), urlstr);
let pp = PatchParams::default().validation_warn();
let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
pp.populate_qp(&mut qp);
let urlstr = qp.finish();
assert_eq!(String::from("some/resource?&fieldValidation=Warn"), urlstr);
let pp = PatchParams::default().validation_strict();
let mut qp = form_urlencoded::Serializer::new(String::from("some/resource?"));
pp.populate_qp(&mut qp);
let urlstr = qp.finish();
assert_eq!(String::from("some/resource?&fieldValidation=Strict"), urlstr);
}
#[test]
fn list_params_serialize() {
let selector: Selector =
Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
let lp = ListParams::default().labels_from(&selector);
let labels = lp.label_selector.unwrap();
assert_eq!(labels, "env in (development,sandbox)");
}
#[test]
fn watch_params_serialize() {
let selector: Selector =
Expression::In("env".into(), ["development".into(), "sandbox".into()].into()).into();
let wp = WatchParams::default().labels_from(&selector);
let labels = wp.label_selector.unwrap();
assert_eq!(labels, "env in (development,sandbox)");
}
}
#[derive(Default, Clone, Serialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Preconditions {
#[serde(skip_serializing_if = "Option::is_none")]
pub resource_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uid: Option<String>,
}
#[derive(Clone, Debug, Serialize, PartialEq)]
pub enum PropagationPolicy {
Orphan,
Background,
Foreground,
}