Skip to content

Commit

Permalink
tweaks and pretty print
Browse files Browse the repository at this point in the history
  • Loading branch information
pront committed Aug 22, 2023
1 parent 5e7620d commit 0c8a56d
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 73 deletions.
29 changes: 25 additions & 4 deletions src/config/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use std::path::Path;
use std::str::FromStr;

// use proptest::prelude::{Arbitrary, BoxedStrategy, Just, Strategy};
// use proptest::prop_oneof;
use serde::de;

/// A type alias to better capture the semantics.
Expand All @@ -28,13 +30,23 @@ impl FromStr for Format {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"toml" => Ok(Format::Toml),
"json" => Ok(Format::Json),
"yaml" => Ok(Format::Yaml),
"json" => Ok(Format::Json),
_ => Err(format!("Invalid format: {}", s)),
}
}
}

// #[cfg(test)]
// impl Arbitrary for Format {
// type Parameters = ();
// fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
// prop_oneof![Just(Format::Toml), Just(Format::Json), Just(Format::Yaml),].boxed()
// }
//
// type Strategy = BoxedStrategy<Self>;
// }

impl Format {
/// Obtain the format from the file path using extension as a hint.
pub fn from_path<T: AsRef<Path>>(path: T) -> Result<Self, T> {
Expand All @@ -48,8 +60,6 @@ impl Format {
}

/// Parse the string represented in the specified format.
/// If the format is unknown - fallback to the default format and attempt
/// parsing using that.
pub fn deserialize<T>(content: &str, format: Format) -> Result<T, Vec<String>>
where
T: de::DeserializeOwned,
Expand All @@ -61,20 +71,31 @@ where
}
}

/// Serialize the specified `value` into a string.
pub fn serialize<T>(value: &T, format: Format) -> Result<String, String>
where
T: serde::ser::Serialize,
{
match format {
Format::Toml => toml::to_string(value).map_err(|e| e.to_string()),
Format::Yaml => serde_yaml::to_string(value).map_err(|e| e.to_string()),
Format::Json => serde_json::to_string(value).map_err(|e| e.to_string()),
Format::Json => serde_json::to_string_pretty(value).map_err(|e| e.to_string()),
}
}

#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;

impl Arbitrary for Format {
type Parameters = ();
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
prop_oneof![Just(Format::Toml), Just(Format::Json), Just(Format::Yaml),].boxed()
}

type Strategy = BoxedStrategy<Self>;
}

/// This test ensures the logic to guess file format from the file path
/// works correctly.
Expand Down
114 changes: 45 additions & 69 deletions src/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub struct Opts {
#[arg(long)]
pub(crate) file: Option<PathBuf>,

#[arg(short, long, default_value = "yaml")]
#[arg(long, default_value = "yaml")]
pub(crate) format: Format,
}

Expand All @@ -81,8 +81,11 @@ pub struct TransformOuter {

#[derive(Serialize, Default)]
pub struct Config {
#[serde(skip_serializing_if = "Option::is_none")]
pub sources: Option<IndexMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transforms: Option<IndexMap<String, TransformOuter>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sinks: Option<IndexMap<String, SinkOuter>>,
}

Expand All @@ -102,6 +105,14 @@ pub(crate) enum TransformInputsStrategy {
All,
}

#[derive(Serialize, Default)]
struct FullConfig {
#[serde(flatten)]
global_options: Option<GlobalOptions>,
#[serde(flatten)]
config: Config,
}

pub(crate) fn generate_example(
opts: &Opts,
transform_inputs_strategy: TransformInputsStrategy,
Expand All @@ -117,10 +128,6 @@ pub(crate) fn generate_example(
})
.collect();

let globals = GlobalOptions {
data_dir: default_data_dir(),
..Default::default()
};
let mut config = Config::default();

let mut errs = Vec::new();
Expand Down Expand Up @@ -307,59 +314,25 @@ pub(crate) fn generate_example(
return Err(errs);
}

let mut builder = if !opts.fragment {
match format::serialize(&globals, opts.format) {
Ok(s) => s,
Err(err) => {
errs.push(format!("failed to marshal globals: {err}"));
return Err(errs);
}
}
} else {
String::new()
let full_config = FullConfig {
global_options: if !opts.fragment {
Some(GlobalOptions {
data_dir: default_data_dir(),
..Default::default()
})
} else {
None
},
config,
};
if let Some(sources) = config.sources {
match format::serialize(
&{
Config {
sources: Some(sources),
..Default::default()
}
},
opts.format,
) {
Ok(v) => builder = [builder, v].join("\n"),
Err(e) => errs.push(format!("failed to marshal sources: {e}")),
}
}
if let Some(transforms) = config.transforms {
match format::serialize(
&{
Config {
transforms: Some(transforms),
..Default::default()
}
},
opts.format,
) {
Ok(v) => builder = [builder, v].join("\n"),
Err(e) => errs.push(format!("failed to marshal transforms: {e}")),
}
}
if let Some(sinks) = config.sinks {
match format::serialize(
&{
Config {
sinks: Some(sinks),
..Default::default()
}
},
opts.format,
) {
Ok(v) => builder = [builder, v].join("\n"),
Err(e) => errs.push(format!("failed to marshal sinks: {e}")),

let builder = match format::serialize(&full_config, opts.format) {
Ok(v) => v,
Err(e) => {
errs.push(format!("failed to marshal sources: {e}"));
return Err(errs);
}
}
};

let file = opts.file.as_ref();
if file.is_some() {
Expand Down Expand Up @@ -419,35 +392,39 @@ fn write_config(filepath: &Path, body: &str) -> Result<(), crate::Error> {
mod tests {
use super::*;
use crate::config::ConfigBuilder;
use proptest::prelude::*;

fn generate_and_deserialize(expression: String) {
fn generate_and_deserialize(expression: String, format: Format) {
let opts = Opts {
fragment: false,
expression,
file: None,
format: Format::Toml,
format,
};
let cfg_string = generate_example(&opts, TransformInputsStrategy::Auto).unwrap();
if let Err(error) = format::deserialize::<ConfigBuilder>(&cfg_string, opts.format) {
println!("{cfg_string}");
panic!(
"Failed to generate example for {} with error: {error:?})",
opts.expression
);
}
}

#[test]
fn generate_all() {
for name in SourceDescription::types() {
generate_and_deserialize(format!("{}//", name));
}
proptest! {
#[test]
fn generate_all(format in any::<Format>()) {
for name in SourceDescription::types() {
generate_and_deserialize(format!("{}//", name), format);
}

for name in TransformDescription::types() {
generate_and_deserialize(format!("/{}/", name));
}
for name in TransformDescription::types() {
generate_and_deserialize(format!("/{}/", name), format);
}

for name in SinkDescription::types() {
generate_and_deserialize(format!("//{}", name));
for name in SinkDescription::types() {
generate_and_deserialize(format!("//{}", name), format);
}
}
}

Expand Down Expand Up @@ -645,7 +622,6 @@ mod tests {
assert_eq!(
generate_example(&opts, TransformInputsStrategy::Auto),
Ok(indoc::indoc! {r#"
[transforms.transform0]
inputs = []
increase = 0.0
Expand Down

0 comments on commit 0c8a56d

Please sign in to comment.