mirror of
https://github.com/m-lamonaca/json-to-env.git
synced 2025-04-05 10:26:40 +00:00
split into lib
module
This commit is contained in:
parent
d116a49b11
commit
610e24729d
2 changed files with 236 additions and 225 deletions
235
src/lib.rs
Normal file
235
src/lib.rs
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ParseOptions {
|
||||||
|
key_separator: String,
|
||||||
|
array_separator: String,
|
||||||
|
enumerate_array: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseOptions {
|
||||||
|
pub fn new(key_separator: String, array_separator: String, enumerate_array: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
key_separator,
|
||||||
|
array_separator,
|
||||||
|
enumerate_array,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JsonParser {
|
||||||
|
options: ParseOptions,
|
||||||
|
vars: Vec<EnvVar>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsonParser {
|
||||||
|
pub fn new(options: ParseOptions) -> Self {
|
||||||
|
Self {
|
||||||
|
options,
|
||||||
|
vars: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(&mut self, json: &Value) -> &Vec<EnvVar> {
|
||||||
|
Self::parse_keys(&mut self.vars, "", json, &self.options);
|
||||||
|
&self.vars
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_keys(lines: &mut Vec<EnvVar>, key: &str, value: &Value, options: &ParseOptions) {
|
||||||
|
match value {
|
||||||
|
Value::Array(array) => {
|
||||||
|
if options.enumerate_array {
|
||||||
|
for (index, item) in array.iter().enumerate() {
|
||||||
|
let key = Self::build_key(key, &index.to_string(), &options.key_separator);
|
||||||
|
Self::parse_keys(lines, &key, item, options)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let value = array
|
||||||
|
.iter()
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(&options.array_separator);
|
||||||
|
|
||||||
|
let item = serde_json::Value::String(value);
|
||||||
|
|
||||||
|
Self::parse_keys(lines, key, &item, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Object(object) => {
|
||||||
|
for (name, value) in object {
|
||||||
|
let key = Self::build_key(key, name, &options.key_separator);
|
||||||
|
Self::parse_keys(lines, &key, value, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => lines.push(EnvVar(key.trim().to_owned(), value.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_key(prefix: &str, key: &str, separator: &str) -> String {
|
||||||
|
match prefix.is_empty() {
|
||||||
|
true => key.to_string(),
|
||||||
|
false => format!("{prefix}{separator}{key}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct EnvVar(String, Value);
|
||||||
|
|
||||||
|
impl Display for EnvVar {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self.1 {
|
||||||
|
Value::Null => write!(fmt, "{key}=null", key = self.0),
|
||||||
|
Value::Bool(bool) => write!(fmt, "{key}={bool}", key = self.0),
|
||||||
|
Value::Number(ref number) => write!(fmt, "{key}={number}", key = self.0),
|
||||||
|
Value::String(ref string) => write!(
|
||||||
|
fmt,
|
||||||
|
r#"{key}="{value}""#,
|
||||||
|
key = self.0,
|
||||||
|
value = string.replace('"', r#"\""#)
|
||||||
|
),
|
||||||
|
_ => write!(fmt, ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
use crate::{EnvVar, JsonParser, ParseOptions};
|
||||||
|
|
||||||
|
const KEY: &str = r#""key""#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_key_should_leave_key_unchanged_when_prefix_is_empty() {
|
||||||
|
// ARRANGE
|
||||||
|
let separator = "";
|
||||||
|
let input = KEY.to_owned();
|
||||||
|
let expected = KEY;
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
let result = JsonParser::build_key("", &input, separator);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
assert_eq!(result, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_key_should_leave_prepend_prefix_with_separator() {
|
||||||
|
// ARRANGE
|
||||||
|
let separator = "_";
|
||||||
|
let input = KEY.to_owned();
|
||||||
|
let expected = format!("prefix{separator}{KEY}");
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
let actual = JsonParser::build_key("prefix", &input, separator);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bool_env_var_should_be_formatted_correctly() {
|
||||||
|
// ARRANGE
|
||||||
|
let input = EnvVar(KEY.to_owned(), json!(true));
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
let result = input.to_string();
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
assert_eq!(result, r#""key"=true"#)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn numeric_env_var_should_be_formatted_correctly() {
|
||||||
|
// ARRANGE
|
||||||
|
let input = EnvVar(KEY.to_owned(), json!(1.0));
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
let result = input.to_string();
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
assert_eq!(result, r#""key"=1.0"#)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_env_var_should_be_formatted_correctly() {
|
||||||
|
// ARRANGE
|
||||||
|
let input = EnvVar(KEY.to_owned(), json!("hello"));
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
let result = input.to_string();
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
assert_eq!(result, r#""key"="hello""#)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn array_env_var_should_be_formatted_correctly() {
|
||||||
|
// ARRANGE
|
||||||
|
let input = EnvVar(KEY.to_owned(), json!([1, 2]));
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
let result = input.to_string();
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
assert_eq!(result, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_env_var_should_be_formatted_correctly() {
|
||||||
|
// ARRANGE
|
||||||
|
let input = EnvVar(KEY.to_owned(), json!({ "key": "value" }));
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
let result = input.to_string();
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
assert_eq!(result, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_array_not_enumerated() {
|
||||||
|
// ARRANGE
|
||||||
|
let json = json!({ "array": [1, 2, 3] });
|
||||||
|
let options = ParseOptions::new("__".to_string(), ",".to_string(), false);
|
||||||
|
let mut parser = JsonParser::new(options);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
let environ = parser.parse(&json);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
assert_eq!(
|
||||||
|
*environ,
|
||||||
|
vec![EnvVar(
|
||||||
|
"array".to_string(),
|
||||||
|
Value::String("1,2,3".to_string())
|
||||||
|
)]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_array_enumerated() {
|
||||||
|
// ARRANGE
|
||||||
|
let json = json!({ "array": [1, 2, 3] });
|
||||||
|
let options = ParseOptions::new("__".to_string(), ",".to_string(), true);
|
||||||
|
let mut parser = JsonParser::new(options);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
let environ = parser.parse(&json);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
assert_eq!(
|
||||||
|
*environ,
|
||||||
|
vec![
|
||||||
|
EnvVar("array__0".to_string(), Value::Number(1.into())),
|
||||||
|
EnvVar("array__1".to_string(), Value::Number(2.into())),
|
||||||
|
EnvVar("array__2".to_string(), Value::Number(3.into()))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
226
src/main.rs
226
src/main.rs
|
@ -1,11 +1,11 @@
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fmt::Display,
|
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufRead, BufReader, BufWriter, Read, Write},
|
io::{BufRead, BufReader, BufWriter, Read, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use json2env::{JsonParser, ParseOptions};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
@ -87,227 +87,3 @@ struct Args {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
enumerate_array: bool,
|
enumerate_array: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct ParseOptions {
|
|
||||||
key_separator: String,
|
|
||||||
array_separator: String,
|
|
||||||
enumerate_array: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParseOptions {
|
|
||||||
fn new(key_separator: String, array_separator: String, enumerate_array: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
key_separator,
|
|
||||||
array_separator,
|
|
||||||
enumerate_array,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct JsonParser {
|
|
||||||
options: ParseOptions,
|
|
||||||
vars: Vec<EnvVar>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JsonParser {
|
|
||||||
fn new(options: ParseOptions) -> Self {
|
|
||||||
Self {
|
|
||||||
options,
|
|
||||||
vars: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(&mut self, json: &Value) -> &Vec<EnvVar> {
|
|
||||||
Self::parse_keys(&mut self.vars, "", json, &self.options);
|
|
||||||
&self.vars
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_keys(lines: &mut Vec<EnvVar>, key: &str, value: &Value, options: &ParseOptions) {
|
|
||||||
match value {
|
|
||||||
Value::Array(array) => {
|
|
||||||
if options.enumerate_array {
|
|
||||||
for (index, item) in array.iter().enumerate() {
|
|
||||||
let key = Self::build_key(key, &index.to_string(), &options.key_separator);
|
|
||||||
Self::parse_keys(lines, &key, item, options)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let value = array
|
|
||||||
.iter()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(&options.array_separator);
|
|
||||||
|
|
||||||
let item = serde_json::Value::String(value);
|
|
||||||
|
|
||||||
Self::parse_keys(lines, key, &item, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Object(object) => {
|
|
||||||
for (name, value) in object {
|
|
||||||
let key = Self::build_key(key, name, &options.key_separator);
|
|
||||||
Self::parse_keys(lines, &key, value, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => lines.push(EnvVar(key.trim().to_owned(), value.clone())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_key(prefix: &str, key: &str, separator: &str) -> String {
|
|
||||||
match prefix.is_empty() {
|
|
||||||
true => key.to_string(),
|
|
||||||
false => format!("{prefix}{separator}{key}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
struct EnvVar(String, Value);
|
|
||||||
|
|
||||||
impl Display for EnvVar {
|
|
||||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self.1 {
|
|
||||||
Value::Null => write!(fmt, "{key}=null", key = self.0),
|
|
||||||
Value::Bool(bool) => write!(fmt, "{key}={bool}", key = self.0),
|
|
||||||
Value::Number(ref number) => write!(fmt, "{key}={number}", key = self.0),
|
|
||||||
Value::String(ref string) => write!(
|
|
||||||
fmt,
|
|
||||||
r#"{key}="{value}""#,
|
|
||||||
key = self.0,
|
|
||||||
value = string.replace('"', r#"\""#)
|
|
||||||
),
|
|
||||||
_ => write!(fmt, ""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
use crate::{EnvVar, JsonParser, ParseOptions};
|
|
||||||
|
|
||||||
const KEY: &str = r#""key""#;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn build_key_should_leave_key_unchanged_when_prefix_is_empty() {
|
|
||||||
// ARRANGE
|
|
||||||
let separator = "";
|
|
||||||
let input = KEY.to_owned();
|
|
||||||
let expected = KEY;
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
let result = JsonParser::build_key("", &input, separator);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
assert_eq!(result, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn build_key_should_leave_prepend_prefix_with_separator() {
|
|
||||||
// ARRANGE
|
|
||||||
let separator = "_";
|
|
||||||
let input = KEY.to_owned();
|
|
||||||
let expected = format!("prefix{separator}{KEY}");
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
let actual = JsonParser::build_key("prefix", &input, separator);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bool_env_var_should_be_formatted_correctly() {
|
|
||||||
// ARRANGE
|
|
||||||
let input = EnvVar(KEY.to_owned(), json!(true));
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
let result = input.to_string();
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
assert_eq!(result, r#""key"=true"#)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn numeric_env_var_should_be_formatted_correctly() {
|
|
||||||
// ARRANGE
|
|
||||||
let input = EnvVar(KEY.to_owned(), json!(1.0));
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
let result = input.to_string();
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
assert_eq!(result, r#""key"=1.0"#)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn string_env_var_should_be_formatted_correctly() {
|
|
||||||
// ARRANGE
|
|
||||||
let input = EnvVar(KEY.to_owned(), json!("hello"));
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
let result = input.to_string();
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
assert_eq!(result, r#""key"="hello""#)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn array_env_var_should_be_formatted_correctly() {
|
|
||||||
// ARRANGE
|
|
||||||
let input = EnvVar(KEY.to_owned(), json!([1, 2]));
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
let result = input.to_string();
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
assert_eq!(result, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn object_env_var_should_be_formatted_correctly() {
|
|
||||||
// ARRANGE
|
|
||||||
let input = EnvVar(KEY.to_owned(), json!({ "key": "value" }));
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
let result = input.to_string();
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
assert_eq!(result, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_array_not_enumerated() {
|
|
||||||
let json = json!({ "array": [1, 2, 3] });
|
|
||||||
let options = ParseOptions::new("__".to_string(), ",".to_string(), false);
|
|
||||||
let mut parser = JsonParser::new(options);
|
|
||||||
let environ = parser.parse(&json);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
*environ,
|
|
||||||
vec![EnvVar(
|
|
||||||
"array".to_string(),
|
|
||||||
serde_json::Value::String("1,2,3".to_string())
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_array_enumerated() {
|
|
||||||
let json = json!({ "array": [1, 2, 3] });
|
|
||||||
let options = ParseOptions::new("__".to_string(), ",".to_string(), true);
|
|
||||||
let mut parser = JsonParser::new(options);
|
|
||||||
let environ = parser.parse(&json);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
*environ,
|
|
||||||
vec![
|
|
||||||
EnvVar("array__0".to_string(), serde_json::Value::Number(1.into())),
|
|
||||||
EnvVar("array__1".to_string(), serde_json::Value::Number(2.into())),
|
|
||||||
EnvVar("array__2".to_string(), serde_json::Value::Number(3.into()))
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue