Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helpers to deserialize into Option or Result #17

Merged
merged 3 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## Unreleased

- Add helpers to deserialize into `std::option::Option` or `std::result::Result` ([#17](https://github.com/informalsystems/itf-rs/pull/17))

## v0.2.3

*March 25th, 2024*
Expand Down
6 changes: 4 additions & 2 deletions src/de.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Helpers for annotating types to deserialize from ITF values.

use serde::de::DeserializeOwned;

use crate::Value;
Expand All @@ -6,12 +8,12 @@ mod error;
pub use error::Error;

mod helpers;
pub use helpers::{As, Integer, Same};
pub use helpers::{As, Integer, Option, Result, Same};

mod deserializer;

#[doc(hidden)]
pub fn decode_value<T>(value: Value) -> Result<T, Error>
pub fn decode_value<T>(value: Value) -> std::result::Result<T, Error>
where
T: DeserializeOwned,
{
Expand Down
1 change: 1 addition & 0 deletions src/de/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::fmt;

/// Error type for deserialization.
#[derive(Debug)]
pub enum Error {
Custom(String),
Expand Down
140 changes: 140 additions & 0 deletions src/de/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use num_bigint::BigInt;
use serde::{Deserialize, Serialize};

pub use serde_with::{As, Same};

Expand Down Expand Up @@ -71,3 +72,142 @@ pub use serde_with::{As, Same};
/// itf::from_value::<Vec<FooBarMixInt>>(json.clone()).unwrap();
/// ```
pub type Integer = serde_with::TryFromInto<BigInt>;

/// Helper for `serde` to deserialize types isomorphic to [`std::option::Option`].
///
/// To be used in conjunction with [`As`].
///
/// ## Example
///
/// ```rust
/// use serde::Deserialize;
/// use serde_json::json;
///
/// use itf::de::{self, As};
///
/// #[derive(Debug, PartialEq, Deserialize)]
/// struct FooOption {
/// #[serde(with = "As::<de::Option::<_>>")]
/// foo: Option<u64>,
/// }
///
/// let some_itf = json!({
/// "foo": {
/// "tag": "Some",
/// "value": 42,
/// }
/// });
///
/// let some_foo = itf::from_value::<FooOption>(some_itf).unwrap();
/// assert_eq!(some_foo, FooOption { foo: Some(42) });
///
/// let none_itf = json!({
/// "foo": {
/// "tag": "None",
/// "value": {},
/// }
/// });
///
/// let none_foo = itf::from_value::<FooOption>(none_itf).unwrap();
/// assert_eq!(none_foo, FooOption { foo: None });
/// ```
pub type Option<T> = serde_with::FromInto<QuintOption<T>>;

/// A type isomorphic to [`std::option::Option`].
///
/// Can either be used directly or with [`As`] together with [`Option`].
#[derive(
Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
)]
#[serde(tag = "tag", content = "value")]
pub enum QuintOption<T> {
#[default]
None,
Some(T),
}

impl<T> From<std::option::Option<T>> for QuintOption<T> {
fn from(opt: std::option::Option<T>) -> Self {
match opt {
Some(value) => QuintOption::Some(value),
None => QuintOption::None,
}
}
}

impl<T> From<QuintOption<T>> for std::option::Option<T> {
fn from(opt: QuintOption<T>) -> Self {
match opt {
QuintOption::Some(value) => Some(value),
QuintOption::None => None,
}
}
}

/// Helper for `serde` to deserialize types isomorphic to [`std::result::Result`].
///
/// To be used in conjunction with [`As`].
///
/// ## Example
///
/// ```rust
/// use serde::Deserialize;
/// use serde_json::json;
///
/// use itf::de::{self, As};
///
/// #[derive(Debug, PartialEq, Deserialize)]
/// struct FooResult {
/// #[serde(with = "As::<de::Result::<_, _>>")]
/// foo: Result<u64, u64>,
/// }
///
/// let ok_itf = json!({
/// "foo": {
/// "tag": "Ok",
/// "value": 42,
/// }
/// });
///
/// let ok = itf::from_value::<FooResult>(ok_itf).unwrap();
/// assert_eq!(ok.foo, Ok(42));
///
/// let err_itf = json!({
/// "foo": {
/// "tag": "Err",
/// "value": 42,
/// }
/// });
///
/// let err = itf::from_value::<FooResult>(err_itf).unwrap();
/// assert_eq!(err.foo, Err(42));
/// ```
pub type Result<T, E> = serde_with::FromInto<QuintResult<T, E>>;

/// A type isomorphic to [`std::result::Result`].
///
/// Can either be used directly or with [`As`] together with [`Result`].
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(tag = "tag", content = "value")]
pub enum QuintResult<T, E> {
Ok(T),
Err(E),
}

impl<T, E> From<std::result::Result<T, E>> for QuintResult<T, E> {
fn from(opt: std::result::Result<T, E>) -> Self {
match opt {
Ok(value) => QuintResult::Ok(value),
Err(e) => QuintResult::Err(e),
}
}
}

impl<T, E> From<QuintResult<T, E>> for std::result::Result<T, E> {
fn from(opt: QuintResult<T, E>) -> Self {
match opt {
QuintResult::Ok(value) => Ok(value),
QuintResult::Err(e) => Err(e),
}
}
}
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Error type for the library.

/// Error type for the library.
#[derive(Debug)]
pub enum Error {
Expand Down
2 changes: 2 additions & 0 deletions src/runner.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Infrastructure for running a trace against a concrete implementation.

use crate::Trace;

pub trait Runner {
Expand Down
2 changes: 2 additions & 0 deletions src/state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Defines the states contained within an ITF trace.

use std::collections::BTreeMap;

use serde::de::DeserializeOwned;
Expand Down
2 changes: 2 additions & 0 deletions src/trace.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Defines an ITF trace.

use std::collections::BTreeMap;

use serde::de::DeserializeOwned;
Expand Down
72 changes: 70 additions & 2 deletions tests/sum_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn parse_trace() {
}

#[test]
fn test_deserialize_some() {
fn test_deserialize_adhoc_some() {
let some_itf = json!({
"tag": "Some",
"value": {"#bigint": "1"},
Expand All @@ -39,7 +39,7 @@ fn test_deserialize_some() {
}

#[test]
fn test_deserialize_none() {
fn test_deserialize_adhoc_none() {
let none_itf = json!({
"tag": "None",
"value": {},
Expand Down Expand Up @@ -93,3 +93,71 @@ fn test_deserialize_enum() {
let foobar = itf::from_value::<Enum>(foobar_itf).unwrap();
assert_eq!(foobar, Enum::FooBar("hello".to_string(), 42.into(), true));
}

#[derive(Debug, PartialEq, Deserialize)]
struct FooOption {
#[serde(with = "As::<itf::de::Option::<_>>")]
foo: Option<u64>,
}

#[test]
#[allow(clippy::disallowed_names)]
fn test_deserialize_option_some() {
let some_itf = json!({
"foo": {
"tag": "Some",
"value": 42,
}
});

let some_foo = itf::from_value::<FooOption>(some_itf).unwrap();
assert_eq!(some_foo, FooOption { foo: Some(42) });
}

#[test]
#[allow(clippy::disallowed_names)]
fn test_deserialize_option_none() {
let none_itf = json!({
"foo": {
"tag": "None",
"value": {},
}
});

let none_foo = itf::from_value::<FooOption>(none_itf).unwrap();
assert_eq!(none_foo, FooOption { foo: None });
}

#[derive(Debug, PartialEq, Deserialize)]
struct FooResult {
#[serde(with = "As::<itf::de::Result::<_, _>>")]
foo: Result<u64, u64>,
}

#[test]
#[allow(clippy::disallowed_names)]
fn test_deserialize_result_ok() {
let ok_itf = json!({
"foo": {
"tag": "Ok",
"value": 42,
}
});

let ok = itf::from_value::<FooResult>(ok_itf).unwrap();
assert_eq!(ok.foo, Ok(42));
}

#[test]
#[allow(clippy::disallowed_names)]
fn test_deserialize_result_err() {
let err_itf = json!({
"foo": {
"tag": "Err",
"value": 42,
}
});

let err = itf::from_value::<FooResult>(err_itf).unwrap();
assert_eq!(err.foo, Err(42));
}
Loading