Unverified Commit 2b072e08 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Implement simple JSON serializer (#411)

* Implement simple JSON serializer

* CR feedback

* Add debug assertion to `append_string_unchecked`
parent b18421d5
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ plugins {
val smithyVersion: String by project

val sdkOutputDir = buildDir.resolve("aws-sdk")
val runtimeModules = listOf("smithy-types", "smithy-xml", "smithy-http", "smithy-http-tower", "protocol-test-helpers")
val runtimeModules = listOf("smithy-types", "smithy-json", "smithy-xml", "smithy-http", "smithy-http-tower", "protocol-test-helpers")
val awsModules = listOf("aws-auth", "aws-endpoint", "aws-types", "aws-hyper", "aws-sig-auth", "aws-http")

buildscript {
+1 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ are to allow this crate to be compilable and testable in isolation, no client co
"http" = "0.2.1"
"smithy-types" = { version = "0.0.1", path = "../smithy-types" }
"smithy-http" = { version = "0.0.1", path = "../smithy-http" }
"smithy-json" = { path = "../smithy-json" }
"smithy-xml" = { path = "../smithy-xml" }
"fastrand" = "1"

+14 −0
Original line number Diff line number Diff line
[package]
name = "smithy-json"
version = "0.1.0"
authors = ["AWS Rust SDK Team <aws-sdk-rust@amazon.com>", "John DiSanti <jdisanti@amazon.com>"]
edition = "2018"

[dependencies]
itoa = "0.4"
ryu = "1.0"
smithy-types = { path = "../smithy-types" }

[dev-dependencies]
proptest = "1"
serde_json = "1.0"
+68 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

use std::borrow::Cow;

const ESCAPES: &[char] = &['"', '\\', '\u{08}', '\u{0C}', '\n', '\r', '\t'];

/// Escapes a string for embedding in a JSON string value.
pub fn escape_string(value: &str) -> Cow<str> {
    if !value.contains(ESCAPES) {
        return Cow::Borrowed(value);
    }

    let mut escaped = String::new();
    let (mut last, end) = (0, value.len());
    for (index, chr) in value
        .char_indices()
        .filter(|(_index, chr)| ESCAPES.contains(chr))
    {
        escaped.push_str(&value[last..index]);
        escaped.push_str(match chr {
            '"' => "\\\"",
            '\\' => "\\\\",
            '\u{08}' => "\\b",
            '\u{0C}' => "\\f",
            '\n' => "\\n",
            '\r' => "\\r",
            '\t' => "\\t",
            _ => unreachable!(),
        });
        last = index + 1;
    }
    escaped.push_str(&value[last..end]);
    Cow::Owned(escaped)
}

#[cfg(test)]
mod test {
    use super::escape_string;

    #[test]
    fn escape() {
        assert_eq!("", escape_string("").as_ref());
        assert_eq!("foo", escape_string("foo").as_ref());
        assert_eq!("foo\\r\\n", escape_string("foo\r\n").as_ref());
        assert_eq!("foo\\r\\nbar", escape_string("foo\r\nbar").as_ref());
        assert_eq!(r#"foo\\bar"#, escape_string(r#"foo\bar"#).as_ref());
        assert_eq!(r#"\\foobar"#, escape_string(r#"\foobar"#).as_ref());
        assert_eq!(
            r#"\bf\fo\to\r\n"#,
            escape_string("\u{08}f\u{0C}o\to\r\n").as_ref()
        );
        assert_eq!("\\\"test\\\"", escape_string("\"test\"").as_ref());
    }

    use proptest::proptest;
    proptest! {
        #[test]
        fn matches_serde_json(s: String) {
            assert_eq!(
                serde_json::to_string(&s).unwrap(),
                format!(r#""{}""#, escape_string(&s))
            )
        }
    }
}
+9 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

//! JSON Abstractions for Smithy

mod escape;
pub mod serialize;
Loading