packages feed

hydra-0.15.0: src/main/haskell/Hydra/Sources/Other/AzureDtld.hs

module Hydra.Sources.Other.AzureDtld where

-- Standard imports for type-level sources outside of the kernel
import           Hydra.Kernel
import           Hydra.Dsl.Annotations
import           Hydra.Dsl.Bootstrap
import           Hydra.Dsl.Types                 ((>:))
import qualified Hydra.Dsl.Types                 as T
import qualified Hydra.Sources.Kernel.Types.Core as Core
import qualified Data.List                       as L
import qualified Data.Map                        as M
import qualified Data.Set                        as S
import qualified Data.Maybe                      as Y


ns :: Namespace
ns = Namespace "hydra.azure.dtld"

define :: String -> Type -> Binding
define = defineType ns

dtld :: String -> Type
dtld = typeref ns

-- Helper types for bounded values
dtmi128 :: Type
dtmi128 = bounded Nothing (Just 128) $ dtld "Dtmi"

dtmi2048 :: Type
dtmi2048 = bounded Nothing (Just 2048) $ dtld "Dtmi"

nonemptyString64 :: Type
nonemptyString64 = boundedString (Just 1) (Just 64)

nonemptyString512 :: Type
nonemptyString512 = boundedString (Just 1) (Just 512)

string128 :: Type
string128 = boundedString Nothing (Just 128)

-- Helper field constructors
idField :: Bool -> Int -> String -> FieldType
idField req maxlen desc = "id">: doc desc $ if req then t else T.maybe t
  where
    t = bounded Nothing (Just maxlen) $ dtld "Dtmi"

idOptionalField :: [Char] -> FieldType
idOptionalField cat = idField False 2048 $
  "The ID of the " ++ cat ++ ". If no @id is provided, the digital twin interface processor will assign one."

commentField :: FieldType
commentField = "comment">: doc "A comment for model authors" $ T.maybe nonemptyString512

descriptionField :: FieldType
descriptionField = "description">: doc "A localizable description for display" $ T.maybe nonemptyString512

displayNameField :: FieldType
displayNameField = "displayName">: doc "A localizable name for display" $ T.maybe nonemptyString64

nameField :: String -> String -> FieldType
nameField cat regex = "name">: doc (
  "The 'programming' name of the " ++ cat ++ ". The name may only contain the characters " ++
  "a-z, A-Z, 0-9, and underscore, and must match this regular expression " ++ regex ++ ".")
  nonemptyString64

schemaField :: String -> FieldType
schemaField cat = "schema">: doc ("The data type of the " ++ cat) $ dtld "Schema"

schemaInterfaceField :: String -> FieldType
schemaInterfaceField cat = "schema">: doc ("The data type of the " ++ cat) $ dtld "Interface"

typeField :: String -> FieldType
typeField desc = "type">: doc desc $ dtld "Iri"

unitField :: String -> FieldType
unitField cat = "unit">:
  doc ("The unit type of the " ++ cat ++ ". A semantic type is required for the unit property to be available.") $
  T.maybe $ dtld "Unit"

writableField :: String -> FieldType
writableField cat = "writable">:
  doc (
    "A boolean value that indicates whether the " ++ cat ++ " is writable by an external source, " ++
    "such as an application, or not. The default value is false (read-only).") $
  T.maybe T.boolean

module_ :: Module
module_ = Module {
            moduleNamespace = ns,
            moduleDefinitions = (map toTypeDef definitions),
            moduleTermDependencies = [Core.ns],
            moduleTypeDependencies = [],
            moduleDescription = Just ("An Azure Digital Twin Definition Language (DTLD) model. Based on:\n" ++
      "  https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md#digital-twins-definition-language\n" ++
      "DTLD features which are not currently included in this model:\n" ++
      "  * geospatial schemas (https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md#geospatial-schemas)\n" ++
      "  * semantic types and units (https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md#semantic-types)\n" ++
      "  * model versioning (https://github.com/Azure/opendigitaltwins-dtdl/blob/master/DTDL/v2/dtdlv2.md#model-versioning)")}
  where
    definitions = [
      command,
      commandPayload,
      commandType,
      component,
      dtmi,
      enumValue,
      field,
      integerOrString,
      interface_,
      interface_Contents,
      iri,
      mapKey,
      mapValue,
      property_,
      relationship,
      schema,
      schema_Array,
      schema_Complex,
      schema_Enum,
      schema_Interface,
      schema_Interface_Type,
      schema_Map,
      schema_Object,
      schema_Primitive,
      telemetry,
      unit_]

