diff --git a/src/Microsoft.Health.Fhir.SpecManager/Language/ZodValidator.cs b/src/Microsoft.Health.Fhir.SpecManager/Language/ZodValidator.cs
new file mode 100644
index 000000000..11f554d51
--- /dev/null
+++ b/src/Microsoft.Health.Fhir.SpecManager/Language/ZodValidator.cs
@@ -0,0 +1,587 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using Microsoft.Health.Fhir.CodeGenCommon.Extensions;
+using Microsoft.Health.Fhir.SpecManager.Manager;
+using Microsoft.Health.Fhir.SpecManager.Models;
+
+namespace Microsoft.Health.Fhir.SpecManager.Language
+{
+ /// Export to TypeScript - serializable to/from JSON.
+ public sealed class ZodValidator : ILanguage
+ {
+ /// The systems named by display.
+ private static HashSet _systemsNamedByDisplay = new HashSet()
+ {
+ /// Units of Measure have incomprehensible codes after naming substitutions.
+ "http://unitsofmeasure.org",
+ };
+
+ private static HashSet _systemsNamedByCode = new HashSet()
+ {
+ /// Operation Outcomes include c-style string formats in display.
+ "http://terminology.hl7.org/CodeSystem/operation-outcome",
+
+ /// Descriptions have quoted values.
+ "http://terminology.hl7.org/CodeSystem/smart-capabilities",
+
+ /// Descriptions have quoted values.
+ "http://hl7.org/fhir/v2/0301",
+
+ /// Display values are too long to be useful.
+ "http://terminology.hl7.org/CodeSystem/v2-0178",
+
+ /// Display values are too long to be useful.
+ "http://terminology.hl7.org/CodeSystem/v2-0277",
+
+ /// Display values are too long to be useful.
+ "http://terminology.hl7.org/CodeSystem/v3-VaccineManufacturer",
+
+ /// Display values are too long to be useful.
+ "http://hl7.org/fhir/v2/0278",
+
+ /// Display includes operation symbols: $.
+ "http://terminology.hl7.org/CodeSystem/testscript-operation-codes",
+
+ /// Display are often just symbols.
+ "http://hl7.org/fhir/v2/0290",
+
+ /// Display includes too many Unicode characters (invalid export names).
+ "http://hl7.org/fhir/v2/0255",
+
+ /// Display includes too many Unicode characters (invalid export names).
+ "http://hl7.org/fhir/v2/0256",
+ };
+
+ /// FHIR information we are exporting.
+ private FhirVersionInfo _info;
+
+ /// Options for controlling the export.
+ private ExporterOptions _options;
+
+ ///
+ /// True if we should write a namespace directive
+ ///
+ private bool _includeNamespace = false;
+
+ ///
+ /// The namespace to use.
+ ///
+ private string _namespace = string.Empty;
+
+ /// The exported codes.
+ private HashSet _exportedCodes = new HashSet();
+
+ /// The exported resources.
+ private List _exportedResources = new List();
+
+ /// The currently in-use text writer.
+ private ExportStreamWriter _writer;
+
+ /// Name of the language.
+ private const string _languageName = "Zod";
+
+ /// The single file export extension.
+ private const string _singleFileExportExtension = ".ts";
+
+ /// The minimum type script version.
+ private string _minimumTypeScriptVersion = "3.7";
+
+ /// Dictionary mapping FHIR primitive types to language equivalents.
+ private static readonly Dictionary _primitiveTypeMap = new Dictionary()
+ {
+ { "base", "Object" },
+ { "base64Binary", "string" },
+ { "boolean", "boolean" },
+ { "canonical", "string" },
+ { "code", "string" },
+ { "date", "string" },
+ { "dateTime", "string" },
+ { "decimal", "number" },
+ { "id", "string" },
+ { "instant", "string" },
+ { "integer", "number" },
+ { "integer64", "string" }, // int64 serializes as string, need to add custom handling here
+ { "markdown", "string" },
+ { "oid", "string" },
+ { "positiveInt", "number" },
+ { "string", "string" },
+ { "time", "string" },
+ { "unsignedInt", "number" },
+ { "uri", "string" },
+ { "url", "string" },
+ { "uuid", "string" },
+ { "xhtml", "string" },
+ };
+
+ /// Gets the reserved words.
+ /// The reserved words.
+ private static readonly HashSet _reservedWords = new HashSet()
+ {
+ "const",
+ "enum",
+ "export",
+ "interface",
+ "z",
+ };
+
+ /// The generics and type hints.
+ private static readonly Dictionary _genericsAndTypeHints = new Dictionary()
+ {
+ {
+ "Bundle",
+ new GenericTypeHintInfo()
+ {
+ Alias = "BundleContentType",
+ GenericHint = "FhirResource",
+ IncludeBase = true,
+ }
+ },
+ {
+ "Bundle.entry",
+ new GenericTypeHintInfo()
+ {
+ Alias = "BundleContentType",
+ GenericHint = "FhirResource",
+ IncludeBase = true,
+ }
+ },
+ {
+ "Bundle.entry.resource",
+ new GenericTypeHintInfo()
+ {
+ Alias = "BundleContentType",
+ GenericHint = string.Empty,
+ IncludeBase = false,
+ }
+ },
+ };
+
+ /// Gets the name of the language.
+ /// The name of the language.
+ string ILanguage.LanguageName => _languageName;
+
+ ///
+ /// Gets the single file extension for this language - null or empty indicates a multi-file
+ /// export (exporter should copy the contents of the directory).
+ ///
+ string ILanguage.SingleFileExportExtension => _singleFileExportExtension;
+
+ /// Gets the FHIR primitive type map.
+ /// The FHIR primitive type map.
+ Dictionary ILanguage.FhirPrimitiveTypeMap => _primitiveTypeMap;
+
+ /// Gets the reserved words.
+ /// The reserved words.
+ HashSet ILanguage.ReservedWords => _reservedWords;
+
+ ///
+ /// Gets a list of FHIR class types that the language WILL export, regardless of user choices.
+ /// Used to provide information to users.
+ ///
+ List ILanguage.RequiredExportClassTypes => new List()
+ {
+ ExporterOptions.FhirExportClassType.ComplexType,
+ ExporterOptions.FhirExportClassType.Resource,
+ };
+
+ ///
+ /// Gets a list of FHIR class types that the language CAN export, depending on user choices.
+ ///
+ List ILanguage.OptionalExportClassTypes => new List()
+ {
+ };
+
+ /// Gets language-specific options and their descriptions.
+ Dictionary ILanguage.LanguageOptions => new Dictionary()
+ {
+ { "namespace", "Base namespace for TypeScript files (default: fhir{VersionNumber})." },
+ { "min-ts-version", "Minimum TypeScript version (default: 3.7, use '-' for none)." }
+ };
+
+ /// Export the passed FHIR version into the specified directory.
+ /// The information.
+ /// Information describing the server.
+ /// Options for controlling the operation.
+ /// Directory to write files.
+ void ILanguage.Export(
+ FhirVersionInfo info,
+ FhirCapabiltyStatement serverInfo,
+ ExporterOptions options,
+ string exportDirectory)
+ {
+ // set internal vars so we don't pass them to every function
+ // this is ugly, but the interface patterns get bad quickly because we need the type map to copy the FHIR info
+ _info = info;
+ _options = options;
+
+ _includeNamespace = _options.GetParam("namespace", false);
+
+ _namespace = $"fhir{FhirPackageCommon.RForSequence(_info.FhirSequence).Substring(1).ToLowerInvariant()}.zod";
+
+ _minimumTypeScriptVersion = _options.GetParam("min-ts-version", "3.7");
+
+ _exportedCodes = new HashSet();
+ _exportedResources = new List();
+
+ // create a filename for writing (single file for now)
+ string filename = Path.Combine(exportDirectory, $"zod{info.FhirSequence}.ts");
+
+ using (FileStream stream = new FileStream(filename, FileMode.Create))
+ using (ExportStreamWriter writer = new ExportStreamWriter(stream))
+ {
+ _writer = writer;
+
+ WriteHeader();
+
+ WriteComplexes(_info.ComplexTypes.Values, false);
+ WriteComplexes(_info.Resources.Values, true);
+ WriteFHIRResourceSchema();
+ WriteExports();
+ WriteFooter();
+ }
+ }
+
+ /// Writes the complexes.
+ /// The complexes.
+ /// (Optional) True if is resource, false if not.
+ private void WriteComplexes(
+ IEnumerable complexes,
+ bool isResource = false)
+ {
+ foreach (FhirComplex complex in complexes.OrderBy(c => c.Name))
+ {
+ WriteComplex(complex, isResource);
+ }
+ }
+
+ /// Writes a complex.
+ /// The complex.
+ /// True if is resource, false if not.
+ private void WriteComplex(
+ FhirComplex complex,
+ bool isResource)
+ {
+ // check for nested components
+ if (complex.Components != null)
+ {
+ foreach (FhirComplex component in complex.Components.Values)
+ {
+ WriteComplex(component, false);
+ }
+ }
+
+ // zod schema name
+ string schemaName;
+ if (string.IsNullOrEmpty(complex.BaseTypeName) ||
+ complex.Name.Equals("Element", StringComparison.Ordinal))
+ {
+ schemaName = complex.NameForExport(FhirTypeBase.NamingConvention.PascalCase);
+ _writer.WriteLineIndented($"const {schemaName}Schema = z.lazy>>((): z.ZodObject> => {{");
+ _writer.IncreaseIndent();
+ _writer.WriteLineIndented($"return z.object({{");
+ }
+ else if (complex.Name.Equals(complex.BaseTypeName, StringComparison.Ordinal))
+ {
+ schemaName = complex.NameForExport(FhirTypeBase.NamingConvention.PascalCase, true);
+ _writer.WriteLineIndented($"const {schemaName}Schema = z.lazy>>((): z.ZodObject> => {{");
+ _writer.IncreaseIndent();
+ _writer.WriteLineIndented($"return z.object({{");
+ }
+ else
+ {
+ schemaName = complex.NameForExport(FhirTypeBase.NamingConvention.PascalCase, true);
+ string typeName = complex.TypeForExport(FhirTypeBase.NamingConvention.PascalCase, _primitiveTypeMap, false);
+ _writer.WriteLineIndented($"const {schemaName}Schema = z.lazy>>((): z.ZodObject> => {{");
+ _writer.IncreaseIndent();
+ _writer.WriteLineIndented($"return {typeName}Schema.schema.extend({{");
+ }
+
+ _writer.IncreaseIndent();
+
+ if (isResource)
+ {
+ if (ShouldWriteResourceType(complex.Name))
+ {
+ _exportedResources.Add(schemaName);
+ _writer.WriteLineIndented($"resourceType: z.literal('{complex.Name}'),");
+ }
+ else
+ {
+ _writer.WriteLineIndented($"resourceType: z.string(),");
+ }
+ }
+
+ // write elements
+ WriteElements(complex, out List elementsWithCodes);
+
+ _writer.DecreaseIndent();
+
+ // close interface (type)
+ _writer.WriteLineIndented("});");
+
+
+ _writer.DecreaseIndent();
+ _writer.WriteLine("});");
+ }
+
+ /// Writes the expanded resource interface binding.
+ private void WriteExports()
+ {
+ _exportedResources.Sort();
+
+ _writer.WriteLine("export {");
+ int index = 0;
+ int last = _exportedResources.Count - 1;
+ _writer.IncreaseIndent();
+ foreach (string exportedName in _exportedResources)
+ {
+ _writer.WriteLineIndented($"{exportedName}Schema{(index != last ? "," : "")}");
+ index++;
+ }
+ _writer.DecreaseIndent();
+ _writer.WriteLine("};");
+ }
+
+ private void WriteFHIRResourceSchema()
+ {
+ _exportedResources.Sort();
+
+ _writer.WriteLine("FhirResourceSchema = z.union([");
+ int index = 0;
+ int last = _exportedResources.Count - 1;
+ _writer.IncreaseIndent();
+ foreach (string exportedName in _exportedResources)
+ {
+ _writer.WriteLineIndented($"{exportedName}Schema{(index != last ? "," : "")}");
+ index++;
+ }
+ _writer.DecreaseIndent();
+ _writer.WriteLine("]);");
+ }
+
+ /// Determine if we should write resource name.
+ /// The name.
+ /// True if it succeeds, false if it fails.
+ private static bool ShouldWriteResourceType(string name)
+ {
+ switch (name)
+ {
+ case "Resource":
+ case "DomainResource":
+ case "MetadataResource":
+ case "CanonicalResource":
+ return false;
+ }
+
+ return true;
+ }
+
+ /// Writes the elements.
+ /// The complex.
+ /// [out] The elements with codes.
+ private void WriteElements(
+ FhirComplex complex,
+ out List elementsWithCodes)
+ {
+ elementsWithCodes = new List();
+
+ foreach (FhirElement element in complex.Elements.Values.OrderBy(s => s.Name))
+ {
+ if (element.IsInherited)
+ {
+ continue;
+ }
+
+ WriteElement(complex, element);
+
+ if ((element.Codes != null) && (element.Codes.Count > 0))
+ {
+ elementsWithCodes.Add(element);
+ }
+ }
+ }
+
+ /// Writes an element.
+ /// The complex.
+ /// The element.
+ private void WriteElement(
+ FhirComplex complex,
+ FhirElement element)
+ {
+ HashSet primitives = new HashSet();
+ primitives.Add("string");
+ primitives.Add("boolean");
+ primitives.Add("number");
+ Dictionary values = element.NamesAndTypesForExport(
+ FhirTypeBase.NamingConvention.CamelCase,
+ FhirTypeBase.NamingConvention.PascalCase,
+ false,
+ string.Empty,
+ complex.Components.ContainsKey(element.Path));
+
+ foreach (KeyValuePair kvp in values)
+ {
+ bool isPrimative = primitives.Contains(kvp.Value);
+ string result = $"{kvp.Key}: ";
+
+ // Use generated enum for codes when required strength
+ // EXCLUDE the MIME type value set - those should be bound to strings
+ if (element.Codes != null
+ && element.Codes.Any()
+ && !string.IsNullOrEmpty(element.ValueSet)
+ && !string.IsNullOrEmpty(element.BindingStrength)
+ && string.Equals(element.BindingStrength, "required", StringComparison.Ordinal)
+ && (element.ValueSet != "http://www.rfc-editor.org/bcp/bcp13.txt")
+ && (!element.ValueSet.StartsWith("http://hl7.org/fhir/ValueSet/mimetypes", StringComparison.Ordinal)))
+ {
+ if (_info.TryGetValueSet(element.ValueSet, out FhirValueSet vs))
+ {
+ if (element.IsArray)
+ {
+ result += $"z.array(z.enum([{string.Join(",", vs.Concepts.Select(c => $"'{c.Code}'"))}]))";
+ }
+ else
+ {
+ result += $"z.enum([{string.Join(",", vs.Concepts.Select(c => $"'{c.Code}'"))}])";
+ }
+ }
+ else
+ {
+ if (element.IsArray)
+ {
+ result += $"z.array(z.enum([{string.Join(",", element.Codes.Select(c => $"'{c}'"))}]))";
+ }
+ else
+ {
+ result += $"z.enum([{string.Join(",", element.Codes.Select(c => $"'{c}'"))}])";
+ }
+ }
+ }
+ else if (kvp.Value.Equals("Resource", StringComparison.Ordinal))
+ {
+ if (element.IsArray)
+ {
+ result += $"z.array(FhirResourceSchema)";
+ }
+ else
+ {
+ result += "FhirResourceSchema";
+ }
+ }
+ else
+ {
+ if (element.IsArray)
+ {
+ result += $"z.array({(isPrimative ? "z." : "")}{kvp.Value}{(isPrimative ? "()" : "Schema")})";
+ }
+ else
+ {
+ result += $"{(isPrimative ? "z." : "")}{kvp.Value}{(isPrimative ? "()" : "Schema")}";
+ }
+ }
+
+ // TODO various fields in the fhir spec are mutually exclusive ors (xors)
+ // but zod does not have an out of the box xor method, so using nullish
+ result+= ".nullish()";
+
+ if (element.IsOptional)
+ {
+ result += ".optional()";
+ }
+
+ /* worry about this later
+ if (!string.IsNullOrEmpty(element.Comment))
+ {
+ result += $".description('{element.Comment}')";
+ }
+ */
+
+ result += ",";
+ _writer.WriteLineIndented(result);
+ }
+ }
+
+ /// Writes a header.
+ private void WriteHeader()
+ {
+ _writer.WriteLineIndented("// ");
+ _writer.WriteLineIndented($"// Contents of: {_info.PackageName} version: {_info.VersionString}");
+ _writer.WriteLineIndented($" // Primitive Naming Style: {FhirTypeBase.NamingConvention.None}");
+ _writer.WriteLineIndented($" // Complex Type / Resource Naming Style: {FhirTypeBase.NamingConvention.PascalCase}");
+ _writer.WriteLineIndented($" // Interaction Naming Style: {FhirTypeBase.NamingConvention.None}");
+ _writer.WriteLineIndented($" // Extension Support: {_options.ExtensionSupport}");
+
+ if ((_options.ExportList != null) && _options.ExportList.Any())
+ {
+ string restrictions = string.Join("|", _options.ExportList);
+ _writer.WriteLineIndented($" // Restricted to: {restrictions}");
+ }
+
+ if ((_options.LanguageOptions != null) && (_options.LanguageOptions.Count > 0))
+ {
+ foreach (KeyValuePair kvp in _options.LanguageOptions)
+ {
+ _writer.WriteLineIndented($" // Language option: \"{kvp.Key}\" = \"{kvp.Value}\"");
+ }
+ }
+
+ if (!_minimumTypeScriptVersion.Equals("-"))
+ {
+ _writer.WriteLine($"// Minimum TypeScript Version: {_minimumTypeScriptVersion}");
+ }
+
+ // import zod as z
+ _writer.WriteLine("import { z } from 'zod';");
+ _writer.WriteLine();
+ // zod does not support inheritance so we need to use composition via union
+ // we also have a hoisting problem
+ _writer.WriteLine("let FhirResourceSchema: z.ZodTypeAny;");
+
+ if (_includeNamespace)
+ {
+ _writer.WriteLineIndented($"export as namespace {_namespace};");
+ }
+ }
+
+ /// Writes a footer.
+ private void WriteFooter()
+ {
+ return;
+ }
+
+ /// Writes an indented comment.
+ /// The value.
+ private void WriteIndentedComment(string value)
+ {
+ _writer.WriteLineIndented($"/**");
+
+ string comment = value.Replace('\r', '\n').Replace("\r\n", "\n", StringComparison.Ordinal).Replace("\n\n", "\n", StringComparison.Ordinal);
+
+ string[] lines = comment.Split('\n');
+ foreach (string line in lines)
+ {
+ _writer.WriteIndented(" * ");
+ _writer.WriteLine(line);
+ }
+
+ _writer.WriteLineIndented($" */");
+ }
+
+ /// Information about the generic type hint.
+ private struct GenericTypeHintInfo
+ {
+ internal string Alias;
+ internal bool IncludeBase;
+ internal string GenericHint;
+ }
+ }
+}