mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-26 18:48:35 +02:00
Switch to vkbeautify for XML
https://feedback.yaak.app/p/xml-pretty-formatter-not-rendering-correctly
This commit is contained in:
304
src-tauri/yaak-templates/src/format_json.rs
Normal file
304
src-tauri/yaak-templates/src/format_json.rs
Normal file
@@ -0,0 +1,304 @@
|
||||
enum FormatState {
|
||||
TemplateTag,
|
||||
String,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Formats JSON that might contain template tags (skipped entirely)
|
||||
pub fn format_json(text: &str, tab: &str) -> String {
|
||||
let mut chars = text.chars().peekable();
|
||||
|
||||
let mut new_json = "".to_string();
|
||||
let mut depth = 0;
|
||||
let mut state = FormatState::None;
|
||||
|
||||
loop {
|
||||
let rest_of_chars = chars.clone();
|
||||
let current_char = match chars.next() {
|
||||
None => break,
|
||||
Some(c) => c,
|
||||
};
|
||||
|
||||
// Handle JSON string states
|
||||
if let FormatState::String = state {
|
||||
match current_char {
|
||||
'"' => {
|
||||
state = FormatState::None;
|
||||
new_json.push(current_char);
|
||||
continue;
|
||||
}
|
||||
'\\' => {
|
||||
new_json.push(current_char);
|
||||
if let Some(c) = chars.next() {
|
||||
new_json.push(c);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
new_json.push(current_char);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Close Template tag states
|
||||
if let FormatState::TemplateTag = state {
|
||||
if rest_of_chars.take(2).collect::<String>() == "]}" {
|
||||
state = FormatState::None;
|
||||
new_json.push_str("]}");
|
||||
chars.next(); // Skip the second closing bracket
|
||||
continue;
|
||||
} else {
|
||||
new_json.push(current_char);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if rest_of_chars.take(3).collect::<String>() == "${[" {
|
||||
state = FormatState::TemplateTag;
|
||||
new_json.push_str("${[");
|
||||
chars.next(); // Skip {
|
||||
chars.next(); // Skip [
|
||||
continue;
|
||||
}
|
||||
|
||||
match current_char {
|
||||
',' => {
|
||||
new_json.push(current_char);
|
||||
new_json.push('\n');
|
||||
new_json.push_str(tab.to_string().repeat(depth).as_str());
|
||||
}
|
||||
'{' => match chars.peek() {
|
||||
Some('}') => {
|
||||
new_json.push(current_char);
|
||||
new_json.push('}');
|
||||
chars.next(); // Skip }
|
||||
}
|
||||
_ => {
|
||||
depth += 1;
|
||||
new_json.push(current_char);
|
||||
new_json.push('\n');
|
||||
new_json.push_str(tab.to_string().repeat(depth).as_str());
|
||||
}
|
||||
},
|
||||
'[' => match chars.peek() {
|
||||
Some(']') => {
|
||||
new_json.push(current_char);
|
||||
new_json.push(']');
|
||||
chars.next(); // Skip ]
|
||||
}
|
||||
_ => {
|
||||
depth += 1;
|
||||
new_json.push(current_char);
|
||||
new_json.push('\n');
|
||||
new_json.push_str(tab.to_string().repeat(depth).as_str());
|
||||
}
|
||||
},
|
||||
'}' => {
|
||||
// Guard just in case invalid JSON has more closes than opens
|
||||
if depth > 0 {
|
||||
depth -= 1;
|
||||
}
|
||||
new_json.push('\n');
|
||||
new_json.push_str(tab.to_string().repeat(depth).as_str());
|
||||
new_json.push(current_char);
|
||||
}
|
||||
']' => {
|
||||
// Guard just in case invalid JSON has more closes than opens
|
||||
if depth > 0 {
|
||||
depth -= 1;
|
||||
}
|
||||
new_json.push('\n');
|
||||
new_json.push_str(tab.to_string().repeat(depth).as_str());
|
||||
new_json.push(current_char);
|
||||
}
|
||||
':' => {
|
||||
new_json.push(current_char);
|
||||
new_json.push(' '); // Pad with space
|
||||
}
|
||||
'"' => {
|
||||
state = FormatState::String;
|
||||
new_json.push(current_char);
|
||||
}
|
||||
_ => {
|
||||
if current_char == ' '
|
||||
|| current_char == '\n'
|
||||
|| current_char == '\t'
|
||||
|| current_char == '\r'
|
||||
{
|
||||
// Don't add these
|
||||
} else {
|
||||
new_json.push(current_char);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace only lines containing whitespace with nothing
|
||||
new_json
|
||||
.lines()
|
||||
.filter(|line| !line.trim().is_empty()) // Filter out whitespace-only lines
|
||||
.collect::<Vec<&str>>() // Collect the non-empty lines into a vector
|
||||
.join("\n") // Join the lines back into a single string
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::format_json::format_json;
|
||||
|
||||
#[test]
|
||||
fn test_simple_object() {
|
||||
assert_eq!(
|
||||
format_json(r#"{"foo":"bar","baz":"qux"}"#, " "),
|
||||
r#"
|
||||
{
|
||||
"foo": "bar",
|
||||
"baz": "qux"
|
||||
}
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escaped() {
|
||||
assert_eq!(
|
||||
format_json(r#"{"foo":"Hi \"world!\""}"#, " "),
|
||||
r#"
|
||||
{
|
||||
"foo": "Hi \"world!\""
|
||||
}
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_array() {
|
||||
assert_eq!(
|
||||
format_json(r#"["foo","bar","baz","qux"]"#, " "),
|
||||
r#"
|
||||
[
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
"qux"
|
||||
]
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extra_whitespace() {
|
||||
assert_eq!(
|
||||
format_json(
|
||||
r#"["foo", "bar", "baz","qux"
|
||||
|
||||
]"#,
|
||||
" "
|
||||
),
|
||||
r#"
|
||||
[
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
"qux"
|
||||
]
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_json() {
|
||||
assert_eq!(
|
||||
format_json(r#"["foo", {"bar", }"baz",["qux" ]]"#, " "),
|
||||
r#"
|
||||
[
|
||||
"foo",
|
||||
{
|
||||
"bar",
|
||||
}"baz",
|
||||
[
|
||||
"qux"
|
||||
]
|
||||
]
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_template_tags() {
|
||||
assert_eq!(
|
||||
format_json(r#"{"foo":${[ fn("hello", "world") ]} }"#, " "),
|
||||
r#"
|
||||
{
|
||||
"foo": ${[ fn("hello", "world") ]}
|
||||
}
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_graphql_response() {
|
||||
assert_eq!(
|
||||
format_json(
|
||||
r#"{"data":{"capsules":[{"landings":null,"original_launch":null,"reuse_count":0,"status":"retired","type":"Dragon 1.0","missions":null},{"id":"5e9e2c5bf3591882af3b2665","landings":null,"original_launch":null,"reuse_count":0,"status":"retired","type":"Dragon 1.0","missions":null}]}}"#,
|
||||
" "
|
||||
),
|
||||
r#"
|
||||
{
|
||||
"data": {
|
||||
"capsules": [
|
||||
{
|
||||
"landings": null,
|
||||
"original_launch": null,
|
||||
"reuse_count": 0,
|
||||
"status": "retired",
|
||||
"type": "Dragon 1.0",
|
||||
"missions": null
|
||||
},
|
||||
{
|
||||
"id": "5e9e2c5bf3591882af3b2665",
|
||||
"landings": null,
|
||||
"original_launch": null,
|
||||
"reuse_count": 0,
|
||||
"status": "retired",
|
||||
"type": "Dragon 1.0",
|
||||
"missions": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_immediate_close() {
|
||||
assert_eq!(
|
||||
format_json(r#"{"bar":[]}"#, " "),
|
||||
r#"
|
||||
{
|
||||
"bar": []
|
||||
}
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_more_closes() {
|
||||
assert_eq!(
|
||||
format_json(r#"{}}"#, " "),
|
||||
r#"
|
||||
{}
|
||||
}
|
||||
"#
|
||||
.trim()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user