command :: Binding
command = define "Command" $
  doc "A Command describes a function or operation that can be performed on any digital twin." $
  T.record [
    typeField "This must be 'Command'",
    nameField "command" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$",
    idOptionalField "command",
    commentField,
    descriptionField,
    displayNameField,
    "commandType">:
      doc (
        "This property is deprecated. Either value, synchronous or asynchronous, has the same meaning: " ++
        "a command that starts execution within a configurable time and that completes execution " ++
        "within a configurable time.") $
      T.maybe $ dtld "CommandType",
    "request">:
      doc "A description of the input to the Command" $
      T.maybe $ dtld "CommandPayload",
    "response">:
      doc "A description of the output of the Command" $
      T.maybe $ dtld "CommandPayload"]

commandPayload :: Binding
commandPayload = define "CommandPayload" $
  doc "A CommandPayload describes the inputs to or the outputs from a Command." $
  T.record [
    nameField "payload" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$",
    schemaField "payload",
    idOptionalField "payload",
    commentField,
    descriptionField,
    displayNameField]

commandType :: Binding
commandType = define "CommandType" $
  doc (
    "CommandType is deprecated. Either value, synchronous or asynchronous, has the same meaning: " ++
    "a command that starts execution within a configurable time and that completes execution " ++
    "within a configurable time.") $
  T.enum [
    "synchronous",
    "asynchronous"]

component :: Binding
component = define "Component" $
  doc (
    "Components enable interfaces to be composed of other interfaces. Components are different from " ++
    "relationships because they describe contents that are directly part of the interface. " ++
    "(A relationship describes a link between two interfaces.)") $
  T.record [
    typeField "This must be 'Component'",
    nameField "component" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$",
    schemaInterfaceField "component", -- Note: "maximum depth of 1; no cycles"
    idOptionalField "component",
    commentField,
    descriptionField,
    displayNameField]

dtmi :: Binding
dtmi = define "Dtmi" $
  doc "A digital twin model identifier" $ T.wrap T.string

enumValue :: Binding
enumValue = define "EnumValue" $
  doc "An EnumValue describes an element of an Enum." $
  T.record [
    nameField "enum value" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$",
    "enumValue">:
      doc (
        "The on-the-wire value that maps to the EnumValue. EnumValue may be either an integer or a string " ++
        "and must be unique for all enum values in this enum.") $
      dtld "IntegerOrString",
    idOptionalField "enum value",
    commentField,
    descriptionField,
    displayNameField]

field :: Binding
field = define "Field" $
  doc "A Field describes a field in an Object." $
  T.record [
    nameField "field" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$",
    schemaField "field",
    idOptionalField "field",
    commentField,
    descriptionField,
    displayNameField]

integerOrString :: Binding
integerOrString = define "IntegerOrString" $ T.union [
  "integer">: T.int32,
  "string">: T.string]

interface_ :: Binding
interface_ = define "Interface" $ T.record [
  idField True 128 "A digital twin model identifier for the interface",
  typeField "This must be 'Interface'",
  "context">:
    doc (
      "The context to use when processing this interface. " ++
      "For this version, it must be set to 'dtmi:dtdl:context;2'") $
    dtld "Iri",
  commentField,
  "contents">:
    doc (
      "A set of objects that define the contents (Telemetry, Properties, Commands, Relationships, " ++
      "and/or Components) of this interface") $
    T.maybe $ boundedSet Nothing (Just 300) $ dtld "Interface_Contents",
  descriptionField,
  displayNameField,
  "extends">: -- Note: there is a maximum depth of 10 levels, not captured here
    doc (
      "A set of DTMIs that refer to interfaces this interface inherits from. " ++
      "Interfaces can inherit from multiple interfaces.") $
    T.maybe $ boundedSet Nothing (Just 2) $ dtld "Interface",
  "schemas">:
    doc "A set of IRIs or objects that refer to the reusable schemas within this interface." $
    T.maybe $ T.set $ dtld "Schema_Interface"]

interface_Contents :: Binding
interface_Contents = define "Interface_Contents" $ T.union [
  "command">: dtld "Command",
  "component">: dtld "Component",
  "property">: dtld "Property",
  "relationship">: dtld "Relationship",
  "telemetry">: dtld "Telemetry"]

