daml_json/
schema_encoder.rs

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/// A data dictionary for augmenting the generated JSON Schema with `title` and `description` attributes.
30#[derive(Debug, Deserialize, Default)]
31pub struct DataDict(BTreeMap<String, DataDictEntry>);
32
33/// A data item in the data dictionary.
34#[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
42/// The JSON schema version.
43const SCHEMA_VERSION: &str = "https://json-schema.org/draft/2020-12/schema";
44
45/// Control which JSON schemas should include a `$schema` property.
46#[derive(Debug, Copy, Clone)]
47pub enum RenderSchema {
48    /// Do not render the `$schema` property for any schemas
49    None,
50    /// Render the `$schema` property for Daml data (Record, Template, Enum & Variant) schemas only.
51    Data,
52    /// Render the `$schema` property for all schemas.
53    All,
54}
55
56impl Default for RenderSchema {
57    fn default() -> Self {
58        Self::Data
59    }
60}
61
62/// Control which JSON schemas should include a `title` property.
63#[derive(Debug, Copy, Clone)]
64pub enum RenderTitle {
65    /// Do not render the `title` property for any schemas
66    None,
67    /// Render the `title` property for Daml data (Record, Template, Enum & Variant) schemas only.
68    Data,
69}
70
71impl Default for RenderTitle {
72    fn default() -> Self {
73        Self::Data
74    }
75}
76
77/// Control which JSON schemas should include a `description` property.
78#[derive(Debug, Copy, Clone)]
79pub enum RenderDescription {
80    /// Do not render the `description` property for any schemas
81    None,
82    /// Render the `description` property for Daml data (Record, Template, Enum & Variant) schemas only.
83    Data,
84    /// Render the `description` property for all schemas.
85    All,
86}
87
88impl Default for RenderDescription {
89    fn default() -> Self {
90        Self::All
91    }
92}
93
94/// Control whether nested `DamlTyCon` are referenced or inlined.
95///
96/// If `Inline` mode is set then the encoder will attempt to emit the target data type nested under the parent data
97/// type.
98///
99/// If `Reference` mode is set then the encoder will attempt to emit an absolute reference to a data type from a given
100/// `prefix`.  In this mode it is assumed the the target data type will be emitted elsewhere and made available under
101/// the `prefix`.
102///
103/// It is not possible to emit every possible `DamlTyCon` in the requested mode and so there are some specific rules
104/// which apply based on the requested mode, whether the target data type is recursive and whether the target type
105/// expects type parameters.
106///
107/// - Recursive: Indicates that the `DamlTyCon` resolves to a data type which (directly or indirectly) contains itself
108/// as a field or type argument.
109///
110/// - Type Parameters:  Indicates that the `DamlTyCon` resolves to a data type which has type one or more parameters
111/// that must be resolved before that data type can be emitted.
112///
113/// The following table enumerates how a `DamlTyCon` will be encoded for all possible cases:
114///
115/// | Mode      | Recursive? | Type params? | Encoding                                                     |
116/// |-----------|------------|--------------|--------------------------------------------------------------|
117/// | Inline    | No         | No           | 1 - Encode target type inline                                |
118/// | Inline    | No         | Yes          | 2 - Encode target type inline (with resolved type arguments) |
119/// | Inline    | Yes        | No           | 3 - Encode to accept any object                              |
120/// | Inline    | Yes        | Yes          | 4 - Encode to accept any object                              |
121/// | Reference | No         | No           | 5 - Encode as reference to target type                       |
122/// | Reference | No         | Yes          | 6 - Encode target type inline (fallback to #2)               |
123/// | Reference | Yes        | No           | 7 - Encode as reference to target type                       |
124/// | Reference | Yes        | Yes          | 8 - Encode as accept any object (no fallback possible)       |
125///
126/// Cases 1, 2, 5 & 7 are straightforward, whereas cases 3, 4, 6 & 8 are more complex:
127///
128/// * Cases 3 & 4:
129///
130/// If `Inline` mode is chosen and the `DamlTyCon` resolves to a data type which is recursive then the emitter emits a
131/// JSON schema object which matches any JSON type:
132///
133/// For example, given:
134///
135/// ```daml
136/// data Rec = Rec with foo: Text, bar: Rec
137/// ```
138///
139/// The data type `Rec` includes itself recursively and so cannot be emitted `Inline` and will instead be emitted as
140/// follows:
141///
142/// ```json
143/// {
144///    "description": "Any (Rec)",
145///    "comment": "inline recursive data types cannot be represented"
146/// }
147/// ```
148///
149/// * Case 6:
150///
151/// If `Reference` mode is chosen and the `DamlTyCon` resolves to a data type which expects type parameters we
152/// do not emit a reference as the fully resolved target data type is unknown.  In this case the emitter will
153/// `fallback` to `Inline` mode.
154///
155/// For example, given:
156///
157/// ```daml
158/// data Bottom a = Bottom with bottom: a
159/// data Middle = Middle with middle: Bottom Int
160/// ```
161///
162/// Attempting to emit the `middle: Bottom Int` field in `Reference` mode (with a prefix of `#/components/schemas/`)
163/// cannot emit the below reference as this does not account for the type parameter applied to `Bottom`, of which there
164/// are infinitely many:
165///
166/// ```json
167/// {
168///   "$ref": "#/components/schemas/Bottom"
169/// }
170/// ```
171///
172/// Instead, the schema for `Bottom Int` will be emitted `Inline`.
173///
174/// * Case 8:
175///
176/// Case 8 is similar to case 6, however, the `DamlTyCon` resolves to a data type which is also recursive.  In this
177/// case we cannot 'fallback' to `Inline` mode as recursive types cannot be inlined.  The emitter therefore emits a
178/// JSON schema object which matches any JSON type.
179///
180/// For example, given:
181///
182/// ```daml
183/// data TopRec a = TopRec with top: TopRec a
184/// ```
185///
186/// The structure `TopRec` is both recursive and has a type parameter and therefore cannot be emitted as a `$ref` nor
187/// can it 'fallback' to `Inline` mode.
188#[derive(Debug, Clone)]
189pub enum ReferenceMode {
190    /// Inline nested `DamlTyCon`.
191    Inline,
192    /// Reference nested `DamlTyCon` by `$ref` from `prefix`.
193    Reference {
194        prefix: String,
195    },
196}
197
198impl Default for ReferenceMode {
199    fn default() -> Self {
200        Self::Inline
201    }
202}
203
204/// JSON schema encoder configuration.
205#[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/// Encode a `DamlArchive` as a JSON schema.
233///
234/// Generate [JSON Schema](https://json-schema.org/) from Daml LF using the `draft/2020-12/schema` version of the
235/// schema.
236#[derive(Debug)]
237pub struct JsonSchemaEncoder<'a> {
238    arc: &'a DamlArchive<'a>,
239    config: SchemaEncoderConfig,
240}
241
242impl<'a> JsonSchemaEncoder<'a> {
243    /// Create a Json schema encoder for a given `DamlArchive` with the default `SchemaEncoderConfig`.
244    pub fn new(arc: &'a DamlArchive<'a>) -> Self {
245        Self {
246            arc,
247            config: SchemaEncoderConfig::default(),
248        }
249    }
250
251    /// Create a Json schema encoder for a given `DamlArchive` with the given `SchemaEncoderConfig`.
252    pub fn new_with_config(arc: &'a DamlArchive<'a>, config: SchemaEncoderConfig) -> Self {
253        Self {
254            arc,
255            config,
256        }
257    }
258
259    /// Encode a `DamlType` as a JSON schema.
260    pub fn encode_type(&self, ty: &DamlType<'_>) -> DamlJsonSchemaCodecResult<Value> {
261        self.do_encode_type(ty, true, &[], &[])
262    }
263
264    /// Encode a `DamlData` as a JSON schema.
265    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    /// Encode a Daml `Unit` type as JSON schema.
272    ///
273    /// A Daml LF `Unit` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#unit) as an empty
274    /// JSON object and matches the following JSON schema:
275    ///
276    /// ```json
277    /// {
278    ///   "type": "object",
279    ///   "description": "Unit",
280    ///   "additionalProperties": false
281    /// }
282    /// ```
283    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    /// Encode a Daml `Bool` type as JSON schema.
293    ///
294    /// A Daml LF `Bool` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#bool) as a JSON
295    /// `boolean` and matches the following JSON schema:
296    ///
297    /// ```json
298    /// {
299    ///   "type": "boolean",
300    ///   "description": "Bool"
301    /// }
302    /// ```
303    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    /// Encode a Daml `Text` type as JSON schema.
312    ///
313    /// A Daml LF `Text` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#text) as a JSON
314    /// `string` and matches the following JSON schema:
315    ///
316    /// ```json
317    /// {
318    ///   "type": "string",
319    ///   "description": "Text"
320    /// }
321    /// ```
322    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    /// Encode a Daml `Party` type as JSON schema.
331    ///
332    /// A Daml LF `Party` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#party) as a
333    /// JSON `string` and matches the following JSON schema:
334    ///
335    /// ```json
336    /// {
337    ///   "type": "string",
338    ///   "description": "Party"
339    /// }
340    /// ```
341    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    /// Encode a Daml `ContractId` type as JSON schema.
350    ///
351    /// A Daml LF `ContractId` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#contractid)
352    /// as a JSON `string` and matches the following JSON schema:
353    ///
354    /// ```json
355    /// {
356    ///   "type": "string",
357    ///   "description": "ContractId"
358    /// }
359    /// ```
360    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    /// Encode a Daml `Date` type as JSON schema.
373    ///
374    /// A Daml LF `Date` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#date) as a JSON
375    /// `string` and matches the following JSON schema:
376    ///
377    /// ```json
378    /// {
379    ///   "type": "string",
380    ///   "description": "Date"
381    /// }
382    /// ```
383    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    /// Encode a Daml `Timestamp` type as JSON schema.
392    ///
393    /// A Daml LF `Timestamp` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#date) as a
394    /// JSON `string` and matches the following JSON schema:
395    ///
396    /// ```json
397    /// {
398    ///   "type": "string",
399    ///   "description": "Timestamp"
400    /// }
401    /// ```
402    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    /// Encode a Daml `Int64` type as JSON schema.
411    ///
412    /// A Daml LF `Int64` type can be [encoded](https://docs.daml.com/json-api/lf-value-specification.html#int64) as
413    /// either a JSON `integer` or as a JSON `string` and matches the following JSON schema:
414    ///
415    /// ```json
416    /// {
417    ///   "type": "string",
418    ///   "description": "Int64"
419    /// }
420    /// ```
421    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    /// Encode a Daml `Decimal` type as JSON schema.
430    ///
431    /// A Daml LF `Decimal` type can be [encoded](https://docs.daml.com/json-api/lf-value-specification.html#decimal)
432    /// as either a JSON `number` or as a JSON `string` and matches the following JSON schema:
433    ///
434    /// ```json
435    /// {
436    ///   "type": "string",
437    ///   "description": "Decimal"
438    /// }
439    /// ```
440    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    /// Encode a Daml `List` type as JSON schema.
449    ///
450    /// A Daml LF `List a` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#list) as a JSON
451    /// array and matches the following JSON schema where each array item is the JSON schema encoding of the type `a`:
452    ///
453    /// ```json
454    /// {
455    ///   "type": "array",
456    ///   "description": "List",
457    ///   "items": {
458    ///     "type": "..."
459    ///   }
460    /// }
461    /// ```
462    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    /// Encode a Daml `TextMap` type as JSON schema.
472    ///
473    /// A Daml LF `TextMap a` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#textmap) as
474    /// a JSON object and matches the following JSON schema where the value of each entry is the JSON schema encoding
475    /// of the type `a`:
476    ///
477    /// ```json
478    /// {
479    ///   "type": "object",
480    ///   "description": "TextMap",
481    ///   "additionalProperties": {
482    ///     "type": "..."
483    ///   }
484    /// }
485    /// ```
486    ///
487    /// > ⓘ Note: it is not possible to enforce the uniqueness of object properties in the JSON schema (see
488    /// [here](https://github.com/json-schema-org/json-schema-vocabularies/issues/22) for details) and so it is
489    /// assumed that the uniqueness will be enforced by the JSON parser, though the JSON specification does not require
490    /// them to do so.
491    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    /// Encode a Daml `GenMap` type as JSON schema.
501    ///
502    /// A Daml LF `GenMap k v` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#genmap) as
503    /// a JSON array of any length and matches the following JSON schema where each array item is a JSON array of
504    /// length 2 where the first item is the JSON schema encoding of the type `k` and the second item is the JSON
505    /// schema encoding of the type `v`:
506    ///
507    /// ```json
508    /// {
509    ///   "description": "GenMap",
510    ///   "type": "array",
511    ///   "items": {
512    ///     "type": "array",
513    ///     "items": [
514    ///       {
515    ///         "type": "..."
516    ///       },
517    ///       {
518    ///         "type": "..."
519    ///       }
520    ///     ],
521    ///     "minItems": 2,
522    ///     "maxItems": 2,
523    ///   }
524    /// }
525    /// ```
526    ///
527    /// > ⓘ Note: The LF encoding specification states that _"any duplicate keys will cause the map to be treated as
528    /// invalid"_ however this cannot be enforced by the JSON schema for this array of `[key, val]` arrays.
529    ///
530    /// > ⓘ Note: The validation of the `items` keyword has changed in the 2020-12 draft, however the specification
531    /// [notes](https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00#section-10.3.1.2)
532    /// that 'the behavior of "items" without "prefixItems" is identical to that of the schema form of "items" in prior
533    /// drafts'
534    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    /// Encode a Daml `Optional` type as JSON schema.
549    ///
550    /// A top-level Daml LF `Optional a` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#optional)
551    /// as a JSON `null` (`None` case) or the JSON schema encoding of the type `a` (`Some a` case) and matches the
552    /// following JSON schema:
553    ///
554    /// ```json
555    /// {
556    ///   "description": "Optional",
557    ///   "oneOf": [
558    ///     {
559    ///       "type": "null"
560    ///     },
561    ///     {
562    ///       "type": "..."
563    ///     }
564    ///   ]
565    /// }
566    /// ```
567    ///
568    /// A nested Daml LF `Optional a` type is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#optional)
569    /// as an empty JSON `array` (`None` case), or a JSON array of length one where the sole array item is the encoding
570    /// of the type `a` (`Some a` case) and matches the following JSON schema:
571    ///
572    /// ```json
573    /// {
574    ///   "description": "Optional (depth > 1)",
575    ///   "oneOf": [
576    ///     {
577    ///       "type": "array",
578    ///       "minItems": 0,
579    ///       "maxItems": 0
580    ///     },
581    ///     {
582    ///       "type": "array",
583    ///       "items": {
584    ///         "type": "..."
585    ///       },
586    ///       "minItems": 1,
587    ///       "maxItems": 1
588    ///     }
589    ///   ]
590    /// }
591    /// ```
592    ///
593    /// > ⓘ Note: Top-level optional fields may be excluded from the JSON object encoding of Daml `Record` types, see
594    /// the section on Daml `Record` below.
595    ///
596    /// > ⓘ Note: Nested optionals refers to non-top-level the optional such as the optional in parentheses in the
597    /// type `Optional (Optional a)`
598    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    /// Encode a Daml `Record` data type as JSON schema.
628    ///
629    /// A Daml LF `Record` type can be [encoded](https://docs.daml.com/json-api/lf-value-specification.html#record) as
630    /// either a JSON object, or a JSON list and matches the following JSON schema:
631    ///
632    /// ```json
633    /// {
634    ///   "$schema": "https://json-schema.org/draft/2020-12/schema",
635    ///   "title": "Foo.Bar:Baz",
636    ///   "description": "Record (... name ...)",
637    ///   "oneOf": [
638    ///     {
639    ///       "description": "Record ...",
640    ///       "type": "object",
641    ///       "properties": {
642    ///         "field1": {
643    ///           "type": "..."
644    ///         },
645    ///         "field2": {
646    ///           "type": "..."
647    ///         }
648    ///       },
649    ///       "additionalProperties": false,
650    ///       "required": [
651    ///         "list",
652    ///         "of",
653    ///         "required",
654    ///         "properties"
655    ///       ]
656    ///     },
657    ///     {
658    ///       "description": "Record ...",
659    ///       "type": "array",
660    ///       "items": [
661    ///         {
662    ///           "type": "..."
663    ///         },
664    ///         {
665    ///           "type": "..."
666    ///         }
667    ///       ],
668    ///       "minItems": "...",
669    ///       "maxItems": "..."
670    ///     }
671    ///   ]
672    /// }
673    /// ```
674    ///
675    /// > ⓘ Note: For the JSON object encoding, optional fields may be omitted and so only mandatory fields will be
676    /// included in the `required` property list.
677    ///
678    /// > ⓘ Note: For the JSON list encoding, all fields will be included, and the order is significant. The
679    /// `minItems` and `maxItems`will be set to reflect the number of fields on the record.
680    ///
681    /// > ⓘ Note: The validation of the `items` keyword has changed in the 2020-12 draft, however the specification
682    /// [notes](https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00#section-10.3.1.2)
683    /// that 'the behavior of "items" without "prefixItems" is identical to that of the schema form of "items" in prior
684    /// drafts'
685    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    /// Encode a Daml `Variant` data type as JSON schema.
707    ///
708    /// A Daml LF `Variant` is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#variant) as one of
709    /// several JSON `object`, each containing a `tag` and a `value` and matches the following JSON schema, where the
710    /// `tag` is a JSON `string` with a single possible value and `value` is the type contained with a given variant
711    /// arm:
712    ///
713    /// ```json
714    /// {
715    ///   "$schema": "https://json-schema.org/draft/2020-12/schema",
716    ///   "title": "Foo.Bar:Baz",
717    ///   "description": "Variant (... name ...)",
718    ///   "oneOf": [
719    ///     {
720    ///       "title": "tag1",
721    ///       "description": "Variant ...",
722    ///       "type": "object",
723    ///       "properties": {
724    ///         "tag": {
725    ///           "type": "string",
726    ///           "enum": [
727    ///             "tag1"
728    ///           ]
729    ///         },
730    ///         "value": {
731    ///           "type": "..."
732    ///         }
733    ///       },
734    ///       "additionalProperties": false,
735    ///       "required": [ "tag", "value" ]
736    ///     },
737    ///     {
738    ///       "title": "tag2",
739    ///       "description": "Variant ...",
740    ///       "type": "object",
741    ///       "properties": {
742    ///         "tag": {
743    ///           "type": "string",
744    ///           "enum": [
745    ///             "tag2"
746    ///           ]
747    ///         },
748    ///         "value": {
749    ///           "type": "..."
750    ///         }
751    ///       },
752    ///       "additionalProperties": false,
753    ///       "required": [ "tag", "value" ]
754    ///     }
755    ///   ]
756    /// }
757    /// ```
758    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    /// Encode a Daml `Enum` data type as JSON schema.
781    ///
782    /// A Daml LF `Enum` is [encoded](https://docs.daml.com/json-api/lf-value-specification.html#enum) as JSON `string`
783    /// with a defined set of possible enum values and matches the following JSON schema:
784    ///
785    /// ```json
786    /// {
787    ///   "$schema": "https://json-schema.org/draft/2020-12/schema",
788    ///   "title": "Foo.Bar:Baz",
789    ///   "description": "Enum ...",
790    ///   "oneOf": [
791    ///     {
792    ///       "type": "string",
793    ///       "title": "Red",
794    ///       "description": "Enum ...",
795    ///       "enum": [
796    ///         "Red"
797    ///       ]
798    ///     },
799    ///     {
800    ///       "type": "string",
801    ///       "title": "Green",
802    ///       "description": "Enum ...",
803    ///       "enum": [
804    ///         "Green"
805    ///       ]
806    ///     },
807    ///     {
808    ///       "type": "string",
809    ///       "title": "Blue",
810    ///       "description": "Enum ...",
811    ///       "enum": [
812    ///         "Blue"
813    ///       ]
814    ///     }
815    ///   ]
816    /// }
817    /// ```
818    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    /// Encode a `DamlTyCon`.
888    ///
889    /// This covers cases 1, 2, 5 & 6 in the `ReferenceMode` documentation.
890    fn encode_tycon(&self, tycon: &DamlTyCon<'_>) -> DamlJsonSchemaCodecResult<Value> {
891        match &self.config.reference_mode {
892            ReferenceMode::Inline => {
893                // cases 1 & 2
894                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                    // case 5
903                    Ok(Self::encode_reference(prefix, tycon.tycon()))
904                } else {
905                    // case 6
906                    self.do_encode_data(data, tycon.type_arguments())
907                }
908            },
909        }
910    }
911
912    /// Encode a `DamlTyCon` which recursively (directly or indirectly) references itself.
913    ///
914    /// This covers cases 3, 4, 7 & 8 in the `ReferenceMode` documentation.
915    fn encode_boxed_tycon(&self, tycon: &DamlTyCon<'_>) -> DamlJsonSchemaCodecResult<Value> {
916        match &self.config.reference_mode {
917            ReferenceMode::Inline => {
918                // cases 3 & 4
919                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                    // case 7
927                    Ok(Self::encode_reference(prefix, tycon.tycon()))
928                } else {
929                    // case 8
930                    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    ///
1064    fn encode_reference(prefix: &str, tycon: &DamlTyConName<'_>) -> Value {
1065        json!({ "$ref": format!("{}{}.{}", prefix, tycon.module_path().join("."), tycon.data_name()) })
1066    }
1067
1068    /// Inline recursive data types cannot be represented and so we emit a schema object which matches anything.
1069    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    /// Reference recursive data types with type parameters cannot be represented and so we emit a schema object which
1079    /// matches anything.
1080    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    /// Resolve a `DamlTyCon` to a `DamlData` from the archive.
1090    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    /// Resolve a `DamlVar` to a specific `DamlType` from the current type arguments by matching the position of the var
1095    /// in the type parameters.
1096    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    /// Determine if a given field is `DamlType::Optional`, or a `DamlType::Var` that resolves to a
1109    /// `DamlType::Optional`.
1110    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    /// Update the `description` in a `json_val` if this data item and field is defined in the `DataDict`.
1124    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    /// Lookup the data dictionary for a given key and resolve `title` and `description` if they are defined.
1144    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    /// Optional Int64
1314    #[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    /// Optional (Optional Int64)
1324    #[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    /// Optional (Optional (Optional Int64))
1334    #[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    /// Covers case 1 from `ReferenceMode` (inline, non-recursive, no type parameters)
1454    #[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    /// Covers case 2 from `ReferenceMode` (inline, non-recursive, with type parameters)
1466    #[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    /// Covers case 3 from `ReferenceMode` (inline, recursive, no type parameters)
1478    #[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    /// Covers case 4 from `ReferenceMode` (inline, recursive, with type parameters)
1490    #[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    /// Covers case 5 from `ReferenceMode` (reference, non-recursive, no type parameters)
1502    #[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    /// Covers case 6 from `ReferenceMode` (reference, non-recursive, with type parameters)
1514    #[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    /// Covers case 7 from `ReferenceMode` (reference, recursive, no type parameters)
1526    #[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    /// Covers case 8 from `ReferenceMode` (reference, recursive, with type parameters)
1538    #[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 the generated JSON schema against various sample JSON values.
1620    //
1621
1622    #[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    /// The JSON schema does _not_ validate the uniqueness of keys in a `TextMap` and the JSON implementation used for
1778    /// this test does not enforce it either.
1779    #[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    /// { foo: 42 }     -->  Oa { foo: Some 42 }        : Oa Int
1809    #[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    /// { }             -->  Oa { foo: None }           : Oa Int
1819    #[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    /// { foo: [42] }   -->  Oa { foo: Some (Some 42) } : Oa (Optional Int)
1829    #[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    /// { foo: [] }     -->  Oa { foo: Some None }      : Oa (Optional Int)
1840    #[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}