#![allow(clippy::or_fun_call)]
use std::collections::BTreeMap;
use std::ops::Not;
use itertools::Itertools;
use serde::Deserialize;
use serde_json::json;
use serde_json::Value;
use daml_lf::element::{
DamlArchive, DamlData, DamlEnum, DamlField, DamlTyCon, DamlTyConName, DamlType, DamlTypeVarWithKind, DamlVar,
DamlVariant,
};
use crate::error::DamlJsonSchemaCodecError::NotSerializableDamlType;
use crate::error::{DamlJsonSchemaCodecError, DamlJsonSchemaCodecResult};
use crate::schema_data::{
DamlJsonSchemaBool, DamlJsonSchemaContractId, DamlJsonSchemaDate, DamlJsonSchemaDecimal, DamlJsonSchemaEnum,
DamlJsonSchemaEnumEntry, DamlJsonSchemaGenMap, DamlJsonSchemaGenMapItems, DamlJsonSchemaInt64, DamlJsonSchemaList,
DamlJsonSchemaOptional, DamlJsonSchemaOptionalNonTopLevel, DamlJsonSchemaOptionalNonTopLevelOneOf,
DamlJsonSchemaOptionalTopLevel, DamlJsonSchemaParty, DamlJsonSchemaRecord, DamlJsonSchemaRecordAsArray,
DamlJsonSchemaRecordAsObject, DamlJsonSchemaText, DamlJsonSchemaTextMap, DamlJsonSchemaTimestamp,
DamlJsonSchemaUnit, DamlJsonSchemaVariant, DamlJsonSchemaVariantArm,
};
use crate::util::AsSingleSliceExt;
use crate::util::Required;
#[derive(Debug, Deserialize, Default)]
pub struct DataDict(BTreeMap<String, DataDictEntry>);
#[derive(Debug, Deserialize, Default)]
pub struct DataDictEntry {
title: Option<String>,
description: Option<String>,
#[serde(default)]
items: BTreeMap<String, String>,
}
const SCHEMA_VERSION: &str = "https://json-schema.org/draft/2020-12/schema";
#[derive(Debug, Copy, Clone)]
pub enum RenderSchema {
None,
Data,
All,
}
impl Default for RenderSchema {
fn default() -> Self {
Self::Data
}
}
#[derive(Debug, Copy, Clone)]
pub enum RenderTitle {
None,
Data,
}
impl Default for RenderTitle {
fn default() -> Self {
Self::Data
}
}
#[derive(Debug, Copy, Clone)]
pub enum RenderDescription {
None,
Data,
All,
}
impl Default for RenderDescription {
fn default() -> Self {
Self::All
}
}
#[derive(Debug, Clone)]
pub enum ReferenceMode {
Inline,
Reference {
prefix: String,
},
}
impl Default for ReferenceMode {
fn default() -> Self {
Self::Inline
}
}
#[derive(Debug, Default)]
pub struct SchemaEncoderConfig {
render_schema: RenderSchema,
render_title: RenderTitle,
render_description: RenderDescription,
reference_mode: ReferenceMode,
data_dict: DataDict,
}
impl SchemaEncoderConfig {
pub fn new(
render_schema: RenderSchema,
render_title: RenderTitle,
render_description: RenderDescription,
reference_mode: ReferenceMode,
data_dict: DataDict,
) -> Self {
Self {
render_schema,
render_title,
render_description,
reference_mode,
data_dict,
}
}
}
#[derive(Debug)]
pub struct JsonSchemaEncoder<'a> {
arc: &'a DamlArchive<'a>,
config: SchemaEncoderConfig,
}
impl<'a> JsonSchemaEncoder<'a> {
pub fn new(arc: &'a DamlArchive<'a>) -> Self {
Self {
arc,
config: SchemaEncoderConfig::default(),
}
}
pub fn new_with_config(arc: &'a DamlArchive<'a>, config: SchemaEncoderConfig) -> Self {
Self {
arc,
config,
}
}
pub fn encode_type(&self, ty: &DamlType<'_>) -> DamlJsonSchemaCodecResult<Value> {
self.do_encode_type(ty, true, &[], &[])
}
pub fn encode_data(&self, data: &DamlData<'_>) -> DamlJsonSchemaCodecResult<Value> {
(data.serializable() && data.type_params().is_empty())
.then(|| self.do_encode_data(data, &[]))
.unwrap_or_else(|| Err(NotSerializableDamlType(data.name().to_owned())))
}
fn encode_unit(&self) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaUnit {
schema: self.schema_if_all(),
description: self.description_if_all("Unit"),
ty: "object",
additional_properties: false,
})?)
}
fn encode_bool(&self) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaBool {
schema: self.schema_if_all(),
description: self.description_if_all("Bool"),
ty: "boolean",
})?)
}
fn encode_text(&self) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaText {
schema: self.schema_if_all(),
description: self.description_if_all("Text"),
ty: "string",
})?)
}
fn encode_party(&self) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaParty {
schema: self.schema_if_all(),
description: self.description_if_all("Party"),
ty: "string",
})?)
}
fn encode_contract_id(&self, template_path: &Option<String>) -> DamlJsonSchemaCodecResult<Value> {
let description = match template_path.as_deref() {
Some(tid) => self.description_if_all(&format!("ContractId ({})", tid)).map(ToString::to_string),
None => self.description_if_all("ContractId").map(ToString::to_string),
};
Ok(serde_json::to_value(DamlJsonSchemaContractId {
schema: self.schema_if_all(),
description,
ty: "string",
})?)
}
fn encode_date(&self) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaDate {
schema: self.schema_if_all(),
description: self.description_if_all("Date"),
ty: "string",
})?)
}
fn encode_timestamp(&self) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaTimestamp {
schema: self.schema_if_all(),
description: self.description_if_all("Timestamp"),
ty: "string",
})?)
}
fn encode_int64(&self) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaInt64 {
schema: self.schema_if_all(),
description: self.description_if_all("Int64"),
ty: json!(["integer", "string"]),
})?)
}
fn encode_decimal(&self) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaDecimal {
schema: self.schema_if_all(),
description: self.description_if_all("Decimal"),
ty: json!(["number", "string"]),
})?)
}
fn encode_list(&self, items: Value) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaList {
schema: self.schema_if_all(),
description: self.description_if_all("List"),
ty: "array",
items,
})?)
}
fn encode_textmap(&self, additional_properties: Value) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaTextMap {
schema: self.schema_if_all(),
description: self.description_if_all("TextMap"),
ty: "object",
additional_properties,
})?)
}
fn encode_genmap(&self, ty_key: Value, ty_value: Value) -> DamlJsonSchemaCodecResult<Value> {
Ok(serde_json::to_value(DamlJsonSchemaGenMap {
schema: self.schema_if_all(),
description: self.description_if_all("GenMap"),
ty: "array",
items: DamlJsonSchemaGenMapItems {
ty: "array",
items: [ty_key, ty_value],
min_items: 2,
max_items: 2,
},
})?)
}
fn encode_optional(&self, nested: Value, top_level: bool) -> DamlJsonSchemaCodecResult<Value> {
if top_level {
Ok(serde_json::to_value(DamlJsonSchemaOptional::TopLevel(DamlJsonSchemaOptionalTopLevel {
schema: self.schema_if_all(),
description: self.description_if_all("Optional"),
one_of: [json!({ "type": "null" }), nested],
}))?)
} else {
Ok(serde_json::to_value(DamlJsonSchemaOptional::NonTopLevel(DamlJsonSchemaOptionalNonTopLevel {
schema: self.schema_if_all(),
description: self.description_if_all("Optional (depth > 1)"),
one_of: [
DamlJsonSchemaOptionalNonTopLevelOneOf {
ty: "array",
items: None,
min_items: 0,
max_items: 0,
},
DamlJsonSchemaOptionalNonTopLevelOneOf {
ty: "array",
items: Some(nested),
min_items: 1,
max_items: 1,
},
],
}))?)
}
}
fn do_encode_record(
&self,
name: &str,
module_path: impl Iterator<Item = &'a str>,
fields: &[DamlField<'_>],
type_params: &[DamlTypeVarWithKind<'a>],
type_args: &[DamlType<'_>],
) -> DamlJsonSchemaCodecResult<Value> {
let data_item_path = Self::format_data_item(module_path, name);
let (title, description) = self.get_title_and_description(&data_item_path);
Ok(serde_json::to_value(DamlJsonSchemaRecord {
schema: self.schema_if_data_or_all(),
title: title.or_else(|| self.title_if_data(&data_item_path)),
description: description.or(self.description_if_data_or_all(&format!("Record ({})", name))),
one_of: [
self.do_encode_record_object(name, &data_item_path, fields, type_params, type_args)?,
self.do_encode_record_list(name, fields, type_params, type_args)?,
],
})?)
}
fn encode_variant(
&self,
variant: &DamlVariant<'_>,
module_path: impl Iterator<Item = &'a str>,
type_params: &[DamlTypeVarWithKind<'a>],
type_args: &[DamlType<'_>],
) -> DamlJsonSchemaCodecResult<Value> {
let data_item_path = Self::format_data_item(module_path, variant.name());
let (title, description) = self.get_title_and_description(&data_item_path);
let all_arms = variant
.fields()
.iter()
.map(|field| self.encode_variant_arm(variant.name(), &data_item_path, field, type_params, type_args))
.collect::<DamlJsonSchemaCodecResult<Vec<_>>>()?;
Ok(serde_json::to_value(DamlJsonSchemaVariant {
schema: self.schema_if_data_or_all(),
title: title.or_else(|| self.title_if_data(&data_item_path)),
description: description.or(self.description_if_data_or_all(&format!("Variant ({})", variant.name()))),
one_of: all_arms,
})?)
}
fn encode_enum(
&self,
data_enum: &DamlEnum<'_>,
module_path: impl Iterator<Item = &'a str>,
) -> DamlJsonSchemaCodecResult<Value> {
let data_item_path = Self::format_data_item(module_path, data_enum.name());
let (title, description) = self.get_title_and_description(&data_item_path);
let all_entries = data_enum
.constructors()
.map(|field| self.encode_enum_entry(data_enum.name(), &data_item_path, field))
.collect::<DamlJsonSchemaCodecResult<Vec<_>>>()?;
Ok(serde_json::to_value(DamlJsonSchemaEnum {
schema: self.schema_if_data_or_all(),
title: title.or_else(|| self.title_if_data(&data_item_path)),
description: description.or(self.description_if_data_or_all(&format!("Enum ({})", data_enum.name()))),
one_of: all_entries,
})?)
}
fn do_encode_type(
&self,
ty: &'a DamlType<'a>,
top_level: bool,
type_params: &[DamlTypeVarWithKind<'_>],
type_args: &[DamlType<'_>],
) -> DamlJsonSchemaCodecResult<Value> {
match ty {
DamlType::Unit => self.encode_unit(),
DamlType::Bool => self.encode_bool(),
DamlType::Text => self.encode_text(),
DamlType::ContractId(cid) => self.encode_contract_id(&cid.as_ref().map(|ty| Self::tycon_path(ty))),
DamlType::Party => self.encode_party(),
DamlType::Timestamp => self.encode_timestamp(),
DamlType::Date => self.encode_date(),
DamlType::Int64 => self.encode_int64(),
DamlType::Numeric(_) => self.encode_decimal(),
DamlType::List(tys) =>
self.encode_list(self.do_encode_type(tys.as_single()?, true, type_params, type_args)?),
DamlType::TextMap(tys) =>
self.encode_textmap(self.do_encode_type(tys.as_single()?, true, type_params, type_args)?),
DamlType::GenMap(tys) => self.encode_genmap(
self.do_encode_type(tys.first().req()?, true, type_params, type_args)?,
self.do_encode_type(tys.last().req()?, true, type_params, type_args)?,
),
DamlType::Optional(nested) => self
.encode_optional(self.do_encode_type(nested.as_single()?, false, type_params, type_args)?, top_level),
DamlType::TyCon(tycon) => self.encode_tycon(tycon),
DamlType::BoxedTyCon(tycon) => self.encode_boxed_tycon(tycon),
DamlType::Var(v) => self.do_encode_type(
Self::resolve_type_var(type_params, type_args, v)?,
top_level,
type_params,
type_args,
),
DamlType::Nat(_)
| DamlType::Arrow
| DamlType::Any
| DamlType::TypeRep
| DamlType::Update
| DamlType::Scenario
| DamlType::Forall(_)
| DamlType::Struct(_)
| DamlType::Syn(_)
| DamlType::Bignumeric
| DamlType::RoundingMode
| DamlType::AnyException => Err(DamlJsonSchemaCodecError::UnsupportedDamlType(ty.name().to_owned())),
}
}
fn encode_tycon(&self, tycon: &DamlTyCon<'_>) -> DamlJsonSchemaCodecResult<Value> {
match &self.config.reference_mode {
ReferenceMode::Inline => {
let data = self.resolve_tycon(tycon)?;
self.do_encode_data(data, tycon.type_arguments())
},
ReferenceMode::Reference {
prefix,
} => {
let data = self.resolve_tycon(tycon)?;
if data.type_params().is_empty() {
Ok(Self::encode_reference(prefix, tycon.tycon()))
} else {
self.do_encode_data(data, tycon.type_arguments())
}
},
}
}
fn encode_boxed_tycon(&self, tycon: &DamlTyCon<'_>) -> DamlJsonSchemaCodecResult<Value> {
match &self.config.reference_mode {
ReferenceMode::Inline => {
Ok(Self::encode_inline_recursive(&tycon.tycon().to_string()))
},
ReferenceMode::Reference {
prefix,
} => {
let data = self.resolve_tycon(tycon)?;
if data.type_params().is_empty() {
Ok(Self::encode_reference(prefix, tycon.tycon()))
} else {
Ok(Self::encode_reference_recursive_with_type_params(&tycon.tycon().to_string()))
}
},
}
}
fn do_encode_data(&self, data: &DamlData<'_>, type_args: &[DamlType<'_>]) -> DamlJsonSchemaCodecResult<Value> {
data.serializable()
.then(|| match data {
DamlData::Template(template) =>
self.do_encode_record(template.name(), template.module_path(), template.fields(), &[], type_args),
DamlData::Record(record) => self.do_encode_record(
record.name(),
record.module_path(),
record.fields(),
record.type_params(),
type_args,
),
DamlData::Variant(variant) =>
self.encode_variant(variant, variant.module_path(), variant.type_params(), type_args),
DamlData::Enum(data_enum) => self.encode_enum(data_enum, data_enum.module_path()),
})
.unwrap_or_else(|| Err(NotSerializableDamlType(data.name().to_owned())))
}
fn do_encode_record_object(
&self,
name: &str,
data_item_path: &str,
fields: &[DamlField<'_>],
type_params: &[DamlTypeVarWithKind<'a>],
type_args: &[DamlType<'_>],
) -> DamlJsonSchemaCodecResult<Value> {
let fields_map = fields
.iter()
.map(|field| {
self.do_encode_type(field.ty(), true, type_params, type_args)
.map(|json_val| self.update_desc_from_data_dict(json_val, data_item_path, field.name()))
})
.collect::<DamlJsonSchemaCodecResult<BTreeMap<&str, Value>>>()?;
let required = fields
.iter()
.filter_map(|field| match Self::is_optional_field(field, type_args, type_params) {
Ok(is_opt) if !is_opt => Some(Ok(field.name())),
Ok(_) => None,
Err(e) => Some(Err(e)),
})
.collect::<DamlJsonSchemaCodecResult<Vec<_>>>()?;
Ok(serde_json::to_value(DamlJsonSchemaRecordAsObject {
ty: "object",
description: self.description_if_all(&format!("Record ({})", name)),
properties: fields_map.is_empty().not().then(|| fields_map),
additional_properties: false,
required,
})?)
}
fn do_encode_record_list(
&self,
name: &str,
fields: &[DamlField<'_>],
type_params: &[DamlTypeVarWithKind<'a>],
type_args: &[DamlType<'_>],
) -> DamlJsonSchemaCodecResult<Value> {
let fields_list = fields
.iter()
.map(|field| self.do_encode_type(field.ty(), true, type_params, type_args))
.collect::<DamlJsonSchemaCodecResult<Vec<Value>>>()?;
let field_names = fields.iter().map(DamlField::name).join(", ");
let item_count = fields_list.len();
Ok(serde_json::to_value(DamlJsonSchemaRecordAsArray {
ty: "array",
description: self.description_if_all(&format!("Record ({}, fields = [{}])", name, field_names)),
items: (item_count > 0).then(|| fields_list),
min_items: item_count,
max_items: item_count,
})?)
}
fn encode_variant_arm(
&self,
name: &str,
data_item_path: &str,
daml_field: &DamlField<'_>,
type_params: &[DamlTypeVarWithKind<'a>],
type_args: &[DamlType<'_>],
) -> DamlJsonSchemaCodecResult<Value> {
let description = if let Some(DataDictEntry {
items: fields,
..
}) = self.config.data_dict.0.get(data_item_path)
{
fields.get(daml_field.name()).map(AsRef::as_ref)
} else {
None
};
Ok(serde_json::to_value(DamlJsonSchemaVariantArm {
ty: "object",
title: Some(daml_field.name()),
description: description.or(self.description_if_all(&format!(
"Variant ({}, tag={})",
name,
daml_field.name()
))),
properties: json!(
{
"tag": { "type": "string", "enum": [daml_field.name()] },
"value": self.do_encode_type(daml_field.ty(), true, type_params, type_args)?
}
),
required: vec!["tag", "value"],
additional_properties: false,
})?)
}
fn encode_enum_entry(&self, name: &str, data_item_path: &str, entry: &str) -> DamlJsonSchemaCodecResult<Value> {
let description = if let Some(DataDictEntry {
items: fields,
..
}) = self.config.data_dict.0.get(data_item_path)
{
fields.get(entry).map(AsRef::as_ref)
} else {
None
};
Ok(serde_json::to_value(DamlJsonSchemaEnumEntry {
ty: "string",
title: Some(entry),
description: description.or(self.description_if_all(&format!("Enum ({}, tag={})", name, entry))),
data_enum: vec![entry],
})?)
}
fn encode_reference(prefix: &str, tycon: &DamlTyConName<'_>) -> Value {
json!({ "$ref": format!("{}{}.{}", prefix, tycon.module_path().join("."), tycon.data_name()) })
}
fn encode_inline_recursive(name: &str) -> Value {
json!(
{
"description": format!("Any ({})", name),
"comment": "inline recursive data types cannot be represented"
}
)
}
fn encode_reference_recursive_with_type_params(name: &str) -> Value {
json!(
{
"description": format!("Any ({})", name),
"comment": "recursive data types with type parameters cannot be represented"
}
)
}
fn resolve_tycon(&self, tycon: &DamlTyCon<'_>) -> DamlJsonSchemaCodecResult<&DamlData<'_>> {
self.arc.data_by_tycon(tycon).ok_or_else(|| DamlJsonSchemaCodecError::DataNotFound(tycon.tycon().to_string()))
}
fn resolve_type_var<'arg>(
type_params: &[DamlTypeVarWithKind<'_>],
type_args: &'arg [DamlType<'arg>],
var: &DamlVar<'_>,
) -> DamlJsonSchemaCodecResult<&'arg DamlType<'arg>> {
let index = type_params
.iter()
.position(|h| h.var() == var.var())
.ok_or_else(|| DamlJsonSchemaCodecError::TypeVarNotFoundInArgs(var.var().to_string()))?;
type_args.get(index).ok_or_else(|| DamlJsonSchemaCodecError::TypeVarNotFoundInParams(var.var().to_string()))
}
fn is_optional_field(
field: &DamlField<'_>,
type_args: &[DamlType<'_>],
type_params: &[DamlTypeVarWithKind<'a>],
) -> DamlJsonSchemaCodecResult<bool> {
match field.ty() {
DamlType::Optional(_) => Ok(true),
DamlType::Var(var) =>
Ok(matches!(Self::resolve_type_var(type_params, type_args, var)?, DamlType::Optional(_))),
_ => Ok(false),
}
}
fn update_desc_from_data_dict<'f>(
&self,
json_val: Value,
data_item_path: &str,
field_name: &'f str,
) -> (&'f str, Value) {
let mut json_val = json_val;
if let Some(DataDictEntry {
items: fields,
..
}) = self.config.data_dict.0.get(data_item_path)
{
if let Some(desc) = fields.get(field_name) {
json_val.as_object_mut().unwrap().insert(String::from("description"), json!(desc));
}
}
(field_name, json_val)
}
fn get_title_and_description(&self, key: &str) -> (Option<&str>, Option<&str>) {
match self.config.data_dict.0.get(key).as_ref() {
Some(DataDictEntry {
title: Some(title),
description: Some(description),
..
}) => (Some(title.as_str()), Some(description.as_str())),
Some(DataDictEntry {
title: Some(title),
..
}) => (Some(title.as_str()), None),
Some(DataDictEntry {
description: Some(description),
..
}) => (None, Some(description.as_str())),
_ => (None, None),
}
}
fn format_data_item(module_path: impl Iterator<Item = &'a str>, data: &str) -> String {
let mut it = module_path;
format!("{}:{}", it.join("."), data)
}
fn tycon_path(cid: &'a DamlType<'a>) -> String {
match cid {
DamlType::TyCon(tycon) | DamlType::BoxedTyCon(tycon) =>
Self::format_data_item(tycon.tycon().module_path(), tycon.tycon().data_name()),
_ => "".to_string(),
}
}
fn schema_if_all(&self) -> Option<&'static str> {
matches!(self.config.render_schema, RenderSchema::All).then(|| SCHEMA_VERSION)
}
fn schema_if_data_or_all(&self) -> Option<&'static str> {
matches!(self.config.render_schema, RenderSchema::Data | RenderSchema::All).then(|| SCHEMA_VERSION)
}
fn title_if_data<'t>(&self, title: &'t str) -> Option<&'t str> {
matches!(self.config.render_title, RenderTitle::Data).then(|| title)
}
fn description_if_all<'t>(&self, description: &'t str) -> Option<&'t str> {
matches!(self.config.render_description, RenderDescription::All).then(|| description)
}
fn description_if_data_or_all<'t>(&self, description: &'t str) -> Option<&'t str> {
matches!(self.config.render_description, RenderDescription::Data | RenderDescription::All).then(|| description)
}
}
#[cfg(test)]
mod tests {
use anyhow::{anyhow, Result};
use assert_json_diff::assert_json_eq;
use jsonschema::JSONSchema;
use super::*;
static TESTING_TYPES_DAR_PATH: &str = "../resources/testing_types_sandbox/TestingTypes-latest.dar";
#[macro_export]
macro_rules! get_expected {
($name : literal) => {
serde_json::from_str::<Value>(include_str!(concat!("../test_resources/json_schema/", $name)))
};
}
#[macro_export]
macro_rules! get_datadict {
($name : literal) => {
serde_yaml::from_str::<DataDict>(include_str!(concat!("../test_resources/json_schema/", $name)))
};
}
#[test]
fn test_unit() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Unit;
let expected = get_expected!("test_unit.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_text() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Text;
let expected = get_expected!("test_text.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_party() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Party;
let expected = get_expected!("test_party.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_int64() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Int64;
let expected = get_expected!("test_int64.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_numeric() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Numeric(vec![DamlType::Nat(18)]);
let expected = get_expected!("test_numeric.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_bool() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Bool;
let expected = get_expected!("test_bool.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_contract_id() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::ContractId(None);
let expected = get_expected!("test_contract_id.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_contract_id_for_template() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ping_ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "PingPong"], "Ping");
let ty = DamlType::ContractId(Some(Box::new(ping_ty)));
let expected = get_expected!("test_contract_id_for_template.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_timestamp() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Timestamp;
let expected = get_expected!("test_timestamp.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_date() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Date;
let expected = get_expected!("test_date.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_optional_int64() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Optional(vec![DamlType::Int64]);
let expected = get_expected!("test_optional_int64.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_optional_optional_int64() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Optional(vec![DamlType::Optional(vec![DamlType::Int64])]);
let expected = get_expected!("test_optional_optional_int64.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_optional_optional_optional_int64() -> DamlJsonSchemaCodecResult<()> {
let ty = DamlType::Optional(vec![DamlType::Optional(vec![DamlType::Optional(vec![DamlType::Int64])])]);
let expected = get_expected!("test_optional_optional_optional_int64.json")?;
let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn test_list_of_text() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "RecordArgument");
let expected = get_expected!("test_list_of_text.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_text_map_of_int64() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "MapTest"], "Bar");
let expected = get_expected!("test_text_map_of_int64.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_gen_map_of_int_text() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "MapTest"], "Foo");
let expected = get_expected!("test_gen_map_of_int_text.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_record() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Person");
let expected = get_expected!("test_record.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_large_field_count() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "LargeExpr"], "Call");
let expected = get_expected!("test_large_field_count.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_empty_record() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "PingPong"], "ResetPingCount");
let expected = get_expected!("test_empty_record.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_template() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "PingPong"], "Ping");
let expected = get_expected!("test_template.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_enum() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Vehicle"], "SimpleColor");
let expected = get_expected!("test_enum.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_variant() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Shape"], "Color");
let expected = get_expected!("test_variant.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_optional_depth1() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Depth1");
let expected = get_expected!("test_optional_depth1.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_optional_depth2() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Depth2");
let expected = get_expected!("test_optional_depth2.json")?;
let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_reference_mode_case_1() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "PersonMap");
let expected = get_expected!("test_reference_mode_case_1.json")?;
let config = get_schema_config_inline();
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_reference_mode_case_2() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "OPerson");
let expected = get_expected!("test_reference_mode_case_2.json")?;
let config = get_schema_config_inline();
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_reference_mode_case_3() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Rec");
let expected = get_expected!("test_reference_mode_case_3.json")?;
let config = get_schema_config_inline();
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_reference_mode_case_4() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "GenericTypes"], "PatternRecord");
let expected = get_expected!("test_reference_mode_case_4.json")?;
let config = get_schema_config_inline();
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_reference_mode_case_5() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let data = arc.data(arc.main_package_id(), &["Fuji", "JsonTest"], "PersonMap").req()?;
let expected = get_expected!("test_reference_mode_case_5.json")?;
let config = get_schema_config_reference();
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_data(data)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_reference_mode_case_6() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let data = arc.data(arc.main_package_id(), &["Fuji", "JsonTest"], "Middle").req()?;
let expected = get_expected!("test_reference_mode_case_6.json")?;
let config = get_schema_config_reference();
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_data(data)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_reference_mode_case_7() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let data = arc.data(arc.main_package_id(), &["Fuji", "JsonTest"], "Rec").req()?;
let expected = get_expected!("test_reference_mode_case_7.json")?;
let config = get_schema_config_reference();
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_data(data)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_reference_mode_case_8() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let data = arc.data(arc.main_package_id(), &["Fuji", "JsonTest"], "TopRec").req()?;
let expected = get_expected!("test_reference_mode_case_8.json")?;
let config = get_schema_config_reference();
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_data(data)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_record_datadict() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Person");
let expected = get_expected!("test_record_datadict.json")?;
let datadict = get_datadict!("datadict.yaml").unwrap();
let config = get_schema_config(ReferenceMode::Inline, datadict);
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_template_datadict() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "PingPong"], "Ping");
let expected = get_expected!("test_template_datadict.json")?;
let datadict = get_datadict!("datadict.yaml").unwrap();
let config = get_schema_config(ReferenceMode::Inline, datadict);
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_enum_datadict() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Vehicle"], "SimpleColor");
let expected = get_expected!("test_enum_datadict.json")?;
let datadict = get_datadict!("datadict.yaml").unwrap();
let config = get_schema_config(ReferenceMode::Inline, datadict);
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_variant_datadict() -> DamlJsonSchemaCodecResult<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Shape"], "Color");
let expected = get_expected!("test_variant_datadict.json")?;
let datadict = get_datadict!("datadict.yaml").unwrap();
let config = get_schema_config(ReferenceMode::Inline, datadict);
let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
assert_json_eq!(actual, expected);
Ok(())
}
#[test]
fn test_fail_for_non_serializable_record() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "HigherKindTest"], "HigherKindedData");
match JsonSchemaEncoder::new(arc).encode_type(&ty) {
Err(DamlJsonSchemaCodecError::NotSerializableDamlType(s)) if s == "HigherKindedData" => Ok(()),
Err(e) => panic!("expected different error: {}", e),
_ => panic!("expected error"),
}
}
#[test]
fn test_fail_for_generic_missing_type_arg() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa");
match JsonSchemaEncoder::new(arc).encode_type(&ty) {
Err(DamlJsonSchemaCodecError::TypeVarNotFoundInParams(s)) if s == "a" => Ok(()),
Err(e) => panic!("expected different error: {}", e),
_ => panic!("expected error"),
}
}
#[test]
fn test_validate_unit() -> Result<()> {
validate_schema_match(&DamlType::Unit, &json!({}))
}
#[test]
fn test_validate_unit_unexpected_property() -> Result<()> {
validate_schema_no_match(&DamlType::Unit, &json!({ "unexpected_key": "unexpected_value" }))
}
#[test]
fn test_validate_int64_as_integer() -> Result<()> {
validate_schema_match(&DamlType::Int64, &json!(42))
}
#[test]
fn test_validate_int64_as_string() -> Result<()> {
validate_schema_match(&DamlType::Int64, &json!("42"))
}
#[test]
fn test_validate_int64_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::Int64, &json!(3.4111))
}
#[test]
fn test_validate_text() -> Result<()> {
validate_schema_match(&DamlType::Text, &json!("test"))
}
#[test]
fn test_validate_text_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::Text, &json!(42))
}
#[test]
fn test_validate_party() -> Result<()> {
validate_schema_match(&DamlType::Party, &json!("Alice"))
}
#[test]
fn test_validate_party_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::Party, &json!(1.234))
}
#[test]
fn test_validate_contract_id() -> Result<()> {
validate_schema_match(&DamlType::ContractId(None), &json!("#1:0"))
}
#[test]
fn test_validate_contract_id_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::ContractId(None), &json!({}))
}
#[test]
fn test_validate_bool_true() -> Result<()> {
validate_schema_match(&DamlType::Bool, &json!(true))
}
#[test]
fn test_validate_bool_false() -> Result<()> {
validate_schema_match(&DamlType::Bool, &json!(false))
}
#[test]
fn test_validate_bool_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::Bool, &json!(0))
}
#[test]
fn test_validate_numeric_with_decimal() -> Result<()> {
validate_schema_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!(9.99))
}
#[test]
fn test_validate_numeric_with_integer() -> Result<()> {
validate_schema_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!(42))
}
#[test]
fn test_validate_numeric_with_decimal_string() -> Result<()> {
validate_schema_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!("3.14"))
}
#[test]
fn test_validate_numeric_with_integer_string() -> Result<()> {
validate_schema_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!("42"))
}
#[test]
fn test_validate_numeric_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!([1, 2, 3]))
}
#[test]
fn test_validate_date() -> Result<()> {
validate_schema_match(&DamlType::Date, &json!("2021-05-14"))
}
#[test]
fn test_validate_bad_date() -> Result<()> {
validate_schema_match(&DamlType::Date, &json!("the schema only validates that this is a string"))
}
#[test]
fn test_validate_date_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::Date, &json!(1234))
}
#[test]
fn test_validate_timestamp() -> Result<()> {
validate_schema_match(&DamlType::Timestamp, &json!("1990-11-09T04:30:23.1234569Z"))
}
#[test]
fn test_validate_bad_timestamp() -> Result<()> {
validate_schema_match(&DamlType::Timestamp, &json!("the schema only validates that this is a string"))
}
#[test]
fn test_validate_timestamp_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::Timestamp, &json!({"foo": 42}))
}
#[test]
fn test_validate_list_of_int() -> Result<()> {
validate_schema_match(&DamlType::List(vec![DamlType::Int64]), &json!([1, 2, 3, 42]))
}
#[test]
fn test_validate_list_of_text() -> Result<()> {
validate_schema_match(&DamlType::List(vec![DamlType::Text]), &json!(["this", "is", "a", "test"]))
}
#[test]
fn test_validate_list_invalid_mixed_types() -> Result<()> {
validate_schema_no_match(&DamlType::List(vec![DamlType::Text]), &json!(["foo", 42, "bar"]))
}
#[test]
fn test_validate_textmap_of_int64() -> Result<()> {
validate_schema_match(&DamlType::TextMap(vec![DamlType::Int64]), &json!({"key1": 1, "key2": 2}))
}
#[test]
fn test_validate_textmap_of_int64_empty() -> Result<()> {
validate_schema_match(&DamlType::TextMap(vec![DamlType::Int64]), &json!({}))
}
#[test]
fn test_validate_textmap_of_int64_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::TextMap(vec![DamlType::Int64]), &json!({"key1": {}}))
}
#[test]
fn test_validate_textmap_of_int64_duplicate_key() -> Result<()> {
validate_schema_match(&DamlType::TextMap(vec![DamlType::Int64]), &json!({"key1": 1, "key1": 2}))
}
#[test]
fn test_validate_genmap_of_int64_to_text() -> Result<()> {
validate_schema_match(
&DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]),
&json!([[101, "foo"], [102, "bar"]]),
)
}
#[test]
fn test_validate_genmap_of_person_to_text() -> Result<()> {
let arc = daml_archive();
let person_ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Person");
let ty = DamlType::GenMap(vec![person_ty, DamlType::Text]);
let instance = json!(
[[{"name": "Alice", "age": 10}, "Alice is 10"], [{"name": "Bob", "age": 6}, "Bob is 6"]]
);
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_genmap_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]), &json!([[101, "foo", 102]]))
}
#[test]
fn test_validate_generic_opt_int_some() -> Result<()> {
let arc = daml_archive();
let ty =
DamlType::make_tycon_with_args(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa", vec![DamlType::Int64]);
let instance = json!({ "foo": 42 });
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_generic_opt_int_none() -> Result<()> {
let arc = daml_archive();
let ty =
DamlType::make_tycon_with_args(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa", vec![DamlType::Int64]);
let instance = json!({});
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_generic_opt_opt_int_some() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon_with_args(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa", vec![
DamlType::Optional(vec![DamlType::Int64]),
]);
let instance = json!({ "foo": [42] });
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_generic_opt_opt_int_none() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon_with_args(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa", vec![
DamlType::Optional(vec![DamlType::Int64]),
]);
let instance = json!({ "foo": [] });
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_genmap_of_int64_to_text_empty() -> Result<()> {
validate_schema_match(&DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]), &json!([]))
}
#[test]
fn test_validate_genmap_of_int64_to_text_broken() -> Result<()> {
validate_schema_no_match(&DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]), &json!([[101]]))
}
#[test]
fn test_validate_genmap_of_int64_to_text_invalid() -> Result<()> {
validate_schema_no_match(&DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]), &json!(123))
}
#[test]
fn test_validate_variant() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
let instance = json!(
{
"tag": "TupleStructListOfPrimitive", "value": [1, 2, 3]
}
);
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_variant_unit_value() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
let instance = json!(
{
"tag": "NoArgument", "value": {}
}
);
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_variant_unknown_tag() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
let instance = json!(
{
"tag": "UnknownTag", "value": {}
}
);
validate_schema_for_arc_no_match(arc, &ty, &instance)
}
#[test]
fn test_validate_variant_no_tag_or_value() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
let instance = json!({});
validate_schema_for_arc_no_match(arc, &ty, &instance)
}
#[test]
fn test_validate_variant_no_value() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
let instance = json!(
{
"tag": "NoArgument"
}
);
validate_schema_for_arc_no_match(arc, &ty, &instance)
}
#[test]
fn test_validate_enum() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Vehicle"], "SimpleColor");
let instance = json!("Red");
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_enum_unknown_ctor() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Vehicle"], "SimpleColor");
let instance = json!("Yellow");
validate_schema_for_arc_no_match(arc, &ty, &instance)
}
#[test]
fn test_validate_complex_as_object_omit_opt_field() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Nested"], "NestedTemplate");
let instance = json!(
{
"list_of_opt_of_map_of_data": [null, {"key": { "my_bool": true }}],
"map_of_data_to_text": [[{ "my_bool": true }, "text"]],
"owner": "me"
}
);
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_complex_as_array() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Nested"], "NestedTemplate");
let instance = json!(
[
"me",
null,
[null, {"key": { "my_bool": true }}],
[[{ "my_bool": true }, "text"]],
]
);
validate_schema_for_arc_match(arc, &ty, &instance)
}
#[test]
fn test_validate_complex_invalid_missing_mand_property() -> Result<()> {
let arc = daml_archive();
let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Nested"], "NestedTemplate");
let instance = json!(
{
"list_of_opt_of_map_of_data": [null, {"key": { "my_bool": true }}],
"map_of_data_to_text": [[{ "my_bool": true }, "text"]]
}
);
validate_schema_for_arc_no_match(arc, &ty, &instance)
}
fn validate_schema_match(ty: &DamlType<'_>, instance: &Value) -> Result<()> {
do_validate_schema(&DamlArchive::default(), ty, instance, true)
}
fn validate_schema_no_match(ty: &DamlType<'_>, instance: &Value) -> Result<()> {
do_validate_schema(&DamlArchive::default(), ty, instance, false)
}
fn validate_schema_for_arc_match(arc: &DamlArchive<'_>, ty: &DamlType<'_>, instance: &Value) -> Result<()> {
do_validate_schema(arc, ty, instance, true)
}
fn validate_schema_for_arc_no_match(arc: &DamlArchive<'_>, ty: &DamlType<'_>, instance: &Value) -> Result<()> {
do_validate_schema(arc, ty, instance, false)
}
fn do_validate_schema(arc: &DamlArchive<'_>, ty: &DamlType<'_>, instance: &Value, matches: bool) -> Result<()> {
let schema = JsonSchemaEncoder::new(arc).encode_type(ty)?;
let compiled =
JSONSchema::compile(&schema).map_err(|e| anyhow!("failed to compile schema: {}", e.to_string()))?;
let result = compiled.validate(instance);
assert_eq!(matches, result.is_ok());
Ok(())
}
fn get_schema_config_reference() -> SchemaEncoderConfig {
get_schema_config(
ReferenceMode::Reference {
prefix: "#/components/schemas/".to_string(),
},
DataDict::default(),
)
}
fn get_schema_config_inline() -> SchemaEncoderConfig {
get_schema_config(ReferenceMode::Inline, DataDict::default())
}
fn get_schema_config(reference_mode: ReferenceMode, datadict: DataDict) -> SchemaEncoderConfig {
SchemaEncoderConfig::new(
RenderSchema::default(),
RenderTitle::Data,
RenderDescription::default(),
reference_mode,
datadict,
)
}
fn daml_archive() -> &'static DamlArchive<'static> {
crate::test_util::daml_archive(TESTING_TYPES_DAR_PATH)
}
}