iri :: Binding
iri = define "Iri" $ T.wrap T.string

mapKey :: Binding
mapKey = define "MapKey" $
  doc "A MapKey describes the key in a Map. The schema of a MapKey must be string." $
  T.record [
    nameField "map's key" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$",
    schemaField "map's key", -- Note: "must be string"
    idOptionalField "map key",
    commentField,
    descriptionField,
    displayNameField]

mapValue :: Binding
mapValue = define "MapValue" $
  doc "A MapValue describes the values in a Map." $
  T.record [
    nameField "map's value" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$",
    schemaField "map's values",
    idOptionalField "map value",
    commentField,
    descriptionField,
    displayNameField]

property_ :: Binding
property_ = define "Property" $
  doc (
    "A Property describes the read-only and read/write state of any digital twin. " ++
    "For example, a device serial number may be a read-only property, the desired temperature " ++
    "on a thermostat may be a read-write property; and the name of a room may be a read-write property.") $
  T.record [
    typeField "This must at least be 'Property'. It can also include a semantic type.",
    nameField "property" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$.",
    -- Note: "may not be Array nor any complex schema that contains Array"
    schemaField "Property",
    idOptionalField "property",
    commentField,
    descriptionField,
    displayNameField,
    unitField "property",
    writableField "property"]

relationship :: Binding
relationship = define "Relationship" $
  doc (
    "A Relationship describes a link to another digital twin and enables graphs of digital twins " ++
    "to be created. Relationships are different from Components because they describe a link " ++
    "to a separate digital twin.") $
  T.record [
    typeField "This must be 'Relationship'",
    nameField "relationship" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$",
    idOptionalField "relationship description",
    commentField,
    descriptionField,
    displayNameField,
    "maxMultiplicity">:
      -- Note: "must be >= 1 and >= minMultiplicity"
      doc (
        "The maximum multiplicity for the target of the relationship. The default value is infinite " ++
        "(there may be an unlimited number of relationship instances for this relationship).") $
      T.maybe $ bounded (Just 1) Nothing T.int32,
    "minMultiplicity">:
      -- Note: "must be <= maxMultiplicity"
      doc (
        "The minimum multiplicity for the target of the relationship. The default value is 0 " ++
        "(this relationship is permitted to have no instances). In DTDL v2, minMultiplicity must always be 0.") $
      T.maybe $ bounded (Just 0) Nothing T.int32,
    "properties">:
      doc "A set of Properties that define relationship-specific state" $
      T.maybe $ bounded Nothing (Just 300) $ T.set $ dtld "Property",
    "target">:
      doc "An interface ID. The default value (when target is not specified) is that the target may be any interface." $
      T.maybe $ dtld "Interface",
      writableField "relationship"]

schema :: Binding
schema = define "Schema" $
  doc (
    "Schemas are used to describe the on-the-wire or serialized format of the data in a digital twin interface. " ++
    "A full set of primitive data types are provided, along with support for a variety of complex schemas " ++
    "in the forms of Arrays, Enums, Maps, and Objects. Schemas described through digital twin's schema " ++
    "definition language are compatible with popular serialization formats, including JSON, Avro, and Protobuf.") $
  T.union [
    "primitive">: dtld "Schema_Primitive",
    "complex">: dtld "Schema_Complex"]

schema_Array :: Binding
schema_Array = define "Schema_Array" $
  doc (
    "An Array describes an indexable data type where each element is of the same schema. " ++
    "An Array elements' schema can itself be a primitive or complex schema.") $
  T.record [
    typeField "This must be 'Array'",
    "elementSchema">:
      doc "The data type of the array elements" $
      dtld "Schema",
    idOptionalField "array",
    commentField,
    descriptionField,
    displayNameField]

schema_Complex :: Binding
schema_Complex = define "Schema_Complex" $
  doc ("Complex schemas are designed for supporting complex data types made up of primitive data types. " ++
       "Currently the following complex schemas are provided: Array, Enum, Map, and Object. " ++
       "A complex schema can be specified directly as the value in a schema statement or " ++
       "described in the interface schemas set and referenced in the schema statement.") $
  T.union [
    -- Note: interface schemas are currently special, and are not included in this union
    "array">: dtld "Schema_Array",
    "enum">: dtld "Schema_Enum",
    "map">: dtld "Schema_Map",
    "object">: dtld "Schema_Object"]

