stackable_versioned/
lib.rs1use std::collections::BTreeMap;
13
14use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
15use snafu::{ErrorCompat, Snafu};
16pub use stackable_versioned_macros::versioned;
18
19pub trait TrackingFrom<T, S>
24where
25 Self: Sized,
26 S: TrackingStatus + Default,
27{
28 fn tracking_from(value: T, status: &mut S, parent: &str) -> Self;
30}
31
32pub trait TrackingInto<T, S>
38where
39 Self: Sized,
40 S: TrackingStatus + Default,
41{
42 fn tracking_into(self, status: &mut S, parent: &str) -> T;
44}
45
46impl<T, U, S> TrackingInto<U, S> for T
47where
48 S: TrackingStatus + Default,
49 U: TrackingFrom<T, S>,
50{
51 fn tracking_into(self, status: &mut S, parent: &str) -> U {
52 U::tracking_from(self, status, parent)
53 }
54}
55
56pub trait TrackingStatus {
58 fn changes(&mut self) -> &mut ChangedValues;
59}
60
61#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
68pub struct ChangedValues {
69 pub downgrades: BTreeMap<String, Vec<ChangedValue>>,
71
72 pub upgrades: BTreeMap<String, Vec<ChangedValue>>,
74 }
77
78#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
80#[serde(rename_all = "camelCase")]
81pub struct ChangedValue {
82 pub json_path: String,
84
85 #[schemars(schema_with = "raw_object_schema")]
87 pub value: serde_yaml::Value,
88}
89
90fn raw_object_schema(_: &mut schemars::r#gen::SchemaGenerator) -> Schema {
94 Schema::Object(SchemaObject {
95 instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
96 extensions: [(
97 "x-kubernetes-preserve-unknown-fields".to_owned(),
98 serde_json::Value::Bool(true),
99 )]
100 .into(),
101 ..Default::default()
102 })
103}
104
105#[derive(Debug, Snafu)]
107pub enum ParseObjectError {
108 #[snafu(display("the field {field:?} is missing"))]
109 FieldNotPresent { field: String },
110
111 #[snafu(display("the field {field:?} must be a string"))]
112 FieldNotStr { field: String },
113
114 #[snafu(display("encountered unknown object API version {api_version:?}"))]
115 UnknownApiVersion { api_version: String },
116
117 #[snafu(display("failed to deserialize object from JSON"))]
118 Deserialize { source: serde_json::Error },
119
120 #[snafu(display("unexpected object kind {kind:?}, expected {expected:?}"))]
121 UnexpectedKind { kind: String, expected: String },
122}
123
124#[derive(Debug, Snafu)]
127pub enum ConvertObjectError {
128 #[snafu(display("failed to parse object"))]
129 Parse { source: ParseObjectError },
130
131 #[snafu(display("failed to serialize object into json"))]
132 Serialize { source: serde_json::Error },
133
134 #[snafu(display("failed to parse desired API version"))]
135 ParseDesiredApiVersion {
136 source: UnknownDesiredApiVersionError,
137 },
138}
139
140impl ConvertObjectError {
141 pub fn join_errors(&self) -> String {
143 self.iter_chain()
148 .map(|err| err.to_string())
149 .collect::<Vec<String>>()
150 .join(": ")
151 }
152
153 pub fn http_status_code(&self) -> u16 {
155 match self {
156 ConvertObjectError::Parse { .. } => 400,
157 ConvertObjectError::Serialize { .. } => 500,
158
159 ConvertObjectError::ParseDesiredApiVersion {
161 source: UnknownDesiredApiVersionError { .. },
162 } => 400,
163 }
164 }
165}
166
167#[derive(Debug, Snafu)]
168#[snafu(display("unknown API version {api_version:?}"))]
169pub struct UnknownDesiredApiVersionError {
170 pub api_version: String,
171}
172
173pub fn jthong_path(parent: &str, child: &str) -> String {
174 format!("{parent}.{child}")
175}