nu-command 0.112.2

Nushell's built-in commands
Documentation
use nu_protocol::test_record;
use nu_test_support::{fs::Stub::FileWithContentToBeTrimmed, prelude::*};

#[test]
fn table_to_json_text_and_from_json_text_back_into_table() -> Result {
    let code = "
        open sgml_description.json
        | to json
        | from json
        | get glossary.GlossDiv.GlossList.GlossEntry.GlossSee
    ";

    test()
        .cwd("tests/fixtures/formats")
        .run(code)
        .expect_value_eq("markup")
}

#[test]
fn table_to_json_float_doesnt_become_int() -> Result {
    let code = "[[a]; [1.0]] | to json | from json | get 0.a";
    let outcome: Value = test().run(code)?;
    assert!(matches!(outcome, Value::Float { .. }));
    Ok(())
}

#[test]
fn from_json_text_to_table() -> Result {
    Playground::setup("filter_from_json_test_1", |dirs, sandbox| {
        sandbox.with_files(&[FileWithContentToBeTrimmed(
            "katz.txt",
            r#"
                {
                    "katz": [
                        {"name":   "Yehuda", "rusty_luck": 1},
                        {"name": "JT", "rusty_luck": 1},
                        {"name":   "Andres", "rusty_luck": 1},
                        {"name":"GorbyPuff", "rusty_luck": 1}
                    ]
                }
            "#,
        )]);

        let code = "
            open katz.txt
            | from json
            | get katz
            | get rusty_luck
            | length
        ";

        test().cwd(dirs.test()).run(code).expect_value_eq(4)
    })
}

#[test]
fn from_json_text_to_table_strict() -> Result {
    Playground::setup("filter_from_json_test_1_strict", |dirs, sandbox| {
        sandbox.with_files(&[FileWithContentToBeTrimmed(
            "katz.txt",
            r#"
                {
                    "katz": [
                        {"name":   "Yehuda", "rusty_luck": 1},
                        {"name": "JT", "rusty_luck": 1},
                        {"name":   "Andres", "rusty_luck": 1},
                        {"name":"GorbyPuff", "rusty_luck": 1}
                    ]
                }
            "#,
        )]);

        let code = "
            open katz.txt
            | from json -s
            | get katz
            | get rusty_luck
            | length
        ";

        test().cwd(dirs.test()).run(code).expect_value_eq(4)
    })
}

#[test]
fn from_json_text_recognizing_objects_independently_to_table() -> Result {
    Playground::setup("filter_from_json_test_2", |dirs, sandbox| {
        sandbox.with_files(&[FileWithContentToBeTrimmed(
            "katz.txt",
            r#"
                {"name":   "Yehuda", "rusty_luck": 1}
                {"name": "JT", "rusty_luck": 1}
                {"name":   "Andres", "rusty_luck": 1}
                {"name":"GorbyPuff", "rusty_luck": 3}
            "#,
        )]);

        let code = r#"
            open katz.txt
            | from json -o
            | where name == "GorbyPuff"
            | get rusty_luck.0
        "#;

        test().cwd(dirs.test()).run(code).expect_value_eq(3)
    })
}

#[test]
fn from_json_text_objects_is_stream() -> Result {
    Playground::setup("filter_from_json_test_2_is_stream", |dirs, sandbox| {
        sandbox.with_files(&[FileWithContentToBeTrimmed(
            "katz.txt",
            r#"
                {"name":   "Yehuda", "rusty_luck": 1}
                {"name": "JT", "rusty_luck": 1}
                {"name":   "Andres", "rusty_luck": 1}
                {"name":"GorbyPuff", "rusty_luck": 3}
            "#,
        )]);

        let code = "
            open katz.txt
            | from json -o
            | describe -n
        ";

        test().cwd(dirs.test()).run(code).expect_value_eq("stream")
    })
}

#[test]
fn from_json_text_recognizing_objects_independently_to_table_strict() -> Result {
    Playground::setup("filter_from_json_test_2_strict", |dirs, sandbox| {
        sandbox.with_files(&[FileWithContentToBeTrimmed(
            "katz.txt",
            r#"
                {"name":   "Yehuda", "rusty_luck": 1}
                {"name": "JT", "rusty_luck": 1}
                {"name":   "Andres", "rusty_luck": 1}
                {"name":"GorbyPuff", "rusty_luck": 3}
            "#,
        )]);

        let code = r#"
            open katz.txt
            | from json -o -s
            | where name == "GorbyPuff"
            | get rusty_luck.0
        "#;

        test().cwd(dirs.test()).run(code).expect_value_eq(3)
    })
}

#[test]
fn table_to_json_text() -> Result {
    Playground::setup("filter_to_json_test", |dirs, sandbox| {
        sandbox.with_files(&[FileWithContentToBeTrimmed(
            "sample.txt",
            "
                JonAndrehudaTZ,3
                GorbyPuff,100
            ",
        )]);

        let code = r#"
            open sample.txt
            | lines
            | split column "," name luck
            | select name
            | to json
            | from json
            | get 0
            | get name
        "#;

        test()
            .cwd(dirs.test())
            .run(code)
            .expect_value_eq("JonAndrehudaTZ")
    })
}

