Skip to content

Commit

Permalink
Added support for deserializing JSON values using the Json Extractor.
Browse files Browse the repository at this point in the history
Resolves #15
  • Loading branch information
sammhicks committed Aug 7, 2024
1 parent 5dac94a commit 4207cf4
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 8 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.12.1] - 2024-08-07

### Added

- Added support for deserializing JSON values using the [Json](https://docs.rs/picoserve/0.12.1/picoserve/extract/struct.Json.html) Extractor.

## [0.12.0] - 2024-07-22

### Breaking
Expand Down
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ exclude = [

[package]
name = "picoserve"
version = "0.12.0"
version = "0.12.1"
authors = ["Samuel Hicks"]
edition = "2021"
rust-version = "1.75"
Expand All @@ -47,6 +47,7 @@ lhash = { version = "1.0.1", features = ["sha1"] }
log = { version = "0.4.19", optional = true, default-features = false }
ryu = "1.0.14"
serde = { version = "1.0.171", default-features = false, features = ["derive"] }
serde-json-core = "0.6.0"
tokio = { version = "1.32.0", optional = true, features = ["io-util", "net", "time"] }

[features]
Expand All @@ -56,7 +57,7 @@ alloc = []
tokio = ["dep:tokio", "std", "serde/std"]
embassy = ["dep:embassy-time", "dep:embassy-net"]

defmt = ["dep:defmt", "embassy-net?/defmt"]
defmt = ["dep:defmt", "embassy-net?/defmt", "serde-json-core/defmt"]
log = ["dep:log"]

[dev-dependencies]
Expand Down
2 changes: 1 addition & 1 deletion examples/embassy/hello_world_defmt/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
targets = ["thumbv6m-none-eabi"]
channel = "nightly-2023-12-20"
channel = "nightly-2024-06-12"
2 changes: 1 addition & 1 deletion examples/embassy/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
targets = ["thumbv6m-none-eabi"]
channel = "nightly-2023-12-20"
channel = "nightly-2024-06-12"
60 changes: 60 additions & 0 deletions src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ use crate::{
ResponseSent,
};

pub mod json {
pub use crate::json::Json;

pub use serde_json_core::str;
}

pub use crate::json::Json;

mod private {
pub struct ViaRequest;
pub struct ViaParts;
Expand Down Expand Up @@ -453,6 +461,58 @@ impl<'r, State, T: serde::de::DeserializeOwned> FromRequest<'r, State> for Form<
}
}

#[cfg_attr(feature = "defmt", defmt::Format)]
pub enum JsonRejection {
IoError,
DeserializationError(serde_json_core::de::Error),
}

impl IntoResponse for JsonRejection {
async fn write_to<R: Read, W: crate::response::ResponseWriter<Error = R::Error>>(
self,
connection: crate::response::Connection<'_, R>,
response_writer: W,
) -> Result<ResponseSent, W::Error> {
match self {
Self::IoError => {
(StatusCode::INTERNAL_SERVER_ERROR, "IO Error")
.write_to(connection, response_writer)
.await
}
Self::DeserializationError(error) => {
(
StatusCode::BAD_REQUEST,
format_args!("Failed to parse JSON body: {error}"),
)
.write_to(connection, response_writer)
.await
}
}
}
}

impl<'r, State, T: serde::Deserialize<'r>, const UNESCAPE_BUFFER_SIZE: usize>
FromRequest<'r, State, T> for Json<T, UNESCAPE_BUFFER_SIZE>
{
type Rejection = JsonRejection;

async fn from_request<R: Read>(
_state: &'r State,
_request_parts: RequestParts<'r>,
request_body: RequestBody<'r, R>,
) -> Result<Self, Self::Rejection> {
serde_json_core::from_slice_escaped(
request_body
.read_all()
.await
.map_err(|_| JsonRejection::IoError)?,
&mut [0; UNESCAPE_BUFFER_SIZE],
)
.map(|(value, _)| Self(value))
.map_err(JsonRejection::DeserializationError)
}
}

/// Used to do reference to value conversions, mainly used with the [State] extractor to extract parts of the application state.
pub trait FromRef<T> {
/// Perform the reference to value conversion
Expand Down
3 changes: 3 additions & 0 deletions src/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/// A JSON encoded value. When serializing, the value might be serialized several times during sending, so the value must be serialized in the same way each time.
/// When values are deserialized, `UNESCAPE_BUFFER_SIZE` is the size of the temporary buffer used for unescaping strings.
pub struct Json<T, const UNESCAPE_BUFFER_SIZE: usize = 32>(pub T);
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ compile_error!("You cannot enable both tokio and embassy support");
#[cfg(feature = "alloc")]
extern crate alloc;

mod json;

#[macro_use]
mod logging;

Expand Down
7 changes: 3 additions & 4 deletions src/response/json.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//! Support for serializing and deserializing JSON structures
//! Support for serializing JSON structures
use core::fmt;

use serde::Serialize;

use crate::io::{FormatBuffer, FormatBufferWriteError, Write};

pub use crate::json::Json;

#[derive(Debug)]
struct SerializeError;

Expand Down Expand Up @@ -496,9 +498,6 @@ impl<T: serde::Serialize> super::Content for JsonBody<T> {
}
}

/// Serializes the value in JSON form. The value might be serialized several times during sending, so the value must be serialized in the same way each time.
pub struct Json<T>(pub T);

impl<T: serde::Serialize> Json<T> {
pub(crate) async fn do_write_to<W: Write>(&self, writer: &mut W) -> Result<(), W::Error> {
JsonStream::new(&self.0).write_json_value(writer).await
Expand Down

0 comments on commit 4207cf4

Please sign in to comment.