schema_Enum :: Binding
schema_Enum = define "Schema_Enum" $
  doc (
    "An Enum describes a data type with a set of named labels that map to values. The values in an Enum " ++
    "can be either integers or strings, but the labels are always strings.") $
  T.record [
    typeField "Enum",
    "enumValues">:
      doc "A set of enum value and label mappings" $
      T.list $ dtld "EnumValue", -- Note: the spec seems to be wrong here; it specifies a lone "EnumValue" instead of a set
    "valueSchema">:
      doc "The data type for the enum values. All enum values must be of the same type." $
      dtld "IntegerOrString",
    idOptionalField "enum",
    commentField,
    descriptionField,
    displayNameField]

schema_Interface :: Binding
schema_Interface = define "Schema_Interface" $
  doc (
    "Within an interface definition, complex schemas may be defined for reusability across " ++
    "Telemetry, Properties, and Commands. This is designed to promote readability and improved " ++
    "maintenance because schemas that are reused can be defined once (per interface). Interface schemas " ++
    "are defined in the schemas property of an interface.") $
  T.record [
    idField True 2048 "The globally unique identifier for the schema",
    "type">:
      doc "The type of complex schema. This must refer to one of the complex schema classes (Array, Enum, Map, or Object)." $
      dtld "Schema_Interface_Type",
    commentField,
    descriptionField,
    displayNameField]

schema_Interface_Type :: Binding
schema_Interface_Type = define "Schema_Interface_Type" $ T.union [
  "array">: dtld "Schema_Array",
  "enum">: dtld "Schema_Enum",
  "map">: dtld "Schema_Map",
  "object">: dtld "Schema_Object"]

schema_Map :: Binding
schema_Map = define "Schema_Map" $
  doc ("A Map describes a data type of key-value pairs where the values share the same schema. " ++
       "The key in a Map must be a string. The values in a Map can be any schema.") $
  T.record [
    typeField "Map",
    "mapKey">:
      doc "A description of the keys in the map" $
      dtld "MapKey",
    "mapValue">:
      doc "A description of the values in the map" $
      dtld "MapValue",
    idOptionalField "map",
    commentField,
    descriptionField,
    displayNameField]

schema_Object :: Binding
schema_Object = define "Schema_Object" $
  doc (
    "An Object describes a data type made up of named fields (like a struct in C). " ++
    "The fields in an Object map can be primitive or complex schemas.") $
  T.record [
    typeField "Object",
    "fields">:
      doc "A set of field descriptions, one for each field in the Object" $
      bounded Nothing (Just 30) $ T.set $ dtld "Field", -- Note: "max depth 5 levels"
    idOptionalField "object",
    commentField,
    descriptionField,
    displayNameField]

schema_Primitive :: Binding
schema_Primitive = define "Schema_Primitive" $
  doc (
    "A full set of primitive data types are provided and can be specified directly as the value " ++
    "in a schema statement in a digital twin interface.") $
  T.union [
    "boolean">: doc "A boolean value" T.unit,
    "date">: doc "A full-date as defined in section 5.6 of RFC 3339" T.unit,
    "dateTime">: doc "A date-time as defined in RFC 3339" T.unit,
    "double">: doc "An IEEE 8-byte floating point" T.unit,
    "duration">: doc "A duration in ISO 8601 format" T.unit,
    "float">: doc "An IEEE 4-byte floating point" T.unit,
    "integer">: doc "A signed 4-byte integer" T.unit,
    "long">: doc "A signed 8-byte integer" T.unit,
    "string">: doc "A UTF8 string" T.unit,
    "time">: doc "A full-time as defined in section 5.6 of RFC 3339" T.unit]

telemetry :: Binding
telemetry = define "Telemetry" $
  doc (
    "Telemetry describes the data emitted by any digital twin, whether the data is a regular stream " ++
    "of sensor readings or a computed stream of data, such as occupancy, " ++
    "or an occasional error or information message.") $
  T.record [
    typeField "This must be at least 'Telemetry'. It can also include a semantic type",
    nameField "telemetry" "^[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$.",
    schemaField "Telemetry",
    idField False 2048
      "The ID of the telemetry. If no @id is provided, the digital twin interface processor will assign one.",
    commentField,
    descriptionField,
    displayNameField,
    unitField "Telemetry"]

unit_ :: Binding
unit_ = define "Unit" $ T.wrap T.unit