#[test]
fn table_to_json_text_strict() -> Result {
    Playground::setup("filter_to_json_test_strict", |dirs, sandbox| {
        sandbox.with_files(&[FileWithContentToBeTrimmed(
            "sample.txt",
            "
                JonAndrehudaTZ,3
                GorbyPuff,100
            ",
        )]);

        let code = r#"
            open sample.txt
            | lines
            | split column "," name luck
            | select name
            | to json
            | from json -s
            | get 0
            | get name
        "#;

        test()
            .cwd(dirs.test())
            .run(code)
            .expect_value_eq("JonAndrehudaTZ")
    })
}

#[test]
fn top_level_values_from_json() -> Result {
    for (value, type_name) in [("null", "nothing"), ("true", "bool"), ("false", "bool")] {
        let code = format!(r#""{value}" | from json | to json"#);
        test().run(&code).expect_value_eq(value)?;

        let code = format!(r#""{value}" | from json | describe"#);
        test().run(&code).expect_value_eq(type_name)?;
    }
    Ok(())
}

#[test]
fn top_level_values_from_json_strict() -> Result {
    for (value, type_name) in [("null", "nothing"), ("true", "bool"), ("false", "bool")] {
        let code = format!(r#""{value}" | from json -s | to json"#);
        test().run(&code).expect_value_eq(value)?;

        let code = format!(r#""{value}" | from json -s | describe"#);
        test().run(&code).expect_value_eq(type_name)?;
    }
    Ok(())
}

#[test]
fn strict_parsing_fails_on_comment() -> Result {
    let code = r#"'{ "a": 1, /* comment */ "b": 2 }' | from json -s"#;

    let err = test().run(code).expect_shell_error()?;
    match err {
        ShellError::Generic(err) => {
            assert_contains("error parsing JSON text", err.msg);
            Ok(())
        }
        other => Err(other.into()),
    }
}

#[test]
fn strict_parsing_fails_on_trailing_comma() -> Result {
    let code = r#"'{ "a": 1, "b": 2, }' | from json -s"#;

    let err = test().run(code).expect_shell_error()?;
    match err {
        ShellError::Generic(err) => {
            assert_contains("error parsing JSON text", err.msg);
            Ok(())
        }
        other => Err(other.into()),
    }
}

#[test]
fn ranges_to_json_as_array() -> Result {
    let code = "1..3 | to json";
    test().run(code).expect_value_eq("[\n  1,\n  2,\n  3\n]")
}

#[test]
fn unbounded_from_in_range_fails() -> Result {
    let code = "1.. | to json";

    let err = test().run(code).expect_shell_error()?;
    match err {
        ShellError::Generic(err) => {
            assert_contains("Cannot create range", err.error);
            Ok(())
        }
        other => Err(other.into()),
    }
}

#[test]
fn inf_in_range_fails() -> Result {
    let code = "inf..5 | to json";
    let err = test().run(code).expect_shell_error()?;
    assert!(matches!(err, ShellError::CannotCreateRange { .. }));

    let code = "5..inf | to json";
    let err = test().run(code).expect_shell_error()?;
    let ShellError::Generic(err) = err else {
        panic!("unexpected err, {err:?}")
    };
    assert_eq!(
        err.msg,
        "Unbounded ranges are not allowed when converting to this format"
    );

    let code = "-inf..inf | to json";
    let err = test().run(code).expect_shell_error()?;
    assert!(matches!(err, ShellError::CannotCreateRange { .. }));

    Ok(())
}

#[test]
fn test_indent_flag() -> Result {
    let code = r#"
        echo '{ "a": 1, "b": 2, "c": 3 }'
        | from json
        | to json --indent 3
    "#;

    let expected_output = "{\n   \"a\": 1,\n   \"b\": 2,\n   \"c\": 3\n}";
    test()
        .cwd("tests/fixtures/formats")
        .run(code)
        .expect_value_eq(expected_output)
}

#[test]
fn test_tabs_indent_flag() -> Result {
    let code = r#"
        echo '{ "a": 1, "b": 2, "c": 3 }'
        | from json
        | to json --tabs 2
    "#;

    let expected_output = "{\n\t\t\"a\": 1,\n\t\t\"b\": 2,\n\t\t\"c\": 3\n}";
    test()
        .cwd("tests/fixtures/formats")
        .run(code)
        .expect_value_eq(expected_output)
}

#[test]
fn test_from_json_content_type_metadata() -> Result {
    let code = r#"
        '{"a": 1, "b": 2}'
        | metadata set --content-type 'application/json' --path-columns [name]
        | from json
        | metadata
        | reject span
    "#;

    test().run(code).expect_value_eq(test_record! {
        "path_columns" => vec![Value::test_string("name")]
    })
}

#[test]
fn test_to_json_content_type_metadata() -> Result {
    let code = "
        {a: 1 b: 2}
        | to json
        | metadata
        | get content_type
    ";

    test().run(code).expect_value_eq("application/json")
}