From ecf85618faf439460aedc8225b68a38d30a61e83 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Mon, 1 Aug 2022 13:44:36 +0200 Subject: [PATCH] Add wrapper for serde_yaml --- CHANGELOG.md | 11 +++++++ Cargo.toml | 4 +-- src/cli.rs | 5 ++-- src/commons/opa.rs | 7 +++-- src/commons/resources.rs | 9 +++--- src/commons/s3.rs | 12 ++++---- src/crd.rs | 3 +- src/error.rs | 4 +-- src/lib.rs | 1 + src/yaml.rs | 65 ++++++++++++++++++++++++++++++++++++++++ 10 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 src/yaml.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef55d2de..be2ceac58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Add wrapper for `serde_yaml` which adheres to the YAML specification. + Operators should use the `yaml` module instead of `serde_yaml` ([#448]). + +### Changed + +- serde\_yaml 0.8.26 -> 0.9 ([#448]) + +[#448]: https://github.com/stackabletech/operator-rs/pull/448 + ## [0.23.0] - 2022-07-26 ### Added diff --git a/Cargo.toml b/Cargo.toml index 46cd1b03b..262133ffc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ regex = "1.6.0" schemars = "0.8.10" serde = { version = "1.0.140", features = ["derive"] } serde_json = "1.0.82" -serde_yaml = "0.8.26" +serde_yaml = "0.9" strum = { version = "0.24.1", features = ["derive"] } thiserror = "1.0.31" tokio = { version = "1.20.1", features = ["macros", "rt-multi-thread"] } @@ -39,7 +39,7 @@ stackable-operator-derive = { path = "stackable-operator-derive" } [dev-dependencies] rstest = "0.15.0" tempfile = "3.3.0" -serde_yaml = "0.8" +serde_yaml = "0.9" [features] default = ["native-tls"] diff --git a/src/cli.rs b/src/cli.rs index 4205cbf93..00bd43da1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -16,6 +16,7 @@ //! use serde::{Deserialize, Serialize}; //! use stackable_operator::cli; //! use stackable_operator::error::OperatorResult; +//! use stackable_operator::yaml; //! //! #[derive(Clone, CustomResource, Debug, JsonSchema, Serialize, Deserialize)] //! #[kube( @@ -57,8 +58,8 @@ //! match opts.command { //! cli::Command::Crd => println!( //! "{}{}", -//! serde_yaml::to_string(&FooCluster::crd())?, -//! serde_yaml::to_string(&BarCluster::crd())?, +//! yaml::to_string(&FooCluster::crd())?, +//! yaml::to_string(&BarCluster::crd())?, //! ), //! cli::Command::Run { .. } => { //! // Run the operator diff --git a/src/commons/opa.rs b/src/commons/opa.rs index 764f350a2..d9ed1924b 100644 --- a/src/commons/opa.rs +++ b/src/commons/opa.rs @@ -10,6 +10,7 @@ //! use stackable_operator::kube::CustomResource; //! use stackable_operator::commons::opa::{OpaApiVersion, OpaConfig}; //! use stackable_operator::schemars::{self, JsonSchema}; +//! use stackable_operator::yaml; //! //! #[derive(Clone, CustomResource, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] //! #[kube( @@ -25,7 +26,7 @@ //! opa: Option //! } //! -//! let cluster: TestCluster = serde_yaml::from_str( +//! let cluster: TestCluster = yaml::from_str( //! " //! apiVersion: test.stackable.tech/v1alpha1 //! kind: TestCluster @@ -234,6 +235,8 @@ impl OpaConfig { #[cfg(test)] mod tests { + use crate::yaml; + use super::*; use kube::CustomResource; use schemars::{self, JsonSchema}; @@ -314,7 +317,7 @@ mod tests { } fn build_test_cluster() -> TestCluster { - serde_yaml::from_str(&format!( + yaml::from_str(&format!( " apiVersion: test/v1 kind: TestCluster diff --git a/src/commons/resources.rs b/src/commons/resources.rs index 6a4ed52ae..fee1d9ae6 100644 --- a/src/commons/resources.rs +++ b/src/commons/resources.rs @@ -215,6 +215,7 @@ where mod tests { use crate::commons::resources::{PvcConfig, Resources}; use crate::config::merge::Merge; + use crate::yaml; use k8s_openapi::api::core::v1::{PersistentVolumeClaim, ResourceRequirements}; use rstest::rstest; use serde::{Deserialize, Serialize}; @@ -320,12 +321,12 @@ mod tests { #[case] input: String, #[case] expected: String, ) { - let input_pvcconfig: PvcConfig = serde_yaml::from_str(&input).expect("illegal test input"); + let input_pvcconfig: PvcConfig = yaml::from_str(&input).expect("illegal test input"); let result = input_pvcconfig.build_pvc(&name, access_modes); let expected_volumeclaim: PersistentVolumeClaim = - serde_yaml::from_str(&expected).expect("illegal expected output"); + yaml::from_str(&expected).expect("illegal expected output"); assert_eq!(result, expected_volumeclaim); } @@ -369,11 +370,11 @@ mod tests { )] fn test_into_resourcelimits(#[case] input: String, #[case] expected: String) { let input_resources: Resources = - serde_yaml::from_str(&input).expect("illegal test input"); + yaml::from_str(&input).expect("illegal test input"); let result: ResourceRequirements = input_resources.into(); let expected_requirements: ResourceRequirements = - serde_yaml::from_str(&expected).expect("illegal expected output"); + yaml::from_str(&expected).expect("illegal expected output"); assert_eq!(result, expected_requirements); } diff --git a/src/commons/s3.rs b/src/commons/s3.rs index 5e328d5a9..7d3d92bf7 100644 --- a/src/commons/s3.rs +++ b/src/commons/s3.rs @@ -220,6 +220,7 @@ impl Default for S3AccessStyle { mod test { use crate::commons::s3::{S3AccessStyle, S3ConnectionDef}; use crate::commons::s3::{S3BucketSpec, S3ConnectionSpec}; + use crate::yaml; #[test] fn test_ser_inline() { @@ -235,14 +236,13 @@ mod test { }; assert_eq!( - serde_yaml::to_string(&bucket).unwrap(), + yaml::to_string(&bucket).unwrap(), "--- bucketName: test-bucket-name -connection: - inline: - host: host - port: 8080 - accessStyle: VirtualHosted +connection: !inline + host: host + port: 8080 + accessStyle: VirtualHosted " .to_owned() ) diff --git a/src/crd.rs b/src/crd.rs index c71400369..84a0d7788 100644 --- a/src/crd.rs +++ b/src/crd.rs @@ -5,6 +5,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::error::{Error, OperatorResult}; +use crate::yaml; use std::fs::File; use std::io::Write; use std::path::Path; @@ -76,7 +77,7 @@ pub trait CustomResourceExt: kube::CustomResourceExt { where W: Write, { - let schema = serde_yaml::to_string(&Self::crd())?; + let schema = yaml::to_string(&Self::crd())?; writer.write_all(schema.as_bytes())?; Ok(()) } diff --git a/src/error.rs b/src/error.rs index fc91c3cf3..690746ee6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use crate::product_config_utils; +use crate::{product_config_utils, yaml}; use std::path::PathBuf; #[derive(Debug, thiserror::Error)] @@ -6,7 +6,7 @@ pub enum Error { #[error("Failed to serialize YAML: {source}")] YamlSerializationError { #[from] - source: serde_yaml::Error, + source: yaml::Error, }, #[error("Kubernetes reported error: {source}")] diff --git a/src/lib.rs b/src/lib.rs index b2085e320..d79bf0165 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub mod product_config_utils; pub mod role_utils; pub mod utils; pub mod validation; +pub mod yaml; pub use crate::crd::CustomResourceExt; diff --git a/src/yaml.rs b/src/yaml.rs new file mode 100644 index 000000000..12aa9aa7a --- /dev/null +++ b/src/yaml.rs @@ -0,0 +1,65 @@ +//! Wrapper around serde_yaml which adheres to the YAML specification +//! +//! Operators should use this module instead of serde_yaml. +use serde::ser; + +use crate::error::OperatorResult; + +pub use serde_yaml::from_str; +pub use serde_yaml::Error; + +/// Serialize the given data structure as a String of YAML. +/// +/// Serialization can fail if `T`'s implementation of `Serialize` decides to +/// return an error. +pub fn to_string(value: &T) -> OperatorResult +where + T: ?Sized + ser::Serialize, +{ + let yaml = serde_yaml::to_string(value)?; + Ok(format!("---\n{}", yaml)) +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use super::*; + + #[test] + fn yaml_with_leading_dashes_can_be_deserialized() { + let yaml = "\ + ---\n\ + key: value"; + + let actual_value: BTreeMap<_, _> = from_str(yaml).expect("deserializable value"); + + let expected_value: BTreeMap<_, _> = [("key", "value")].into(); + + assert_eq!(expected_value, actual_value); + } + + #[test] + fn yaml_without_leading_dashes_can_be_deserialized() { + let yaml = "key: value"; + + let actual_value: BTreeMap<_, _> = from_str(yaml).expect("deserializable value"); + + let expected_value: BTreeMap<_, _> = [("key", "value")].into(); + + assert_eq!(expected_value, actual_value); + } + + #[test] + fn value_can_be_serialized() { + let value: BTreeMap<_, _> = [("key", "value")].into(); + + let actual_yaml = to_string(&value).expect("serializable value"); + + let expected_yaml = "\ + ---\n\ + key: value\n"; + + assert_eq!(expected_yaml, actual_yaml); + } +}