Skip to content

Commit

Permalink
chore: remove dependency on serde_json
Browse files Browse the repository at this point in the history
Since all valid JSON is also valid YAML, the `serde_yaml` library can be
used to parse all kinds of input, without requiring user selection. Also
changes the initial internal representation to be a YAML value instead
of a JSON value, which is a little more expressive.
  • Loading branch information
RomainMuller committed Apr 17, 2023
1 parent 255c8c4 commit 25ec771
Show file tree
Hide file tree
Showing 11 changed files with 372 additions and 102 deletions.
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ license-file = "LICENSE"
[dependencies]
clap = "3.0.0-beta.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
serde_with = {version = "2.0.0", features = ["json"]}
serde_with = { version = "2.0.0", features = ["json"] }
numberkit = "0.1.0"
nom = "7.0.0"
voca_rs = "1.14.0"
topological-sort = "0.2.2"
topological-sort = "0.2.2"
12 changes: 6 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::parser::lookup_table::{build_mappings, MappingsParseTree};
use crate::parser::output::{build_outputs, OutputsParseTree};
use crate::parser::parameters::{build_parameters, Parameters};
use crate::parser::resource::{build_resources, ResourceValue, ResourcesParseTree};
use serde_json::Value;
use serde_yaml::Value;
use std::collections::HashSet;

