Skip to content

Commit

Permalink
Add helpers to deserialize into Option or Result (#17)
Browse files Browse the repository at this point in the history
cc @bugarela

## `Option`

```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 });
```

## `Result`

```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));
```
  • Loading branch information
romac authored May 16, 2024
2 parents 40cbf6a + 72f6a3d commit e9c5fce
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 4 deletions.
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));
}

0 comments on commit e9c5fce

Please sign in to comment.