Write JSON deserializers for Reference, Constructors, Items and Data.
This is needed in order to deserialize a JSON blueprint and use it to perform validation. Still TODO: - [ ] Write JSON deserializer for 'Schema' Which should now be relatively straightforward.
This commit is contained in:
parent
9d2d99ed02
commit
565c0bea74
File diff suppressed because it is too large
Load Diff
|
@ -37,3 +37,6 @@ toml = "0.7.2"
|
||||||
uplc = { path = '../uplc', version = "0.0.29" }
|
uplc = { path = '../uplc', version = "0.0.29" }
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
zip = "0.6.4"
|
zip = "0.6.4"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
proptest = "1.1.0"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use aiken_lang::tipo::{Type, TypeVar};
|
use aiken_lang::tipo::{Type, TypeVar};
|
||||||
use serde::{
|
use serde::{
|
||||||
self,
|
self,
|
||||||
|
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
||||||
ser::{Serialize, SerializeStruct, Serializer},
|
ser::{Serialize, SerializeStruct, Serializer},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -181,3 +182,48 @@ impl Serialize for Reference {
|
||||||
s.end()
|
s.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for Reference {
|
||||||
|
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
enum Field {
|
||||||
|
#[serde(rename = "$ref")]
|
||||||
|
Ref,
|
||||||
|
}
|
||||||
|
const FIELDS: &[&str] = &["$ref"];
|
||||||
|
|
||||||
|
struct ReferenceVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for ReferenceVisitor {
|
||||||
|
type Value = Reference;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("Reference")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut map: V) -> Result<Reference, V::Error>
|
||||||
|
where
|
||||||
|
V: MapAccess<'a>,
|
||||||
|
{
|
||||||
|
let mut inner = None;
|
||||||
|
|
||||||
|
while let Some(key) = map.next_key()? {
|
||||||
|
match key {
|
||||||
|
Field::Ref => {
|
||||||
|
if inner.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field(FIELDS[0]));
|
||||||
|
}
|
||||||
|
inner = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Reference {
|
||||||
|
inner: inner.ok_or_else(|| de::Error::missing_field(FIELDS[0]))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_struct("Reference", FIELDS, ReferenceVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ use aiken_lang::{
|
||||||
use owo_colors::{OwoColorize, Stream::Stdout};
|
use owo_colors::{OwoColorize, Stream::Stdout};
|
||||||
use serde::{
|
use serde::{
|
||||||
self,
|
self,
|
||||||
|
de::{self, Deserialize, Deserializer, MapAccess, Visitor},
|
||||||
ser::{Serialize, SerializeStruct, Serializer},
|
ser::{Serialize, SerializeStruct, Serializer},
|
||||||
};
|
};
|
||||||
use std::ops::Deref;
|
use std::{collections::HashMap, fmt, ops::Deref, sync::Arc};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
|
||||||
|
|
||||||
// NOTE: Can be anything BUT 0
|
// NOTE: Can be anything BUT 0
|
||||||
pub const REDEEMER_DISCRIMINANT: usize = 1;
|
pub const REDEEMER_DISCRIMINANT: usize = 1;
|
||||||
|
@ -44,14 +44,17 @@ pub enum Schema {
|
||||||
pub enum Data {
|
pub enum Data {
|
||||||
Integer,
|
Integer,
|
||||||
Bytes,
|
Bytes,
|
||||||
|
// TODO: Generalize to work with either Reference or Data
|
||||||
List(Items<Reference>),
|
List(Items<Reference>),
|
||||||
|
// TODO: Generalize to work with either Reference or Data
|
||||||
Map(Box<Reference>, Box<Reference>),
|
Map(Box<Reference>, Box<Reference>),
|
||||||
AnyOf(Vec<Annotated<Constructor>>),
|
AnyOf(Vec<Annotated<Constructor>>),
|
||||||
Opaque,
|
Opaque,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A structure that represents either one or many elements.
|
/// A structure that represents either one or many elements.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
pub enum Items<T> {
|
pub enum Items<T> {
|
||||||
One(Box<T>),
|
One(Box<T>),
|
||||||
Many(Vec<T>),
|
Many(Vec<T>),
|
||||||
|
@ -61,6 +64,7 @@ pub enum Items<T> {
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Constructor {
|
pub struct Constructor {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
|
// TODO: Generalize to work with either Reference or Data
|
||||||
pub fields: Vec<Annotated<Reference>>,
|
pub fields: Vec<Annotated<Reference>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,6 +558,170 @@ impl Serialize for Data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for Data {
|
||||||
|
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(field_identifier, rename_all = "camelCase")]
|
||||||
|
enum Field {
|
||||||
|
DataType,
|
||||||
|
Items,
|
||||||
|
Keys,
|
||||||
|
Values,
|
||||||
|
AnyOf,
|
||||||
|
OneOf,
|
||||||
|
}
|
||||||
|
const FIELDS: &[&str] = &["dataType", "items", "keys", "values", "anyOf", "oneOf"];
|
||||||
|
|
||||||
|
struct DataVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for DataVisitor {
|
||||||
|
type Value = Data;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("Data")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut map: V) -> Result<Data, V::Error>
|
||||||
|
where
|
||||||
|
V: MapAccess<'a>,
|
||||||
|
{
|
||||||
|
let mut data_type: Option<String> = None;
|
||||||
|
let mut items: Option<Items<Reference>> = None;
|
||||||
|
let mut keys = None;
|
||||||
|
let mut values = None;
|
||||||
|
let mut any_of = None;
|
||||||
|
|
||||||
|
while let Some(key) = map.next_key()? {
|
||||||
|
match key {
|
||||||
|
Field::DataType => {
|
||||||
|
if data_type.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field(FIELDS[0]));
|
||||||
|
}
|
||||||
|
data_type = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::Items => {
|
||||||
|
if items.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field(FIELDS[1]));
|
||||||
|
}
|
||||||
|
items = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::Keys => {
|
||||||
|
if keys.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field(FIELDS[2]));
|
||||||
|
}
|
||||||
|
keys = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::Values => {
|
||||||
|
if values.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field(FIELDS[3]));
|
||||||
|
}
|
||||||
|
values = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::AnyOf => {
|
||||||
|
if any_of.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field(FIELDS[4]));
|
||||||
|
}
|
||||||
|
any_of = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::OneOf => {
|
||||||
|
if any_of.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field(FIELDS[5]));
|
||||||
|
}
|
||||||
|
any_of = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let expect_no_items = || {
|
||||||
|
if items.is_some() {
|
||||||
|
return Err(de::Error::custom(
|
||||||
|
"unexpected fields 'items' for non-list data-type",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let expect_no_keys = || {
|
||||||
|
if keys.is_some() {
|
||||||
|
return Err(de::Error::custom(
|
||||||
|
"unexpected fields 'keys' for non-map data-type",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let expect_no_values = || {
|
||||||
|
if values.is_some() {
|
||||||
|
return Err(de::Error::custom(
|
||||||
|
"unexpected fields 'values' for non-map data-type",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let expect_no_any_of = || {
|
||||||
|
if any_of.is_some() {
|
||||||
|
return Err(de::Error::custom(
|
||||||
|
"unexpected fields 'anyOf' or 'oneOf'; applicators must singletons",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
match data_type {
|
||||||
|
None => {
|
||||||
|
expect_no_items()?;
|
||||||
|
expect_no_keys()?;
|
||||||
|
expect_no_values()?;
|
||||||
|
match any_of {
|
||||||
|
None => Ok(Data::Opaque),
|
||||||
|
Some(constructors) => Ok(Data::AnyOf(constructors)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(data_type) if data_type == "integer" => {
|
||||||
|
expect_no_items()?;
|
||||||
|
expect_no_keys()?;
|
||||||
|
expect_no_values()?;
|
||||||
|
expect_no_any_of()?;
|
||||||
|
Ok(Data::Integer)
|
||||||
|
}
|
||||||
|
Some(data_type) if data_type == "bytes" => {
|
||||||
|
expect_no_items()?;
|
||||||
|
expect_no_keys()?;
|
||||||
|
expect_no_values()?;
|
||||||
|
expect_no_any_of()?;
|
||||||
|
Ok(Data::Bytes)
|
||||||
|
}
|
||||||
|
Some(data_type) if data_type == "list" => {
|
||||||
|
expect_no_keys()?;
|
||||||
|
expect_no_values()?;
|
||||||
|
expect_no_any_of()?;
|
||||||
|
match items {
|
||||||
|
None => Err(de::Error::missing_field(FIELDS[1])),
|
||||||
|
Some(items) => Ok(Data::List(items)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(data_type) if data_type == "map" => {
|
||||||
|
expect_no_items()?;
|
||||||
|
expect_no_any_of()?;
|
||||||
|
match (keys, values) {
|
||||||
|
(Some(keys), Some(values)) => {
|
||||||
|
Ok(Data::Map(Box::new(keys), Box::new(values)))
|
||||||
|
}
|
||||||
|
(None, _) => Err(de::Error::missing_field(FIELDS[2])),
|
||||||
|
(Some(..), None) => Err(de::Error::missing_field(FIELDS[3])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(..) => Err(de::Error::custom("unknown data-type")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_struct("Data", FIELDS, DataVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serialize for Constructor {
|
impl Serialize for Constructor {
|
||||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
let mut s = serializer.serialize_struct("Constructor", 3)?;
|
let mut s = serializer.serialize_struct("Constructor", 3)?;
|
||||||
|
@ -564,6 +732,60 @@ impl Serialize for Constructor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Deserialize<'a> for Constructor {
|
||||||
|
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(field_identifier, rename_all = "camelCase")]
|
||||||
|
enum Field {
|
||||||
|
Index,
|
||||||
|
Fields,
|
||||||
|
}
|
||||||
|
const FIELDS: &[&str] = &["index", "fields"];
|
||||||
|
|
||||||
|
struct ConstructorVisitor;
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for ConstructorVisitor {
|
||||||
|
type Value = Constructor;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("Constructor")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<V>(self, mut map: V) -> Result<Constructor, V::Error>
|
||||||
|
where
|
||||||
|
V: MapAccess<'a>,
|
||||||
|
{
|
||||||
|
let mut index = None;
|
||||||
|
let mut fields = None;
|
||||||
|
|
||||||
|
while let Some(key) = map.next_key()? {
|
||||||
|
match key {
|
||||||
|
Field::Index => {
|
||||||
|
if index.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field(FIELDS[0]));
|
||||||
|
}
|
||||||
|
index = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
Field::Fields => {
|
||||||
|
if fields.is_some() {
|
||||||
|
return Err(de::Error::duplicate_field(FIELDS[1]));
|
||||||
|
}
|
||||||
|
fields = Some(map.next_value()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Constructor {
|
||||||
|
index: index.ok_or_else(|| de::Error::missing_field(FIELDS[0]))?,
|
||||||
|
fields: fields.ok_or_else(|| de::Error::missing_field(FIELDS[1]))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_struct("Constructor", FIELDS, ConstructorVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, thiserror::Error)]
|
#[derive(Debug, PartialEq, Clone, thiserror::Error)]
|
||||||
#[error("{}", context)]
|
#[error("{}", context)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
|
@ -681,6 +903,7 @@ Here's the types I followed and that led me to this problem:
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test {
|
pub mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use proptest::prelude::*;
|
||||||
use serde_json::{self, json, Value};
|
use serde_json::{self, json, Value};
|
||||||
|
|
||||||
pub fn assert_json(schema: &impl Serialize, expected: Value) {
|
pub fn assert_json(schema: &impl Serialize, expected: Value) {
|
||||||
|
@ -848,4 +1071,166 @@ pub mod test {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_data_opaque() {
|
||||||
|
assert_eq!(Data::Opaque, serde_json::from_value(json!({})).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_data_integer() {
|
||||||
|
assert_eq!(
|
||||||
|
Data::Integer,
|
||||||
|
serde_json::from_value(json!({
|
||||||
|
"dataType": "integer",
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_data_bytes() {
|
||||||
|
assert_eq!(
|
||||||
|
Data::Bytes,
|
||||||
|
serde_json::from_value(json!({
|
||||||
|
"dataType": "bytes",
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_data_list_one() {
|
||||||
|
assert_eq!(
|
||||||
|
Data::List(Items::One(Box::new(Reference::new("foo")))),
|
||||||
|
serde_json::from_value(json!({
|
||||||
|
"dataType": "list",
|
||||||
|
"items": { "$ref": "foo" }
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_data_list_many() {
|
||||||
|
assert_eq!(
|
||||||
|
Data::List(Items::Many(vec![
|
||||||
|
Reference::new("foo"),
|
||||||
|
Reference::new("bar")
|
||||||
|
])),
|
||||||
|
serde_json::from_value(json!({
|
||||||
|
"dataType": "list",
|
||||||
|
"items": [
|
||||||
|
{ "$ref": "foo" },
|
||||||
|
{ "$ref": "bar" }
|
||||||
|
],
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_data_map() {
|
||||||
|
assert_eq!(
|
||||||
|
Data::Map(
|
||||||
|
Box::new(Reference::new("foo")),
|
||||||
|
Box::new(Reference::new("bar"))
|
||||||
|
),
|
||||||
|
serde_json::from_value(json!({
|
||||||
|
"dataType": "map",
|
||||||
|
"keys": { "$ref": "foo" },
|
||||||
|
"values": { "$ref": "bar" }
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_any_of() {
|
||||||
|
assert_eq!(
|
||||||
|
Data::AnyOf(vec![Constructor {
|
||||||
|
index: 0,
|
||||||
|
fields: vec![Reference::new("foo").into(), Reference::new("bar").into()],
|
||||||
|
}
|
||||||
|
.into()]),
|
||||||
|
serde_json::from_value(json!({
|
||||||
|
"anyOf": [{
|
||||||
|
"index": 0,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"$ref": "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "bar",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_one_of() {
|
||||||
|
assert_eq!(
|
||||||
|
Data::AnyOf(vec![Constructor {
|
||||||
|
index: 0,
|
||||||
|
fields: vec![Reference::new("foo").into(), Reference::new("bar").into()],
|
||||||
|
}
|
||||||
|
.into()]),
|
||||||
|
serde_json::from_value(json!({
|
||||||
|
"oneOf": [{
|
||||||
|
"index": 0,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"$ref": "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "bar",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}))
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arbitrary_data() -> impl Strategy<Value = Data> {
|
||||||
|
let r = prop_oneof![".*".prop_map(|s| Reference::new(&s))];
|
||||||
|
let constructor =
|
||||||
|
(0..3usize, prop::collection::vec(r.clone(), 0..3)).prop_map(|(index, fields)| {
|
||||||
|
Constructor {
|
||||||
|
index,
|
||||||
|
fields: fields.into_iter().map(|f| f.into()).collect(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
});
|
||||||
|
prop_oneof![
|
||||||
|
Just(Data::Opaque),
|
||||||
|
Just(Data::Bytes),
|
||||||
|
Just(Data::Integer),
|
||||||
|
(r.clone(), r.clone()).prop_map(|(k, v)| Data::Map(Box::new(k), Box::new(v))),
|
||||||
|
r.clone().prop_map(|x| Data::List(Items::One(Box::new(x)))),
|
||||||
|
prop::collection::vec(r, 1..3).prop_map(|xs| Data::List(Items::Many(xs))),
|
||||||
|
prop::collection::vec(constructor, 1..3).prop_map(Data::AnyOf)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn data_serialization_roundtrip(data in arbitrary_data()) {
|
||||||
|
let json = serde_json::to_value(data);
|
||||||
|
let pretty = json
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| serde_json::to_string_pretty(v).unwrap())
|
||||||
|
.unwrap_or_else(|_| "invalid".to_string());
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
json.and_then(serde_json::from_value::<Data>),
|
||||||
|
Ok{..}
|
||||||
|
),
|
||||||
|
"\ncounterexample: {pretty}\n",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue