1#![allow(clippy::or_fun_call)]
2
3use std::collections::BTreeMap;
4use std::ops::Not;
5
6use itertools::Itertools;
7use serde::Deserialize;
8use serde_json::json;
9use serde_json::Value;
10
11use daml_lf::element::{
12 DamlArchive, DamlData, DamlEnum, DamlField, DamlTyCon, DamlTyConName, DamlType, DamlTypeVarWithKind, DamlVar,
13 DamlVariant,
14};
15
16use crate::error::DamlJsonSchemaCodecError::NotSerializableDamlType;
17use crate::error::{DamlJsonSchemaCodecError, DamlJsonSchemaCodecResult};
18use crate::schema_data::{
19 DamlJsonSchemaBool, DamlJsonSchemaContractId, DamlJsonSchemaDate, DamlJsonSchemaDecimal, DamlJsonSchemaEnum,
20 DamlJsonSchemaEnumEntry, DamlJsonSchemaGenMap, DamlJsonSchemaGenMapItems, DamlJsonSchemaInt64, DamlJsonSchemaList,
21 DamlJsonSchemaOptional, DamlJsonSchemaOptionalNonTopLevel, DamlJsonSchemaOptionalNonTopLevelOneOf,
22 DamlJsonSchemaOptionalTopLevel, DamlJsonSchemaParty, DamlJsonSchemaRecord, DamlJsonSchemaRecordAsArray,
23 DamlJsonSchemaRecordAsObject, DamlJsonSchemaText, DamlJsonSchemaTextMap, DamlJsonSchemaTimestamp,
24 DamlJsonSchemaUnit, DamlJsonSchemaVariant, DamlJsonSchemaVariantArm,
25};
26use crate::util::AsSingleSliceExt;
27use crate::util::Required;
28
29#[derive(Debug, Deserialize, Default)]
31pub struct DataDict(BTreeMap<String, DataDictEntry>);
32
33#[derive(Debug, Deserialize, Default)]
35pub struct DataDictEntry {
36 title: Option<String>,
37 description: Option<String>,
38 #[serde(default)]
39 items: BTreeMap<String, String>,
40}
41
42const SCHEMA_VERSION: &str = "https://json-schema.org/draft/2020-12/schema";
44
45#[derive(Debug, Copy, Clone)]
47pub enum RenderSchema {
48 None,
50 Data,
52 All,
54}
55
56impl Default for RenderSchema {
57 fn default() -> Self {
58 Self::Data
59 }
60}
61
62#[derive(Debug, Copy, Clone)]
64pub enum RenderTitle {
65 None,
67 Data,
69}
70
71impl Default for RenderTitle {
72 fn default() -> Self {
73 Self::Data
74 }
75}
76
77#[derive(Debug, Copy, Clone)]
79pub enum RenderDescription {
80 None,
82 Data,
84 All,
86}
87
88impl Default for RenderDescription {
89 fn default() -> Self {
90 Self::All
91 }
92}
93
94#[derive(Debug, Clone)]
189pub enum ReferenceMode {
190 Inline,
192 Reference {
194 prefix: String,
195 },
196}
197
198impl Default for ReferenceMode {
199 fn default() -> Self {
200 Self::Inline
201 }
202}
203
204#[derive(Debug, Default)]
206pub struct SchemaEncoderConfig {
207 render_schema: RenderSchema,
208 render_title: RenderTitle,
209 render_description: RenderDescription,
210 reference_mode: ReferenceMode,
211 data_dict: DataDict,
212}
213
214impl SchemaEncoderConfig {
215 pub fn new(
216 render_schema: RenderSchema,
217 render_title: RenderTitle,
218 render_description: RenderDescription,
219 reference_mode: ReferenceMode,
220 data_dict: DataDict,
221 ) -> Self {
222 Self {
223 render_schema,
224 render_title,
225 render_description,
226 reference_mode,
227 data_dict,
228 }
229 }
230}
231
232#[derive(Debug)]
237pub struct JsonSchemaEncoder<'a> {
238 arc: &'a DamlArchive<'a>,
239 config: SchemaEncoderConfig,
240}
241
242impl<'a> JsonSchemaEncoder<'a> {
243 pub fn new(arc: &'a DamlArchive<'a>) -> Self {
245 Self {
246 arc,
247 config: SchemaEncoderConfig::default(),
248 }
249 }
250
251 pub fn new_with_config(arc: &'a DamlArchive<'a>, config: SchemaEncoderConfig) -> Self {
253 Self {
254 arc,
255 config,
256 }
257 }
258
259 pub fn encode_type(&self, ty: &DamlType<'_>) -> DamlJsonSchemaCodecResult<Value> {
261 self.do_encode_type(ty, true, &[], &[])
262 }
263
264 pub fn encode_data(&self, data: &DamlData<'_>) -> DamlJsonSchemaCodecResult<Value> {
266 (data.serializable() && data.type_params().is_empty())
267 .then(|| self.do_encode_data(data, &[]))
268 .unwrap_or_else(|| Err(NotSerializableDamlType(data.name().to_owned())))
269 }
270
271 fn encode_unit(&self) -> DamlJsonSchemaCodecResult<Value> {
284 Ok(serde_json::to_value(DamlJsonSchemaUnit {
285 schema: self.schema_if_all(),
286 description: self.description_if_all("Unit"),
287 ty: "object",
288 additional_properties: false,
289 })?)
290 }
291
292 fn encode_bool(&self) -> DamlJsonSchemaCodecResult<Value> {
304 Ok(serde_json::to_value(DamlJsonSchemaBool {
305 schema: self.schema_if_all(),
306 description: self.description_if_all("Bool"),
307 ty: "boolean",
308 })?)
309 }
310
311 fn encode_text(&self) -> DamlJsonSchemaCodecResult<Value> {
323 Ok(serde_json::to_value(DamlJsonSchemaText {
324 schema: self.schema_if_all(),
325 description: self.description_if_all("Text"),
326 ty: "string",
327 })?)
328 }
329
330 fn encode_party(&self) -> DamlJsonSchemaCodecResult<Value> {
342 Ok(serde_json::to_value(DamlJsonSchemaParty {
343 schema: self.schema_if_all(),
344 description: self.description_if_all("Party"),
345 ty: "string",
346 })?)
347 }
348
349 fn encode_contract_id(&self, template_path: &Option<String>) -> DamlJsonSchemaCodecResult<Value> {
361 let description = match template_path.as_deref() {
362 Some(tid) => self.description_if_all(&format!("ContractId ({})", tid)).map(ToString::to_string),
363 None => self.description_if_all("ContractId").map(ToString::to_string),
364 };
365 Ok(serde_json::to_value(DamlJsonSchemaContractId {
366 schema: self.schema_if_all(),
367 description,
368 ty: "string",
369 })?)
370 }
371
372 fn encode_date(&self) -> DamlJsonSchemaCodecResult<Value> {
384 Ok(serde_json::to_value(DamlJsonSchemaDate {
385 schema: self.schema_if_all(),
386 description: self.description_if_all("Date"),
387 ty: "string",
388 })?)
389 }
390
391 fn encode_timestamp(&self) -> DamlJsonSchemaCodecResult<Value> {
403 Ok(serde_json::to_value(DamlJsonSchemaTimestamp {
404 schema: self.schema_if_all(),
405 description: self.description_if_all("Timestamp"),
406 ty: "string",
407 })?)
408 }
409
410 fn encode_int64(&self) -> DamlJsonSchemaCodecResult<Value> {
422 Ok(serde_json::to_value(DamlJsonSchemaInt64 {
423 schema: self.schema_if_all(),
424 description: self.description_if_all("Int64"),
425 ty: json!(["integer", "string"]),
426 })?)
427 }
428
429 fn encode_decimal(&self) -> DamlJsonSchemaCodecResult<Value> {
441 Ok(serde_json::to_value(DamlJsonSchemaDecimal {
442 schema: self.schema_if_all(),
443 description: self.description_if_all("Decimal"),
444 ty: json!(["number", "string"]),
445 })?)
446 }
447
448 fn encode_list(&self, items: Value) -> DamlJsonSchemaCodecResult<Value> {
463 Ok(serde_json::to_value(DamlJsonSchemaList {
464 schema: self.schema_if_all(),
465 description: self.description_if_all("List"),
466 ty: "array",
467 items,
468 })?)
469 }
470
471 fn encode_textmap(&self, additional_properties: Value) -> DamlJsonSchemaCodecResult<Value> {
492 Ok(serde_json::to_value(DamlJsonSchemaTextMap {
493 schema: self.schema_if_all(),
494 description: self.description_if_all("TextMap"),
495 ty: "object",
496 additional_properties,
497 })?)
498 }
499
500 fn encode_genmap(&self, ty_key: Value, ty_value: Value) -> DamlJsonSchemaCodecResult<Value> {
535 Ok(serde_json::to_value(DamlJsonSchemaGenMap {
536 schema: self.schema_if_all(),
537 description: self.description_if_all("GenMap"),
538 ty: "array",
539 items: DamlJsonSchemaGenMapItems {
540 ty: "array",
541 items: [ty_key, ty_value],
542 min_items: 2,
543 max_items: 2,
544 },
545 })?)
546 }
547
548 fn encode_optional(&self, nested: Value, top_level: bool) -> DamlJsonSchemaCodecResult<Value> {
599 if top_level {
600 Ok(serde_json::to_value(DamlJsonSchemaOptional::TopLevel(DamlJsonSchemaOptionalTopLevel {
601 schema: self.schema_if_all(),
602 description: self.description_if_all("Optional"),
603 one_of: [json!({ "type": "null" }), nested],
604 }))?)
605 } else {
606 Ok(serde_json::to_value(DamlJsonSchemaOptional::NonTopLevel(DamlJsonSchemaOptionalNonTopLevel {
607 schema: self.schema_if_all(),
608 description: self.description_if_all("Optional (depth > 1)"),
609 one_of: [
610 DamlJsonSchemaOptionalNonTopLevelOneOf {
611 ty: "array",
612 items: None,
613 min_items: 0,
614 max_items: 0,
615 },
616 DamlJsonSchemaOptionalNonTopLevelOneOf {
617 ty: "array",
618 items: Some(nested),
619 min_items: 1,
620 max_items: 1,
621 },
622 ],
623 }))?)
624 }
625 }
626
627 fn do_encode_record(
686 &self,
687 name: &str,
688 module_path: impl Iterator<Item = &'a str>,
689 fields: &[DamlField<'_>],
690 type_params: &[DamlTypeVarWithKind<'a>],
691 type_args: &[DamlType<'_>],
692 ) -> DamlJsonSchemaCodecResult<Value> {
693 let data_item_path = Self::format_data_item(module_path, name);
694 let (title, description) = self.get_title_and_description(&data_item_path);
695 Ok(serde_json::to_value(DamlJsonSchemaRecord {
696 schema: self.schema_if_data_or_all(),
697 title: title.or_else(|| self.title_if_data(&data_item_path)),
698 description: description.or(self.description_if_data_or_all(&format!("Record ({})", name))),
699 one_of: [
700 self.do_encode_record_object(name, &data_item_path, fields, type_params, type_args)?,
701 self.do_encode_record_list(name, fields, type_params, type_args)?,
702 ],
703 })?)
704 }
705
706 fn encode_variant(
759 &self,
760 variant: &DamlVariant<'_>,
761 module_path: impl Iterator<Item = &'a str>,
762 type_params: &[DamlTypeVarWithKind<'a>],
763 type_args: &[DamlType<'_>],
764 ) -> DamlJsonSchemaCodecResult<Value> {
765 let data_item_path = Self::format_data_item(module_path, variant.name());
766 let (title, description) = self.get_title_and_description(&data_item_path);
767 let all_arms = variant
768 .fields()
769 .iter()
770 .map(|field| self.encode_variant_arm(variant.name(), &data_item_path, field, type_params, type_args))
771 .collect::<DamlJsonSchemaCodecResult<Vec<_>>>()?;
772 Ok(serde_json::to_value(DamlJsonSchemaVariant {
773 schema: self.schema_if_data_or_all(),
774 title: title.or_else(|| self.title_if_data(&data_item_path)),
775 description: description.or(self.description_if_data_or_all(&format!("Variant ({})", variant.name()))),
776 one_of: all_arms,
777 })?)
778 }
779
780 fn encode_enum(
819 &self,
820 data_enum: &DamlEnum<'_>,
821 module_path: impl Iterator<Item = &'a str>,
822 ) -> DamlJsonSchemaCodecResult<Value> {
823 let data_item_path = Self::format_data_item(module_path, data_enum.name());
824 let (title, description) = self.get_title_and_description(&data_item_path);
825 let all_entries = data_enum
826 .constructors()
827 .map(|field| self.encode_enum_entry(data_enum.name(), &data_item_path, field))
828 .collect::<DamlJsonSchemaCodecResult<Vec<_>>>()?;
829 Ok(serde_json::to_value(DamlJsonSchemaEnum {
830 schema: self.schema_if_data_or_all(),
831 title: title.or_else(|| self.title_if_data(&data_item_path)),
832 description: description.or(self.description_if_data_or_all(&format!("Enum ({})", data_enum.name()))),
833 one_of: all_entries,
834 })?)
835 }
836
837 fn do_encode_type(
838 &self,
839 ty: &'a DamlType<'a>,
840 top_level: bool,
841 type_params: &[DamlTypeVarWithKind<'_>],
842 type_args: &[DamlType<'_>],
843 ) -> DamlJsonSchemaCodecResult<Value> {
844 match ty {
845 DamlType::Unit => self.encode_unit(),
846 DamlType::Bool => self.encode_bool(),
847 DamlType::Text => self.encode_text(),
848 DamlType::ContractId(cid) => self.encode_contract_id(&cid.as_ref().map(|ty| Self::tycon_path(ty))),
849 DamlType::Party => self.encode_party(),
850 DamlType::Timestamp => self.encode_timestamp(),
851 DamlType::Date => self.encode_date(),
852 DamlType::Int64 => self.encode_int64(),
853 DamlType::Numeric(_) => self.encode_decimal(),
854 DamlType::List(tys) =>
855 self.encode_list(self.do_encode_type(tys.as_single()?, true, type_params, type_args)?),
856 DamlType::TextMap(tys) =>
857 self.encode_textmap(self.do_encode_type(tys.as_single()?, true, type_params, type_args)?),
858 DamlType::GenMap(tys) => self.encode_genmap(
859 self.do_encode_type(tys.first().req()?, true, type_params, type_args)?,
860 self.do_encode_type(tys.last().req()?, true, type_params, type_args)?,
861 ),
862 DamlType::Optional(nested) => self
863 .encode_optional(self.do_encode_type(nested.as_single()?, false, type_params, type_args)?, top_level),
864 DamlType::TyCon(tycon) => self.encode_tycon(tycon),
865 DamlType::BoxedTyCon(tycon) => self.encode_boxed_tycon(tycon),
866 DamlType::Var(v) => self.do_encode_type(
867 Self::resolve_type_var(type_params, type_args, v)?,
868 top_level,
869 type_params,
870 type_args,
871 ),
872 DamlType::Nat(_)
873 | DamlType::Arrow
874 | DamlType::Any
875 | DamlType::TypeRep
876 | DamlType::Update
877 | DamlType::Scenario
878 | DamlType::Forall(_)
879 | DamlType::Struct(_)
880 | DamlType::Syn(_)
881 | DamlType::Bignumeric
882 | DamlType::RoundingMode
883 | DamlType::AnyException => Err(DamlJsonSchemaCodecError::UnsupportedDamlType(ty.name().to_owned())),
884 }
885 }
886
887 fn encode_tycon(&self, tycon: &DamlTyCon<'_>) -> DamlJsonSchemaCodecResult<Value> {
891 match &self.config.reference_mode {
892 ReferenceMode::Inline => {
893 let data = self.resolve_tycon(tycon)?;
895 self.do_encode_data(data, tycon.type_arguments())
896 },
897 ReferenceMode::Reference {
898 prefix,
899 } => {
900 let data = self.resolve_tycon(tycon)?;
901 if data.type_params().is_empty() {
902 Ok(Self::encode_reference(prefix, tycon.tycon()))
904 } else {
905 self.do_encode_data(data, tycon.type_arguments())
907 }
908 },
909 }
910 }
911
912 fn encode_boxed_tycon(&self, tycon: &DamlTyCon<'_>) -> DamlJsonSchemaCodecResult<Value> {
916 match &self.config.reference_mode {
917 ReferenceMode::Inline => {
918 Ok(Self::encode_inline_recursive(&tycon.tycon().to_string()))
920 },
921 ReferenceMode::Reference {
922 prefix,
923 } => {
924 let data = self.resolve_tycon(tycon)?;
925 if data.type_params().is_empty() {
926 Ok(Self::encode_reference(prefix, tycon.tycon()))
928 } else {
929 Ok(Self::encode_reference_recursive_with_type_params(&tycon.tycon().to_string()))
931 }
932 },
933 }
934 }
935
936 fn do_encode_data(&self, data: &DamlData<'_>, type_args: &[DamlType<'_>]) -> DamlJsonSchemaCodecResult<Value> {
937 data.serializable()
938 .then(|| match data {
939 DamlData::Template(template) =>
940 self.do_encode_record(template.name(), template.module_path(), template.fields(), &[], type_args),
941 DamlData::Record(record) => self.do_encode_record(
942 record.name(),
943 record.module_path(),
944 record.fields(),
945 record.type_params(),
946 type_args,
947 ),
948 DamlData::Variant(variant) =>
949 self.encode_variant(variant, variant.module_path(), variant.type_params(), type_args),
950 DamlData::Enum(data_enum) => self.encode_enum(data_enum, data_enum.module_path()),
951 })
952 .unwrap_or_else(|| Err(NotSerializableDamlType(data.name().to_owned())))
953 }
954
955 fn do_encode_record_object(
956 &self,
957 name: &str,
958 data_item_path: &str,
959 fields: &[DamlField<'_>],
960 type_params: &[DamlTypeVarWithKind<'a>],
961 type_args: &[DamlType<'_>],
962 ) -> DamlJsonSchemaCodecResult<Value> {
963 let fields_map = fields
964 .iter()
965 .map(|field| {
966 self.do_encode_type(field.ty(), true, type_params, type_args)
967 .map(|json_val| self.update_desc_from_data_dict(json_val, data_item_path, field.name()))
968 })
969 .collect::<DamlJsonSchemaCodecResult<BTreeMap<&str, Value>>>()?;
970 let required = fields
971 .iter()
972 .filter_map(|field| match Self::is_optional_field(field, type_args, type_params) {
973 Ok(is_opt) if !is_opt => Some(Ok(field.name())),
974 Ok(_) => None,
975 Err(e) => Some(Err(e)),
976 })
977 .collect::<DamlJsonSchemaCodecResult<Vec<_>>>()?;
978 Ok(serde_json::to_value(DamlJsonSchemaRecordAsObject {
979 ty: "object",
980 description: self.description_if_all(&format!("Record ({})", name)),
981 properties: fields_map.is_empty().not().then(|| fields_map),
982 additional_properties: false,
983 required,
984 })?)
985 }
986
987 fn do_encode_record_list(
988 &self,
989 name: &str,
990 fields: &[DamlField<'_>],
991 type_params: &[DamlTypeVarWithKind<'a>],
992 type_args: &[DamlType<'_>],
993 ) -> DamlJsonSchemaCodecResult<Value> {
994 let fields_list = fields
995 .iter()
996 .map(|field| self.do_encode_type(field.ty(), true, type_params, type_args))
997 .collect::<DamlJsonSchemaCodecResult<Vec<Value>>>()?;
998 let field_names = fields.iter().map(DamlField::name).join(", ");
999 let item_count = fields_list.len();
1000 Ok(serde_json::to_value(DamlJsonSchemaRecordAsArray {
1001 ty: "array",
1002 description: self.description_if_all(&format!("Record ({}, fields = [{}])", name, field_names)),
1003 items: (item_count > 0).then(|| fields_list),
1004 min_items: item_count,
1005 max_items: item_count,
1006 })?)
1007 }
1008
1009 fn encode_variant_arm(
1010 &self,
1011 name: &str,
1012 data_item_path: &str,
1013 daml_field: &DamlField<'_>,
1014 type_params: &[DamlTypeVarWithKind<'a>],
1015 type_args: &[DamlType<'_>],
1016 ) -> DamlJsonSchemaCodecResult<Value> {
1017 let description = if let Some(DataDictEntry {
1018 items: fields,
1019 ..
1020 }) = self.config.data_dict.0.get(data_item_path)
1021 {
1022 fields.get(daml_field.name()).map(AsRef::as_ref)
1023 } else {
1024 None
1025 };
1026 Ok(serde_json::to_value(DamlJsonSchemaVariantArm {
1027 ty: "object",
1028 title: Some(daml_field.name()),
1029 description: description.or(self.description_if_all(&format!(
1030 "Variant ({}, tag={})",
1031 name,
1032 daml_field.name()
1033 ))),
1034 properties: json!(
1035 {
1036 "tag": { "type": "string", "enum": [daml_field.name()] },
1037 "value": self.do_encode_type(daml_field.ty(), true, type_params, type_args)?
1038 }
1039 ),
1040 required: vec!["tag", "value"],
1041 additional_properties: false,
1042 })?)
1043 }
1044
1045 fn encode_enum_entry(&self, name: &str, data_item_path: &str, entry: &str) -> DamlJsonSchemaCodecResult<Value> {
1046 let description = if let Some(DataDictEntry {
1047 items: fields,
1048 ..
1049 }) = self.config.data_dict.0.get(data_item_path)
1050 {
1051 fields.get(entry).map(AsRef::as_ref)
1052 } else {
1053 None
1054 };
1055 Ok(serde_json::to_value(DamlJsonSchemaEnumEntry {
1056 ty: "string",
1057 title: Some(entry),
1058 description: description.or(self.description_if_all(&format!("Enum ({}, tag={})", name, entry))),
1059 data_enum: vec![entry],
1060 })?)
1061 }
1062
1063 fn encode_reference(prefix: &str, tycon: &DamlTyConName<'_>) -> Value {
1065 json!({ "$ref": format!("{}{}.{}", prefix, tycon.module_path().join("."), tycon.data_name()) })
1066 }
1067
1068 fn encode_inline_recursive(name: &str) -> Value {
1070 json!(
1071 {
1072 "description": format!("Any ({})", name),
1073 "comment": "inline recursive data types cannot be represented"
1074 }
1075 )
1076 }
1077
1078 fn encode_reference_recursive_with_type_params(name: &str) -> Value {
1081 json!(
1082 {
1083 "description": format!("Any ({})", name),
1084 "comment": "recursive data types with type parameters cannot be represented"
1085 }
1086 )
1087 }
1088
1089 fn resolve_tycon(&self, tycon: &DamlTyCon<'_>) -> DamlJsonSchemaCodecResult<&DamlData<'_>> {
1091 self.arc.data_by_tycon(tycon).ok_or_else(|| DamlJsonSchemaCodecError::DataNotFound(tycon.tycon().to_string()))
1092 }
1093
1094 fn resolve_type_var<'arg>(
1097 type_params: &[DamlTypeVarWithKind<'_>],
1098 type_args: &'arg [DamlType<'arg>],
1099 var: &DamlVar<'_>,
1100 ) -> DamlJsonSchemaCodecResult<&'arg DamlType<'arg>> {
1101 let index = type_params
1102 .iter()
1103 .position(|h| h.var() == var.var())
1104 .ok_or_else(|| DamlJsonSchemaCodecError::TypeVarNotFoundInArgs(var.var().to_string()))?;
1105 type_args.get(index).ok_or_else(|| DamlJsonSchemaCodecError::TypeVarNotFoundInParams(var.var().to_string()))
1106 }
1107
1108 fn is_optional_field(
1111 field: &DamlField<'_>,
1112 type_args: &[DamlType<'_>],
1113 type_params: &[DamlTypeVarWithKind<'a>],
1114 ) -> DamlJsonSchemaCodecResult<bool> {
1115 match field.ty() {
1116 DamlType::Optional(_) => Ok(true),
1117 DamlType::Var(var) =>
1118 Ok(matches!(Self::resolve_type_var(type_params, type_args, var)?, DamlType::Optional(_))),
1119 _ => Ok(false),
1120 }
1121 }
1122
1123 fn update_desc_from_data_dict<'f>(
1125 &self,
1126 json_val: Value,
1127 data_item_path: &str,
1128 field_name: &'f str,
1129 ) -> (&'f str, Value) {
1130 let mut json_val = json_val;
1131 if let Some(DataDictEntry {
1132 items: fields,
1133 ..
1134 }) = self.config.data_dict.0.get(data_item_path)
1135 {
1136 if let Some(desc) = fields.get(field_name) {
1137 json_val.as_object_mut().unwrap().insert(String::from("description"), json!(desc));
1138 }
1139 }
1140 (field_name, json_val)
1141 }
1142
1143 fn get_title_and_description(&self, key: &str) -> (Option<&str>, Option<&str>) {
1145 match self.config.data_dict.0.get(key).as_ref() {
1146 Some(DataDictEntry {
1147 title: Some(title),
1148 description: Some(description),
1149 ..
1150 }) => (Some(title.as_str()), Some(description.as_str())),
1151 Some(DataDictEntry {
1152 title: Some(title),
1153 ..
1154 }) => (Some(title.as_str()), None),
1155 Some(DataDictEntry {
1156 description: Some(description),
1157 ..
1158 }) => (None, Some(description.as_str())),
1159 _ => (None, None),
1160 }
1161 }
1162
1163 fn format_data_item(module_path: impl Iterator<Item = &'a str>, data: &str) -> String {
1164 let mut it = module_path;
1165 format!("{}:{}", it.join("."), data)
1166 }
1167
1168 fn tycon_path(cid: &'a DamlType<'a>) -> String {
1169 match cid {
1170 DamlType::TyCon(tycon) | DamlType::BoxedTyCon(tycon) =>
1171 Self::format_data_item(tycon.tycon().module_path(), tycon.tycon().data_name()),
1172 _ => "".to_string(),
1173 }
1174 }
1175
1176 fn schema_if_all(&self) -> Option<&'static str> {
1177 matches!(self.config.render_schema, RenderSchema::All).then(|| SCHEMA_VERSION)
1178 }
1179
1180 fn schema_if_data_or_all(&self) -> Option<&'static str> {
1181 matches!(self.config.render_schema, RenderSchema::Data | RenderSchema::All).then(|| SCHEMA_VERSION)
1182 }
1183
1184 fn title_if_data<'t>(&self, title: &'t str) -> Option<&'t str> {
1185 matches!(self.config.render_title, RenderTitle::Data).then(|| title)
1186 }
1187
1188 fn description_if_all<'t>(&self, description: &'t str) -> Option<&'t str> {
1189 matches!(self.config.render_description, RenderDescription::All).then(|| description)
1190 }
1191
1192 fn description_if_data_or_all<'t>(&self, description: &'t str) -> Option<&'t str> {
1193 matches!(self.config.render_description, RenderDescription::Data | RenderDescription::All).then(|| description)
1194 }
1195}
1196
1197#[cfg(test)]
1198mod tests {
1199 use anyhow::{anyhow, Result};
1200 use assert_json_diff::assert_json_eq;
1201 use jsonschema::JSONSchema;
1202
1203 use super::*;
1204
1205 static TESTING_TYPES_DAR_PATH: &str = "../resources/testing_types_sandbox/TestingTypes-latest.dar";
1206
1207 #[macro_export]
1208 macro_rules! get_expected {
1209 ($name : literal) => {
1210 serde_json::from_str::<Value>(include_str!(concat!("../test_resources/json_schema/", $name)))
1211 };
1212 }
1213
1214 #[macro_export]
1215 macro_rules! get_datadict {
1216 ($name : literal) => {
1217 serde_yaml::from_str::<DataDict>(include_str!(concat!("../test_resources/json_schema/", $name)))
1218 };
1219 }
1220
1221 #[test]
1222 fn test_unit() -> DamlJsonSchemaCodecResult<()> {
1223 let ty = DamlType::Unit;
1224 let expected = get_expected!("test_unit.json")?;
1225 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1226 assert_eq!(actual, expected);
1227 Ok(())
1228 }
1229
1230 #[test]
1231 fn test_text() -> DamlJsonSchemaCodecResult<()> {
1232 let ty = DamlType::Text;
1233 let expected = get_expected!("test_text.json")?;
1234 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1235 assert_eq!(actual, expected);
1236 Ok(())
1237 }
1238
1239 #[test]
1240 fn test_party() -> DamlJsonSchemaCodecResult<()> {
1241 let ty = DamlType::Party;
1242 let expected = get_expected!("test_party.json")?;
1243 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1244 assert_eq!(actual, expected);
1245 Ok(())
1246 }
1247
1248 #[test]
1249 fn test_int64() -> DamlJsonSchemaCodecResult<()> {
1250 let ty = DamlType::Int64;
1251 let expected = get_expected!("test_int64.json")?;
1252 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1253 assert_eq!(actual, expected);
1254 Ok(())
1255 }
1256
1257 #[test]
1258 fn test_numeric() -> DamlJsonSchemaCodecResult<()> {
1259 let ty = DamlType::Numeric(vec![DamlType::Nat(18)]);
1260 let expected = get_expected!("test_numeric.json")?;
1261 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1262 assert_eq!(actual, expected);
1263 Ok(())
1264 }
1265
1266 #[test]
1267 fn test_bool() -> DamlJsonSchemaCodecResult<()> {
1268 let ty = DamlType::Bool;
1269 let expected = get_expected!("test_bool.json")?;
1270 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1271 assert_eq!(actual, expected);
1272 Ok(())
1273 }
1274
1275 #[test]
1276 fn test_contract_id() -> DamlJsonSchemaCodecResult<()> {
1277 let ty = DamlType::ContractId(None);
1278 let expected = get_expected!("test_contract_id.json")?;
1279 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1280 assert_eq!(actual, expected);
1281 Ok(())
1282 }
1283
1284 #[test]
1285 fn test_contract_id_for_template() -> DamlJsonSchemaCodecResult<()> {
1286 let arc = daml_archive();
1287 let ping_ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "PingPong"], "Ping");
1288 let ty = DamlType::ContractId(Some(Box::new(ping_ty)));
1289 let expected = get_expected!("test_contract_id_for_template.json")?;
1290 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1291 assert_eq!(actual, expected);
1292 Ok(())
1293 }
1294
1295 #[test]
1296 fn test_timestamp() -> DamlJsonSchemaCodecResult<()> {
1297 let ty = DamlType::Timestamp;
1298 let expected = get_expected!("test_timestamp.json")?;
1299 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1300 assert_eq!(actual, expected);
1301 Ok(())
1302 }
1303
1304 #[test]
1305 fn test_date() -> DamlJsonSchemaCodecResult<()> {
1306 let ty = DamlType::Date;
1307 let expected = get_expected!("test_date.json")?;
1308 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1309 assert_eq!(actual, expected);
1310 Ok(())
1311 }
1312
1313 #[test]
1315 fn test_optional_int64() -> DamlJsonSchemaCodecResult<()> {
1316 let ty = DamlType::Optional(vec![DamlType::Int64]);
1317 let expected = get_expected!("test_optional_int64.json")?;
1318 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1319 assert_eq!(actual, expected);
1320 Ok(())
1321 }
1322
1323 #[test]
1325 fn test_optional_optional_int64() -> DamlJsonSchemaCodecResult<()> {
1326 let ty = DamlType::Optional(vec![DamlType::Optional(vec![DamlType::Int64])]);
1327 let expected = get_expected!("test_optional_optional_int64.json")?;
1328 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1329 assert_eq!(actual, expected);
1330 Ok(())
1331 }
1332
1333 #[test]
1335 fn test_optional_optional_optional_int64() -> DamlJsonSchemaCodecResult<()> {
1336 let ty = DamlType::Optional(vec![DamlType::Optional(vec![DamlType::Optional(vec![DamlType::Int64])])]);
1337 let expected = get_expected!("test_optional_optional_optional_int64.json")?;
1338 let actual = JsonSchemaEncoder::new(&DamlArchive::default()).encode_type(&ty)?;
1339 assert_eq!(actual, expected);
1340 Ok(())
1341 }
1342
1343 #[test]
1344 fn test_list_of_text() -> DamlJsonSchemaCodecResult<()> {
1345 let arc = daml_archive();
1346 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "RecordArgument");
1347 let expected = get_expected!("test_list_of_text.json")?;
1348 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1349 assert_json_eq!(actual, expected);
1350 Ok(())
1351 }
1352
1353 #[test]
1354 fn test_text_map_of_int64() -> DamlJsonSchemaCodecResult<()> {
1355 let arc = daml_archive();
1356 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "MapTest"], "Bar");
1357 let expected = get_expected!("test_text_map_of_int64.json")?;
1358 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1359 assert_json_eq!(actual, expected);
1360 Ok(())
1361 }
1362
1363 #[test]
1364 fn test_gen_map_of_int_text() -> DamlJsonSchemaCodecResult<()> {
1365 let arc = daml_archive();
1366 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "MapTest"], "Foo");
1367 let expected = get_expected!("test_gen_map_of_int_text.json")?;
1368 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1369 assert_json_eq!(actual, expected);
1370 Ok(())
1371 }
1372
1373 #[test]
1374 fn test_record() -> DamlJsonSchemaCodecResult<()> {
1375 let arc = daml_archive();
1376 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Person");
1377 let expected = get_expected!("test_record.json")?;
1378 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1379 assert_json_eq!(actual, expected);
1380 Ok(())
1381 }
1382
1383 #[test]
1384 fn test_large_field_count() -> DamlJsonSchemaCodecResult<()> {
1385 let arc = daml_archive();
1386 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "LargeExpr"], "Call");
1387 let expected = get_expected!("test_large_field_count.json")?;
1388 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1389 assert_json_eq!(actual, expected);
1390 Ok(())
1391 }
1392
1393 #[test]
1394 fn test_empty_record() -> DamlJsonSchemaCodecResult<()> {
1395 let arc = daml_archive();
1396 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "PingPong"], "ResetPingCount");
1397 let expected = get_expected!("test_empty_record.json")?;
1398 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1399 assert_json_eq!(actual, expected);
1400 Ok(())
1401 }
1402
1403 #[test]
1404 fn test_template() -> DamlJsonSchemaCodecResult<()> {
1405 let arc = daml_archive();
1406 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "PingPong"], "Ping");
1407 let expected = get_expected!("test_template.json")?;
1408 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1409 assert_json_eq!(actual, expected);
1410 Ok(())
1411 }
1412
1413 #[test]
1414 fn test_enum() -> DamlJsonSchemaCodecResult<()> {
1415 let arc = daml_archive();
1416 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Vehicle"], "SimpleColor");
1417 let expected = get_expected!("test_enum.json")?;
1418 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1419 assert_json_eq!(actual, expected);
1420 Ok(())
1421 }
1422
1423 #[test]
1424 fn test_variant() -> DamlJsonSchemaCodecResult<()> {
1425 let arc = daml_archive();
1426 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Shape"], "Color");
1427 let expected = get_expected!("test_variant.json")?;
1428 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1429 assert_json_eq!(actual, expected);
1430 Ok(())
1431 }
1432
1433 #[test]
1434 fn test_optional_depth1() -> DamlJsonSchemaCodecResult<()> {
1435 let arc = daml_archive();
1436 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Depth1");
1437 let expected = get_expected!("test_optional_depth1.json")?;
1438 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1439 assert_json_eq!(actual, expected);
1440 Ok(())
1441 }
1442
1443 #[test]
1444 fn test_optional_depth2() -> DamlJsonSchemaCodecResult<()> {
1445 let arc = daml_archive();
1446 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Depth2");
1447 let expected = get_expected!("test_optional_depth2.json")?;
1448 let actual = JsonSchemaEncoder::new(arc).encode_type(&ty)?;
1449 assert_json_eq!(actual, expected);
1450 Ok(())
1451 }
1452
1453 #[test]
1455 fn test_reference_mode_case_1() -> DamlJsonSchemaCodecResult<()> {
1456 let arc = daml_archive();
1457 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "PersonMap");
1458 let expected = get_expected!("test_reference_mode_case_1.json")?;
1459 let config = get_schema_config_inline();
1460 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
1461 assert_json_eq!(actual, expected);
1462 Ok(())
1463 }
1464
1465 #[test]
1467 fn test_reference_mode_case_2() -> DamlJsonSchemaCodecResult<()> {
1468 let arc = daml_archive();
1469 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "OPerson");
1470 let expected = get_expected!("test_reference_mode_case_2.json")?;
1471 let config = get_schema_config_inline();
1472 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
1473 assert_json_eq!(actual, expected);
1474 Ok(())
1475 }
1476
1477 #[test]
1479 fn test_reference_mode_case_3() -> DamlJsonSchemaCodecResult<()> {
1480 let arc = daml_archive();
1481 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Rec");
1482 let expected = get_expected!("test_reference_mode_case_3.json")?;
1483 let config = get_schema_config_inline();
1484 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
1485 assert_json_eq!(actual, expected);
1486 Ok(())
1487 }
1488
1489 #[test]
1491 fn test_reference_mode_case_4() -> DamlJsonSchemaCodecResult<()> {
1492 let arc = daml_archive();
1493 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "GenericTypes"], "PatternRecord");
1494 let expected = get_expected!("test_reference_mode_case_4.json")?;
1495 let config = get_schema_config_inline();
1496 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
1497 assert_json_eq!(actual, expected);
1498 Ok(())
1499 }
1500
1501 #[test]
1503 fn test_reference_mode_case_5() -> DamlJsonSchemaCodecResult<()> {
1504 let arc = daml_archive();
1505 let data = arc.data(arc.main_package_id(), &["Fuji", "JsonTest"], "PersonMap").req()?;
1506 let expected = get_expected!("test_reference_mode_case_5.json")?;
1507 let config = get_schema_config_reference();
1508 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_data(data)?;
1509 assert_json_eq!(actual, expected);
1510 Ok(())
1511 }
1512
1513 #[test]
1515 fn test_reference_mode_case_6() -> DamlJsonSchemaCodecResult<()> {
1516 let arc = daml_archive();
1517 let data = arc.data(arc.main_package_id(), &["Fuji", "JsonTest"], "Middle").req()?;
1518 let expected = get_expected!("test_reference_mode_case_6.json")?;
1519 let config = get_schema_config_reference();
1520 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_data(data)?;
1521 assert_json_eq!(actual, expected);
1522 Ok(())
1523 }
1524
1525 #[test]
1527 fn test_reference_mode_case_7() -> DamlJsonSchemaCodecResult<()> {
1528 let arc = daml_archive();
1529 let data = arc.data(arc.main_package_id(), &["Fuji", "JsonTest"], "Rec").req()?;
1530 let expected = get_expected!("test_reference_mode_case_7.json")?;
1531 let config = get_schema_config_reference();
1532 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_data(data)?;
1533 assert_json_eq!(actual, expected);
1534 Ok(())
1535 }
1536
1537 #[test]
1539 fn test_reference_mode_case_8() -> DamlJsonSchemaCodecResult<()> {
1540 let arc = daml_archive();
1541 let data = arc.data(arc.main_package_id(), &["Fuji", "JsonTest"], "TopRec").req()?;
1542 let expected = get_expected!("test_reference_mode_case_8.json")?;
1543 let config = get_schema_config_reference();
1544 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_data(data)?;
1545 assert_json_eq!(actual, expected);
1546 Ok(())
1547 }
1548
1549 #[test]
1550 fn test_record_datadict() -> DamlJsonSchemaCodecResult<()> {
1551 let arc = daml_archive();
1552 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Person");
1553 let expected = get_expected!("test_record_datadict.json")?;
1554 let datadict = get_datadict!("datadict.yaml").unwrap();
1555 let config = get_schema_config(ReferenceMode::Inline, datadict);
1556 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
1557 assert_json_eq!(actual, expected);
1558 Ok(())
1559 }
1560
1561 #[test]
1562 fn test_template_datadict() -> DamlJsonSchemaCodecResult<()> {
1563 let arc = daml_archive();
1564 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "PingPong"], "Ping");
1565 let expected = get_expected!("test_template_datadict.json")?;
1566 let datadict = get_datadict!("datadict.yaml").unwrap();
1567 let config = get_schema_config(ReferenceMode::Inline, datadict);
1568 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
1569 assert_json_eq!(actual, expected);
1570 Ok(())
1571 }
1572
1573 #[test]
1574 fn test_enum_datadict() -> DamlJsonSchemaCodecResult<()> {
1575 let arc = daml_archive();
1576 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Vehicle"], "SimpleColor");
1577 let expected = get_expected!("test_enum_datadict.json")?;
1578 let datadict = get_datadict!("datadict.yaml").unwrap();
1579 let config = get_schema_config(ReferenceMode::Inline, datadict);
1580 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
1581 assert_json_eq!(actual, expected);
1582 Ok(())
1583 }
1584
1585 #[test]
1586 fn test_variant_datadict() -> DamlJsonSchemaCodecResult<()> {
1587 let arc = daml_archive();
1588 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Shape"], "Color");
1589 let expected = get_expected!("test_variant_datadict.json")?;
1590 let datadict = get_datadict!("datadict.yaml").unwrap();
1591 let config = get_schema_config(ReferenceMode::Inline, datadict);
1592 let actual = JsonSchemaEncoder::new_with_config(arc, config).encode_type(&ty)?;
1593 assert_json_eq!(actual, expected);
1594 Ok(())
1595 }
1596
1597 #[test]
1598 fn test_fail_for_non_serializable_record() -> Result<()> {
1599 let arc = daml_archive();
1600 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "HigherKindTest"], "HigherKindedData");
1601 match JsonSchemaEncoder::new(arc).encode_type(&ty) {
1602 Err(DamlJsonSchemaCodecError::NotSerializableDamlType(s)) if s == "HigherKindedData" => Ok(()),
1603 Err(e) => panic!("expected different error: {}", e),
1604 _ => panic!("expected error"),
1605 }
1606 }
1607
1608 #[test]
1609 fn test_fail_for_generic_missing_type_arg() -> Result<()> {
1610 let arc = daml_archive();
1611 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa");
1612 match JsonSchemaEncoder::new(arc).encode_type(&ty) {
1613 Err(DamlJsonSchemaCodecError::TypeVarNotFoundInParams(s)) if s == "a" => Ok(()),
1614 Err(e) => panic!("expected different error: {}", e),
1615 _ => panic!("expected error"),
1616 }
1617 }
1618
1619 #[test]
1623 fn test_validate_unit() -> Result<()> {
1624 validate_schema_match(&DamlType::Unit, &json!({}))
1625 }
1626
1627 #[test]
1628 fn test_validate_unit_unexpected_property() -> Result<()> {
1629 validate_schema_no_match(&DamlType::Unit, &json!({ "unexpected_key": "unexpected_value" }))
1630 }
1631
1632 #[test]
1633 fn test_validate_int64_as_integer() -> Result<()> {
1634 validate_schema_match(&DamlType::Int64, &json!(42))
1635 }
1636
1637 #[test]
1638 fn test_validate_int64_as_string() -> Result<()> {
1639 validate_schema_match(&DamlType::Int64, &json!("42"))
1640 }
1641
1642 #[test]
1643 fn test_validate_int64_invalid() -> Result<()> {
1644 validate_schema_no_match(&DamlType::Int64, &json!(3.4111))
1645 }
1646
1647 #[test]
1648 fn test_validate_text() -> Result<()> {
1649 validate_schema_match(&DamlType::Text, &json!("test"))
1650 }
1651
1652 #[test]
1653 fn test_validate_text_invalid() -> Result<()> {
1654 validate_schema_no_match(&DamlType::Text, &json!(42))
1655 }
1656
1657 #[test]
1658 fn test_validate_party() -> Result<()> {
1659 validate_schema_match(&DamlType::Party, &json!("Alice"))
1660 }
1661
1662 #[test]
1663 fn test_validate_party_invalid() -> Result<()> {
1664 validate_schema_no_match(&DamlType::Party, &json!(1.234))
1665 }
1666
1667 #[test]
1668 fn test_validate_contract_id() -> Result<()> {
1669 validate_schema_match(&DamlType::ContractId(None), &json!("#1:0"))
1670 }
1671
1672 #[test]
1673 fn test_validate_contract_id_invalid() -> Result<()> {
1674 validate_schema_no_match(&DamlType::ContractId(None), &json!({}))
1675 }
1676
1677 #[test]
1678 fn test_validate_bool_true() -> Result<()> {
1679 validate_schema_match(&DamlType::Bool, &json!(true))
1680 }
1681
1682 #[test]
1683 fn test_validate_bool_false() -> Result<()> {
1684 validate_schema_match(&DamlType::Bool, &json!(false))
1685 }
1686
1687 #[test]
1688 fn test_validate_bool_invalid() -> Result<()> {
1689 validate_schema_no_match(&DamlType::Bool, &json!(0))
1690 }
1691
1692 #[test]
1693 fn test_validate_numeric_with_decimal() -> Result<()> {
1694 validate_schema_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!(9.99))
1695 }
1696
1697 #[test]
1698 fn test_validate_numeric_with_integer() -> Result<()> {
1699 validate_schema_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!(42))
1700 }
1701
1702 #[test]
1703 fn test_validate_numeric_with_decimal_string() -> Result<()> {
1704 validate_schema_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!("3.14"))
1705 }
1706
1707 #[test]
1708 fn test_validate_numeric_with_integer_string() -> Result<()> {
1709 validate_schema_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!("42"))
1710 }
1711
1712 #[test]
1713 fn test_validate_numeric_invalid() -> Result<()> {
1714 validate_schema_no_match(&DamlType::Numeric(vec![DamlType::Nat(18)]), &json!([1, 2, 3]))
1715 }
1716
1717 #[test]
1718 fn test_validate_date() -> Result<()> {
1719 validate_schema_match(&DamlType::Date, &json!("2021-05-14"))
1720 }
1721
1722 #[test]
1723 fn test_validate_bad_date() -> Result<()> {
1724 validate_schema_match(&DamlType::Date, &json!("the schema only validates that this is a string"))
1725 }
1726
1727 #[test]
1728 fn test_validate_date_invalid() -> Result<()> {
1729 validate_schema_no_match(&DamlType::Date, &json!(1234))
1730 }
1731
1732 #[test]
1733 fn test_validate_timestamp() -> Result<()> {
1734 validate_schema_match(&DamlType::Timestamp, &json!("1990-11-09T04:30:23.1234569Z"))
1735 }
1736
1737 #[test]
1738 fn test_validate_bad_timestamp() -> Result<()> {
1739 validate_schema_match(&DamlType::Timestamp, &json!("the schema only validates that this is a string"))
1740 }
1741
1742 #[test]
1743 fn test_validate_timestamp_invalid() -> Result<()> {
1744 validate_schema_no_match(&DamlType::Timestamp, &json!({"foo": 42}))
1745 }
1746
1747 #[test]
1748 fn test_validate_list_of_int() -> Result<()> {
1749 validate_schema_match(&DamlType::List(vec![DamlType::Int64]), &json!([1, 2, 3, 42]))
1750 }
1751
1752 #[test]
1753 fn test_validate_list_of_text() -> Result<()> {
1754 validate_schema_match(&DamlType::List(vec![DamlType::Text]), &json!(["this", "is", "a", "test"]))
1755 }
1756
1757 #[test]
1758 fn test_validate_list_invalid_mixed_types() -> Result<()> {
1759 validate_schema_no_match(&DamlType::List(vec![DamlType::Text]), &json!(["foo", 42, "bar"]))
1760 }
1761
1762 #[test]
1763 fn test_validate_textmap_of_int64() -> Result<()> {
1764 validate_schema_match(&DamlType::TextMap(vec![DamlType::Int64]), &json!({"key1": 1, "key2": 2}))
1765 }
1766
1767 #[test]
1768 fn test_validate_textmap_of_int64_empty() -> Result<()> {
1769 validate_schema_match(&DamlType::TextMap(vec![DamlType::Int64]), &json!({}))
1770 }
1771
1772 #[test]
1773 fn test_validate_textmap_of_int64_invalid() -> Result<()> {
1774 validate_schema_no_match(&DamlType::TextMap(vec![DamlType::Int64]), &json!({"key1": {}}))
1775 }
1776
1777 #[test]
1780 fn test_validate_textmap_of_int64_duplicate_key() -> Result<()> {
1781 validate_schema_match(&DamlType::TextMap(vec![DamlType::Int64]), &json!({"key1": 1, "key1": 2}))
1782 }
1783
1784 #[test]
1785 fn test_validate_genmap_of_int64_to_text() -> Result<()> {
1786 validate_schema_match(
1787 &DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]),
1788 &json!([[101, "foo"], [102, "bar"]]),
1789 )
1790 }
1791
1792 #[test]
1793 fn test_validate_genmap_of_person_to_text() -> Result<()> {
1794 let arc = daml_archive();
1795 let person_ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "JsonTest"], "Person");
1796 let ty = DamlType::GenMap(vec![person_ty, DamlType::Text]);
1797 let instance = json!(
1798 [[{"name": "Alice", "age": 10}, "Alice is 10"], [{"name": "Bob", "age": 6}, "Bob is 6"]]
1799 );
1800 validate_schema_for_arc_match(arc, &ty, &instance)
1801 }
1802
1803 #[test]
1804 fn test_validate_genmap_invalid() -> Result<()> {
1805 validate_schema_no_match(&DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]), &json!([[101, "foo", 102]]))
1806 }
1807
1808 #[test]
1810 fn test_validate_generic_opt_int_some() -> Result<()> {
1811 let arc = daml_archive();
1812 let ty =
1813 DamlType::make_tycon_with_args(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa", vec![DamlType::Int64]);
1814 let instance = json!({ "foo": 42 });
1815 validate_schema_for_arc_match(arc, &ty, &instance)
1816 }
1817
1818 #[test]
1820 fn test_validate_generic_opt_int_none() -> Result<()> {
1821 let arc = daml_archive();
1822 let ty =
1823 DamlType::make_tycon_with_args(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa", vec![DamlType::Int64]);
1824 let instance = json!({});
1825 validate_schema_for_arc_match(arc, &ty, &instance)
1826 }
1827
1828 #[test]
1830 fn test_validate_generic_opt_opt_int_some() -> Result<()> {
1831 let arc = daml_archive();
1832 let ty = DamlType::make_tycon_with_args(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa", vec![
1833 DamlType::Optional(vec![DamlType::Int64]),
1834 ]);
1835 let instance = json!({ "foo": [42] });
1836 validate_schema_for_arc_match(arc, &ty, &instance)
1837 }
1838
1839 #[test]
1841 fn test_validate_generic_opt_opt_int_none() -> Result<()> {
1842 let arc = daml_archive();
1843 let ty = DamlType::make_tycon_with_args(arc.main_package_id(), &["Fuji", "JsonTest"], "Oa", vec![
1844 DamlType::Optional(vec![DamlType::Int64]),
1845 ]);
1846 let instance = json!({ "foo": [] });
1847 validate_schema_for_arc_match(arc, &ty, &instance)
1848 }
1849
1850 #[test]
1851 fn test_validate_genmap_of_int64_to_text_empty() -> Result<()> {
1852 validate_schema_match(&DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]), &json!([]))
1853 }
1854
1855 #[test]
1856 fn test_validate_genmap_of_int64_to_text_broken() -> Result<()> {
1857 validate_schema_no_match(&DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]), &json!([[101]]))
1858 }
1859
1860 #[test]
1861 fn test_validate_genmap_of_int64_to_text_invalid() -> Result<()> {
1862 validate_schema_no_match(&DamlType::GenMap(vec![DamlType::Int64, DamlType::Text]), &json!(123))
1863 }
1864
1865 #[test]
1866 fn test_validate_variant() -> Result<()> {
1867 let arc = daml_archive();
1868 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
1869 let instance = json!(
1870 {
1871 "tag": "TupleStructListOfPrimitive", "value": [1, 2, 3]
1872 }
1873 );
1874 validate_schema_for_arc_match(arc, &ty, &instance)
1875 }
1876
1877 #[test]
1878 fn test_validate_variant_unit_value() -> Result<()> {
1879 let arc = daml_archive();
1880 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
1881 let instance = json!(
1882 {
1883 "tag": "NoArgument", "value": {}
1884 }
1885 );
1886 validate_schema_for_arc_match(arc, &ty, &instance)
1887 }
1888
1889 #[test]
1890 fn test_validate_variant_unknown_tag() -> Result<()> {
1891 let arc = daml_archive();
1892 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
1893 let instance = json!(
1894 {
1895 "tag": "UnknownTag", "value": {}
1896 }
1897 );
1898 validate_schema_for_arc_no_match(arc, &ty, &instance)
1899 }
1900
1901 #[test]
1902 fn test_validate_variant_no_tag_or_value() -> Result<()> {
1903 let arc = daml_archive();
1904 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
1905 let instance = json!({});
1906 validate_schema_for_arc_no_match(arc, &ty, &instance)
1907 }
1908
1909 #[test]
1910 fn test_validate_variant_no_value() -> Result<()> {
1911 let arc = daml_archive();
1912 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "VariantExamples"], "AllVariantTypes");
1913 let instance = json!(
1914 {
1915 "tag": "NoArgument"
1916 }
1917 );
1918 validate_schema_for_arc_no_match(arc, &ty, &instance)
1919 }
1920
1921 #[test]
1922 fn test_validate_enum() -> Result<()> {
1923 let arc = daml_archive();
1924 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Vehicle"], "SimpleColor");
1925 let instance = json!("Red");
1926 validate_schema_for_arc_match(arc, &ty, &instance)
1927 }
1928
1929 #[test]
1930 fn test_validate_enum_unknown_ctor() -> Result<()> {
1931 let arc = daml_archive();
1932 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Vehicle"], "SimpleColor");
1933 let instance = json!("Yellow");
1934 validate_schema_for_arc_no_match(arc, &ty, &instance)
1935 }
1936
1937 #[test]
1938 fn test_validate_complex_as_object_omit_opt_field() -> Result<()> {
1939 let arc = daml_archive();
1940 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Nested"], "NestedTemplate");
1941 let instance = json!(
1942 {
1943 "list_of_opt_of_map_of_data": [null, {"key": { "my_bool": true }}],
1944 "map_of_data_to_text": [[{ "my_bool": true }, "text"]],
1945 "owner": "me"
1946 }
1947 );
1948 validate_schema_for_arc_match(arc, &ty, &instance)
1949 }
1950
1951 #[test]
1952 fn test_validate_complex_as_array() -> Result<()> {
1953 let arc = daml_archive();
1954 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Nested"], "NestedTemplate");
1955 let instance = json!(
1956 [
1957 "me",
1958 null,
1959 [null, {"key": { "my_bool": true }}],
1960 [[{ "my_bool": true }, "text"]],
1961 ]
1962 );
1963 validate_schema_for_arc_match(arc, &ty, &instance)
1964 }
1965
1966 #[test]
1967 fn test_validate_complex_invalid_missing_mand_property() -> Result<()> {
1968 let arc = daml_archive();
1969 let ty = DamlType::make_tycon(arc.main_package_id(), &["Fuji", "Nested"], "NestedTemplate");
1970 let instance = json!(
1971 {
1972 "list_of_opt_of_map_of_data": [null, {"key": { "my_bool": true }}],
1973 "map_of_data_to_text": [[{ "my_bool": true }, "text"]]
1974 }
1975 );
1976 validate_schema_for_arc_no_match(arc, &ty, &instance)
1977 }
1978
1979 fn validate_schema_match(ty: &DamlType<'_>, instance: &Value) -> Result<()> {
1980 do_validate_schema(&DamlArchive::default(), ty, instance, true)
1981 }
1982
1983 fn validate_schema_no_match(ty: &DamlType<'_>, instance: &Value) -> Result<()> {
1984 do_validate_schema(&DamlArchive::default(), ty, instance, false)
1985 }
1986
1987 fn validate_schema_for_arc_match(arc: &DamlArchive<'_>, ty: &DamlType<'_>, instance: &Value) -> Result<()> {
1988 do_validate_schema(arc, ty, instance, true)
1989 }
1990
1991 fn validate_schema_for_arc_no_match(arc: &DamlArchive<'_>, ty: &DamlType<'_>, instance: &Value) -> Result<()> {
1992 do_validate_schema(arc, ty, instance, false)
1993 }
1994
1995 fn do_validate_schema(arc: &DamlArchive<'_>, ty: &DamlType<'_>, instance: &Value, matches: bool) -> Result<()> {
1996 let schema = JsonSchemaEncoder::new(arc).encode_type(ty)?;
1997 let compiled =
1998 JSONSchema::compile(&schema).map_err(|e| anyhow!("failed to compile schema: {}", e.to_string()))?;
1999 let result = compiled.validate(instance);
2000 assert_eq!(matches, result.is_ok());
2001 Ok(())
2002 }
2003
2004 fn get_schema_config_reference() -> SchemaEncoderConfig {
2005 get_schema_config(
2006 ReferenceMode::Reference {
2007 prefix: "#/components/schemas/".to_string(),
2008 },
2009 DataDict::default(),
2010 )
2011 }
2012
2013 fn get_schema_config_inline() -> SchemaEncoderConfig {
2014 get_schema_config(ReferenceMode::Inline, DataDict::default())
2015 }
2016
2017 fn get_schema_config(reference_mode: ReferenceMode, datadict: DataDict) -> SchemaEncoderConfig {
2018 SchemaEncoderConfig::new(
2019 RenderSchema::default(),
2020 RenderTitle::Data,
2021 RenderDescription::default(),
2022 reference_mode,
2023 datadict,
2024 )
2025 }
2026
2027 fn daml_archive() -> &'static DamlArchive<'static> {
2028 crate::test_util::daml_archive(TESTING_TYPES_DAR_PATH)
2029 }
2030}