pub mod integrations;
Expand Down Expand Up @@ -56,29 +56,29 @@ pub struct CloudformationParseTree {

impl CloudformationParseTree {
pub fn build(json_obj: &Value) -> Result<CloudformationParseTree, TransmuteError> {
let parameters = match json_obj["Parameters"].as_object() {
let parameters = match json_obj["Parameters"].as_mapping() {
None => Parameters::new(),
Some(params) => build_parameters(params)?,
};

let conditions = match json_obj["Conditions"].as_object() {
let conditions = match json_obj["Conditions"].as_mapping() {
None => ConditionsParseTree::new(),
Some(x) => build_conditions(x)?,
};

// All stacks must have resources, so no checking.
let resources = build_resources(json_obj["Resources"].as_object().unwrap())?;
let resources = build_resources(json_obj["Resources"].as_mapping().unwrap())?;

let mut logical_lookup = HashSet::new();
for resource in resources.resources.iter() {
logical_lookup.insert(resource.name.clone());
}

let mappings = match json_obj["Mappings"].as_object() {
let mappings = match json_obj["Mappings"].as_mapping() {
None => MappingsParseTree::new(),
Some(x) => build_mappings(x)?,
};
let outputs = match json_obj["Outputs"].as_object() {
let outputs = match json_obj["Outputs"].as_mapping() {
None => OutputsParseTree::new(),
Some(x) => build_outputs(x)?,
};
Expand Down
18 changes: 9 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use clap::{Arg, Command};
use noctilucent::ir::CloudformationProgramIr;
use noctilucent::synthesizer::typescript_synthesizer::TypescriptSynthesizer;
use noctilucent::CloudformationParseTree;
use serde_json::Value;
use std::fs;
use serde_yaml::Value;
use std::{fs, io};

fn main() {
let matches = Command::new("Translates cfn templates to cdk typescript")
Expand All @@ -29,19 +29,19 @@ fn main() {
.long("input-format")
.required(false)
.value_parser(["json", "yaml"])
.default_value("json"),
.hide(true),
)
.get_matches();

if matches.is_present("inputFormat") {
eprintln!("--inputFormat (-f) is a no-op and will be removed in a future version. All input is treated as YAML");
eprintln!("as it is a strict super-set of JSON (all valid JSON is valid YAML).");
}

let txt_location: &str = matches.value_of("INPUT").unwrap();
let contents = fs::read_to_string(txt_location).unwrap();
let input_format: &str = matches.value_of("inputFormat").unwrap();

let value: Value = if input_format.eq("json") {
serde_json::from_str(contents.as_str()).unwrap()
} else {
serde_yaml::from_str::<Value>(contents.as_str()).unwrap()
};
let value: Value = serde_yaml::from_str::<Value>(contents.as_str()).unwrap();

let cfn_tree = CloudformationParseTree::build(&value).unwrap();
let ir = CloudformationProgramIr::new_from_parse_tree(&cfn_tree).unwrap();
Expand Down
51 changes: 31 additions & 20 deletions src/parser/condition.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::TransmuteError;
use serde_json::{Map, Value};
use serde_yaml::{Mapping, Value};
use std::borrow::Cow;
use std::collections::HashMap;

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -58,26 +59,35 @@ impl Default for ConditionsParseTree {
}
}

pub fn build_conditions(vals: &Map<String, Value>) -> Result<ConditionsParseTree, TransmuteError> {
pub fn build_conditions(vals: &Mapping) -> Result<ConditionsParseTree, TransmuteError> {
let mut conditions = ConditionsParseTree::new();

for (name, obj) in vals {
let name = name.as_str().unwrap();
let cond = build_condition_recursively(name, obj)?;
let condition = ConditionParseTree {
name: name.clone(),
name: name.to_string(),
val: cond,
};
conditions.conditions.insert(name.clone(), condition);
conditions.conditions.insert(name.to_string(), condition);
}
Ok(conditions)
}

fn build_condition_recursively(name: &str, obj: &Value) -> Result<ConditionValue, TransmuteError> {
let val = match obj {
let val: Cow<Mapping> = match obj {
Value::String(x) => return Ok(ConditionValue::Str(x.to_string())),
Value::Number(x) => return Ok(ConditionValue::Str(x.to_string())),
Value::Bool(x) => return Ok(ConditionValue::Str(x.to_string())),
Value::Object(x) => x,
Value::Mapping(x) => Cow::Borrowed(x),
Value::Tagged(x) => {
let mut mapping = Mapping::new();
mapping.insert(
serde_yaml::Value::String(format!("!{}", x.tag)),
x.value.clone(),
);
Cow::Owned(mapping)
}
_ => {
return Err(TransmuteError {
details: format!("Condition must be an object or string {name}, {obj:?}"),
Expand All @@ -88,11 +98,11 @@ fn build_condition_recursively(name: &str, obj: &Value) -> Result<ConditionValue
// At this point, we have an object-json, and need to iterate over all
// the keys
#[allow(clippy::never_loop)]
for (condition_name, condition_object) in val {
for (condition_name, condition_object) in val.as_ref() {
let cond: ConditionValue = match condition_name.as_str() {
"!And" | "Fn::And" => {
Some("!And") | Some("Fn::And") => {
let mut v: Vec<ConditionValue> = Vec::new();
let arr = match condition_object.as_array() {
let arr = match condition_object.as_sequence() {
None => {
return Err(TransmuteError::new(
format!("Condition must be an array {name}").as_str(),
Expand All @@ -107,8 +117,8 @@ fn build_condition_recursively(name: &str, obj: &Value) -> Result<ConditionValue

ConditionValue::And(v)
}
"!Equals" | "Fn::Equals" => {
let arr = match condition_object.as_array() {
Some("!Equals") | Some("Fn::Equals") => {
let arr = match condition_object.as_sequence() {
None => {
return Err(TransmuteError {
details: format!("Condition must be an array {name}"),
Expand All @@ -135,8 +145,8 @@ fn build_condition_recursively(name: &str, obj: &Value) -> Result<ConditionValue
}?;
ConditionValue::Equals(Box::new(obj1), Box::new(obj2))
}
"!Not" | "Fn::Not" => {
let arr = match condition_object.as_array() {
Some("!Not") | Some("Fn::Not") => {
let arr = match condition_object.as_sequence() {
None => {
return Err(TransmuteError {
details: format!("Condition must be an array {name}"),
Expand All @@ -155,8 +165,8 @@ fn build_condition_recursively(name: &str, obj: &Value) -> Result<ConditionValue
}?;
ConditionValue::Not(Box::new(obj1))
}
"!Or" | "Fn::Or" => {
let arr = match condition_object.as_array() {
Some("!Or") | Some("Fn::Or") => {
let arr = match condition_object.as_sequence() {
None => {
return Err(TransmuteError {
details: format!("Condition must be an array {name}"),
Expand All @@ -173,7 +183,7 @@ fn build_condition_recursively(name: &str, obj: &Value) -> Result<ConditionValue

ConditionValue::Or(v)
}
"!Condition" | "Condition" => {
Some("Condition") => {
let condition_name = match condition_object.as_str() {
None => {
return Err(TransmuteError {
Expand All @@ -184,7 +194,7 @@ fn build_condition_recursively(name: &str, obj: &Value) -> Result<ConditionValue
};
ConditionValue::Condition(condition_name.to_string())
}
"!Ref" | "Ref" => {
Some("Ref") => {
let ref_name = match condition_object.as_str() {
None => {
return Err(TransmuteError {
Expand All @@ -195,8 +205,8 @@ fn build_condition_recursively(name: &str, obj: &Value) -> Result<ConditionValue
};
ConditionValue::Ref(ref_name.to_string())
}
"!FindInMap" | "Fn::FindInMap" => {
let arr = match condition_object.as_array() {
Some("!FindInMap") | Some("Fn::FindInMap") => {
let arr = match condition_object.as_sequence() {
None => {
return Err(TransmuteError {
details: format!("Fn::FindInMap must form an array {name}"),
Expand All @@ -210,7 +220,8 @@ fn build_condition_recursively(name: &str, obj: &Value) -> Result<ConditionValue
let m3 = build_condition_recursively(name, arr.get(2).unwrap())?;
ConditionValue::FindInMap(Box::new(m1), Box::new(m2), Box::new(m3))
}
v => ConditionValue::Str(v.to_string()),
Some(v) => ConditionValue::Str(v.to_string()),
None => unimplemented!("Condition name is not a string"),
};

return Ok(cond);
Expand Down
25 changes: 14 additions & 11 deletions src/parser/lookup_table.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::primitives::WrapperF64;
use crate::TransmuteError;
use serde_json::{Map, Value};
use serde_yaml::{Mapping, Value};
use std::collections::HashMap;
use std::fmt::{Display, Formatter};

Expand Down Expand Up @@ -82,11 +82,12 @@ impl Display for MappingInnerValue {
}
}

pub fn build_mappings(vals: &Map<String, Value>) -> Result<MappingsParseTree, TransmuteError> {
pub fn build_mappings(vals: &Mapping) -> Result<MappingsParseTree, TransmuteError> {
let mut mappings = MappingsParseTree::new();
for (name, obj) in vals {
let name = name.as_str().unwrap();
let outer_mapping = build_outer_mapping(name, obj)?;
mappings.insert(name.clone(), outer_mapping);
mappings.insert(name.to_string(), outer_mapping);
}
Ok(mappings)
}
Expand All @@ -97,8 +98,9 @@ fn build_outer_mapping(name: &str, obj: &Value) -> Result<MappingParseTree, Tran
let mut outer_mapping: MappingParseTree = MappingParseTree::new();
#[allow(clippy::never_loop)]
for (outer_key, inner_mapping_obj) in val {
let outer_key = outer_key.as_str().unwrap();
let inner_mapping = build_inner_mapping(outer_key, inner_mapping_obj)?;
outer_mapping.insert(outer_key.clone(), inner_mapping);
outer_mapping.insert(outer_key.to_string(), inner_mapping);
}
Ok(outer_mapping)
}
Expand All @@ -112,8 +114,9 @@ fn build_inner_mapping(
let mut inner_mapping: HashMap<String, MappingInnerValue> = HashMap::new();
#[allow(clippy::never_loop)]
for (inner_key, inner_mapping_obj) in val {
let inner_key = inner_key.as_str().unwrap();
let val = ensure_mapping_value_type(inner_key, inner_mapping_obj)?;
inner_mapping.insert(inner_key.clone(), val);
inner_mapping.insert(inner_key.to_string(), val);
}
Ok(inner_mapping)
}
Expand All @@ -127,10 +130,10 @@ fn convert_to_string_vector(
let converted_val = match vector_val {
Value::String(x) => x.to_string(),
Value::Number(x) => x.to_string(),
_ => {
vector_val => {
return Err(TransmuteError {
details: format!(
"List values for mappings must be a string. Found {inner_key:?}, for key {vector_val}"
"List values for mappings must be a string. Found {inner_key:?}, for key {vector_val:?}"
),
});
}
Expand All @@ -140,9 +143,9 @@ fn convert_to_string_vector(
Ok(string_vector)
}

fn ensure_object<'a>(name: &str, obj: &'a Value) -> Result<&'a Map<String, Value>, TransmuteError> {
fn ensure_object<'a>(name: &str, obj: &'a Value) -> Result<&'a Mapping, TransmuteError> {
match obj {
Value::Object(x) => Ok(x),
Value::Mapping(x) => Ok(x),
_ => Err(TransmuteError {
details: format!("Mapping must be an object {name}, {obj:?}"),
}),
Expand All @@ -159,10 +162,10 @@ fn ensure_mapping_value_type(name: &str, obj: &Value) -> Result<MappingInnerValu
false => Ok(MappingInnerValue::Number(x.as_i64().unwrap())),
},
Value::Bool(x) => Ok(MappingInnerValue::Bool(*x)),
Value::Array(x) => Ok(MappingInnerValue::List(convert_to_string_vector(x, name)?)),
Value::Sequence(x) => Ok(MappingInnerValue::List(convert_to_string_vector(x, name)?)),
_ => Err(TransmuteError {
details: format!(
"Inner mapping value must be a string or array. Found {name:?}, for {obj}"
"Inner mapping value must be a string or array. Found {name:?}, for {obj:?}"
),
}),
}
Expand Down
5 changes: 3 additions & 2 deletions src/parser/output.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::parser::resource::build_resources_recursively;
use crate::{ResourceValue, TransmuteError};
use serde_json::{Map, Value};
use serde_yaml::Mapping;
use std::collections::HashMap;

#[derive(Debug)]
Expand Down Expand Up @@ -54,9 +54,10 @@ impl Default for OutputsParseTree {
}
}

pub fn build_outputs(vals: &Map<String, Value>) -> Result<OutputsParseTree, TransmuteError> {
pub fn build_outputs(vals: &Mapping) -> Result<OutputsParseTree, TransmuteError> {
let mut outputs = OutputsParseTree::new();
for (logical_id, value) in vals.iter() {
let logical_id = logical_id.as_str().unwrap();
let val = match value.get("Value") {
None => {
// All outputs *MUST* have a value. Fail
Expand Down
22 changes: 16 additions & 6 deletions src/parser/parameters.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::TransmuteError;
use serde_json::{Map, Value};
use serde_yaml::{Mapping, Value};
use std::collections::HashMap;

// template anatomy can be found here: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-anatomy.html
Expand Down Expand Up @@ -44,21 +44,31 @@ impl Default for Parameters {
}
}

pub fn build_parameters(vals: &Map<String, Value>) -> Result<Parameters, TransmuteError> {
pub fn build_parameters(vals: &Mapping) -> Result<Parameters, TransmuteError> {
let mut params = Parameters::new();
for (name, obj) in vals {
let t = match obj.get("Type") {
Some(v) => v.to_string(),
let name = name.as_str().expect("mapping key was not a string");
let t = match obj.get::<Value>("Type".into()) {
Some(Value::String(v)) => v.to_string(),
Some(bad) => {
return Err(TransmuteError {
details: format!("Type was not a string {bad:?}"),
})
}
None => {
return Err(TransmuteError {
details: format!("Type was not specified correctly {name}"),
})
}
};

let def: Option<String> = obj.get("Default").map(|d| d.to_string());
let def: Option<String> = match obj.get("Default") {
Some(Value::String(v)) => Some(v.to_string()),
Some(bad) => unimplemented!("{bad:?}"),
None => None,
};

params.add(Parameter::new(name.clone(), t, def));
params.add(Parameter::new(name.to_string(), t, def));
}

Ok(params)
Expand Down
Loading

0 comments on commit 25ec771

Please sign in to comment.