diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b703662..fe0ed7c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,19 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- YAML module added with a function to serialize a data structure as an + explicit YAML document. The YAML documents generated by the functions in + `crd::CustomResourceExt` are now explicit documents and can be safely + concatenated to produce a YAML stream ([#450]). + ### Changed - Objects are now streamed rather than polled when waiting for them to be deleted ([#452]). +- serde\_yaml 0.8.26 -> 0.9.9 ([#450]) +[#450]: https://github.com/stackabletech/operator-rs/pull/450 [#452]: https://github.com/stackabletech/operator-rs/pull/452 ## [0.24.0] - 2022-08-04 diff --git a/Cargo.toml b/Cargo.toml index 0225fbdc..6e336dde 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.9" strum = { version = "0.24.1", features = ["derive"] } thiserror = "1.0.31" tokio = { version = "1.20.1", features = ["macros", "rt-multi-thread"] } @@ -38,7 +38,6 @@ stackable-operator-derive = { path = "stackable-operator-derive" } [dev-dependencies] rstest = "0.15.0" tempfile = "3.3.0" -serde_yaml = "0.8" [features] default = ["native-tls"] diff --git a/src/cli.rs b/src/cli.rs index 4205cbf9..8106460b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -11,10 +11,10 @@ //! ```no_run //! // Handle CLI arguments //! use clap::{crate_version, Parser}; -//! use kube::{CustomResource, CustomResourceExt}; +//! use kube::CustomResource; //! use schemars::JsonSchema; //! use serde::{Deserialize, Serialize}; -//! use stackable_operator::cli; +//! use stackable_operator::{CustomResourceExt, cli}; //! use stackable_operator::error::OperatorResult; //! //! #[derive(Clone, CustomResource, Debug, JsonSchema, Serialize, Deserialize)] @@ -55,11 +55,10 @@ //! let opts = Opts::from_args(); //! //! match opts.command { -//! cli::Command::Crd => println!( -//! "{}{}", -//! serde_yaml::to_string(&FooCluster::crd())?, -//! serde_yaml::to_string(&BarCluster::crd())?, -//! ), +//! cli::Command::Crd => { +//! FooCluster::print_yaml_schema()?; +//! BarCluster::print_yaml_schema()?; +//! }, //! cli::Command::Run { .. } => { //! // Run the operator //! } diff --git a/src/commons/s3.rs b/src/commons/s3.rs index cfba7128..9361466a 100644 --- a/src/commons/s3.rs +++ b/src/commons/s3.rs @@ -222,8 +222,11 @@ impl Default for S3AccessStyle { #[cfg(test)] mod test { + use std::str; + use crate::commons::s3::{S3AccessStyle, S3ConnectionDef}; use crate::commons::s3::{S3BucketSpec, S3ConnectionSpec}; + use crate::yaml; #[test] fn test_ser_inline() { @@ -238,17 +241,19 @@ mod test { })), }; - assert_eq!( - serde_yaml::to_string(&bucket).unwrap(), - "--- + let mut buf = Vec::new(); + yaml::serialize_to_explicit_document(&mut buf, &bucket).expect("serializable value"); + let actual_yaml = str::from_utf8(&buf).expect("UTF-8 encoded document"); + + let expected_yaml = "--- bucketName: test-bucket-name connection: inline: host: host port: 8080 accessStyle: VirtualHosted -" - .to_owned() - ) +"; + + assert_eq!(expected_yaml, actual_yaml) } } diff --git a/src/crd.rs b/src/crd.rs index c7140036..dbdb6cd7 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; @@ -72,28 +73,34 @@ pub trait HasApplication { /// (e.g. creation) of `CustomResourceDefinition`s in Kubernetes. pub trait CustomResourceExt: kube::CustomResourceExt { /// Generates a YAML CustomResourceDefinition and writes it to a `Write`. + /// + /// The generated YAML string is an explicit document with leading dashes (`---`). fn generate_yaml_schema(mut writer: W) -> OperatorResult<()> where W: Write, { - let schema = serde_yaml::to_string(&Self::crd())?; - writer.write_all(schema.as_bytes())?; - Ok(()) + yaml::serialize_to_explicit_document(&mut writer, &Self::crd()) } /// Generates a YAML CustomResourceDefinition and writes it to the specified file. + /// + /// The written YAML string is an explicit document with leading dashes (`---`). fn write_yaml_schema>(path: P) -> OperatorResult<()> { let writer = File::create(path)?; Self::generate_yaml_schema(writer) } /// Generates a YAML CustomResourceDefinition and prints it to stdout. + /// + /// The printed YAML string is an explicit document with leading dashes (`---`). fn print_yaml_schema() -> OperatorResult<()> { let writer = std::io::stdout(); Self::generate_yaml_schema(writer) } - // Returns the YAML schema of this CustomResourceDefinition as a string. + /// Returns the YAML schema of this CustomResourceDefinition as a string. + /// + /// The written YAML string is an explicit document with leading dashes (`---`). fn yaml_schema() -> OperatorResult { let mut writer = Vec::new(); Self::generate_yaml_schema(&mut writer)?; diff --git a/src/lib.rs b/src/lib.rs index b9f9e463..f993d09b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,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 00000000..4ccdc099 --- /dev/null +++ b/src/yaml.rs @@ -0,0 +1,59 @@ +//! Utility functions for processing data in the YAML file format +use std::io::Write; + +use serde::ser; + +use crate::error::OperatorResult; + +/// Serializes the given data structure as an explicit YAML document and writes it to a [`Write`]. +/// +/// Enums are serialized as a YAML map containing one entry in which the key identifies the variant +/// name. +/// +/// # Example +/// +/// ``` +/// use serde::Serialize; +/// use stackable_operator::yaml; +/// +/// #[derive(Serialize)] +/// #[serde(rename_all = "camelCase")] +/// enum Connection { +/// Inline(String), +/// Reference(String), +/// } +/// +/// #[derive(Serialize)] +/// struct Spec { +/// connection: Connection, +/// } +/// +/// let value = Spec { +/// connection: Connection::Inline("http://localhost".into()), +/// }; +/// +/// let mut buf = Vec::new(); +/// yaml::serialize_to_explicit_document(&mut buf, &value).unwrap(); +/// let actual_yaml = std::str::from_utf8(&buf).unwrap(); +/// +/// let expected_yaml = "--- +/// connection: +/// inline: http://localhost +/// "; +/// +/// assert_eq!(expected_yaml, actual_yaml); +/// ``` +/// +/// # Errors +/// +/// Serialization can fail if `T`'s implementation of `Serialize` decides to return an error. +pub fn serialize_to_explicit_document(mut writer: W, value: &T) -> OperatorResult<()> +where + T: ser::Serialize, + W: Write, +{ + writer.write_all(b"---\n")?; + let mut serializer = serde_yaml::Serializer::new(writer); + serde_yaml::with::singleton_map_recursive::serialize(value, &mut serializer)?; + Ok(()) +}