Skip to content

How to Add Resiliency to Errors in a no_std and no_alloc Context ? #87

Open
@joboudreault

Description

@joboudreault

Goal

Being able to completely ignore a field in a JSON string in a no_std and no_alloc environment with the serde_json_core crate. The JSON processed may be of two forms :

Successful response :

{
    "status": "success",
    "message": "",
    "data": {"some_data": 42}
}

Unsuccessful response :

{
    "status": "error",
    "message": "error message",
    "data": {"unknown_field": "some_string"}
}

The JSON is then converted into this Rust structure :

pub struct ApiResponse<'a, T> {
    pub status: ApiStatus,
    pub message: &'a str,
    pub data: Option<T>,
}

If the status field is "error", then the data field must be completely ignored even if there are unknown fields of any kind (string, number, object, null, ...).

Problem

Currently : there is no way to perform this deserialization without the serde_json_core::from_slice() function returning Result::Err(_). Thus, the message field is not accessible.

Expected : there exists a way to successfully deserialize the JSON string whether it is an error or not, and access the error message.

Note : All attempts have a Minimal Reproducible Example (MRE) in this GitHub repository (here).

Attempt 1 (normal)

Here, we tried simply using the #[derive(Deserialize)] macro.

Deserialization error :

Error: CustomError;; JSON does not match deserializer’s expected format.

Attempt 2

Here, we tried to deserialize with a custom deserializer thinking that serde will return None if it cannot successfully convert the data field into T.

Deserialization error :

Error: CustomError;; JSON does not match deserializer’s expected format.

Attempt 3

Here, we tried to deserialize with a custom deserializer thinking that the Result returned by the <T as Deserialize>::deserialize() function may be intercepted and mapped to Ok(None)

Deserialization error :

Error: TrailingCharacters;; JSON has non-whitespace trailing characters after the value.

Attempt 4

Here, we tried to deserialize with a custom deserializer and custom Visitor emptying all fields in the data field's object. In this attempt, we realized that all the functions in the Deserializer<'de> trait are taking ownership which disallow us to perform an operation trying to convert T or to None depending on the status field.

Deserialization panic message :

Body is a 'Data' structure: ExpectedObjectCommaOrEnd

Note: All attempts have a MRE on this GitHub repository (here).

Possible Solution

There exists a solution when using serde_json and allocating a HashMap as described in issue #1583. However, we cannot use this method because there must not be any allocator, thus HashMap cannot be used.

Environment

All compilation is run with the following Rust version :

$ cargo -Vv
cargo 1.79.0 (ffa9cf99a 2024-06-03)
release: 1.79.0
commit-hash: ffa9cf99a594e59032757403d4c780b46dc2c43a
commit-date: 2024-06-03
host: x86_64-unknown-linux-gnu
libgit2: 1.7.2 (sys:0.18.3 vendored)
libcurl: 8.6.0-DEV (sys:0.4.72+curl-8.6.0 vendored ssl:OpenSSL/1.1.1w)
ssl: OpenSSL 1.1.1w  11 Sep 2023
os: Arch Linux [64-bit]

$ rustc -Vv
rustc 1.79.0 (129f3b996 2024-06-10)
binary: rustc
commit-hash: 129f3b9964af4d4a709d1383930ade12dfe7c081
commit-date: 2024-06-10
host: x86_64-unknown-linux-gnu
release: 1.79.0
LLVM version: 18.1.7

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions