diff --git a/codegen2/.gitignore b/codegen2/.gitignore new file mode 100644 index 0000000000..fb2906ce76 --- /dev/null +++ b/codegen2/.gitignore @@ -0,0 +1 @@ +!res diff --git a/codegen2/README.md b/codegen2/README.md new file mode 100644 index 0000000000..20aa9fbd01 --- /dev/null +++ b/codegen2/README.md @@ -0,0 +1,11 @@ +# codegen2 + +This folder contains the beginnings of a fork of the `codegen` folder. + +The plan of record is to switch our modeling language from DTDL to WoT. +The largest part of this effort will be changing the ProtocolCompiler to ingest WoT Thing Descriptions instead of DTDL models. + +Rather than attempting to evolve the extant ProtocolCompiler to ingest WoT while preserving its DTDL support for legacy usage, we are starting a new solution for a new ProtocolCompiler. +As the architecture of the new codebase develops, portions of the old ProtocolCompiler will be copied into the new one. + +This folder will contain the new ProtocolCompiler while it is under development. diff --git a/codegen2/codegen.sln b/codegen2/codegen.sln new file mode 100644 index 0000000000..8c6c630ea7 --- /dev/null +++ b/codegen2/codegen.sln @@ -0,0 +1,84 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Iot.Operations.TDParser", "src\Azure.Iot.Operations.TDParser\Azure.Iot.Operations.TDParser.csproj", "{D100A0D2-9E52-4FF4-8964-C4DE816DC0D5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TDParse", "src\TDParse\TDParse.csproj", "{9F1A48E4-A681-4FCF-B0FF-1ECBF57E614D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Iot.Operations.CodeGeneration", "src\Azure.Iot.Operations.CodeGeneration\Azure.Iot.Operations.CodeGeneration.csproj", "{FAACC86C-C974-4FE5-B7C4-6C59D7983D82}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Iot.Operations.TypeGenerator", "src\Azure.Iot.Operations.TypeGenerator\Azure.Iot.Operations.TypeGenerator.csproj", "{6C3D81EA-5950-4AFA-AA0F-75EBFB5C1732}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Iot.Operations.SchemaGenerator", "src\Azure.Iot.Operations.SchemaGenerator\Azure.Iot.Operations.SchemaGenerator.csproj", "{AED5A5DA-CA6D-4FC6-B806-050603EF5416}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Iot.Operations.EnvoyGenerator", "src\Azure.Iot.Operations.EnvoyGenerator\Azure.Iot.Operations.EnvoyGenerator.csproj", "{7A0261EF-4C5B-009B-1F89-A78993646A3D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Iot.Operations.ProtocolCompiler", "src\Azure.Iot.Operations.ProtocolCompiler\Azure.Iot.Operations.ProtocolCompiler.csproj", "{5A83D072-8E7F-424C-B009-4A6011A13CA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dtdl2Wot", "src\Dtdl2Wot\Dtdl2Wot.csproj", "{67B4CD3D-17A8-4D63-9996-79A424E628A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Iot.Operations.ProtocolCompilerLib", "src\Azure.Iot.Operations.ProtocolCompilerLib\Azure.Iot.Operations.ProtocolCompilerLib.csproj", "{F4163CB9-D895-4144-AEB5-CBDBC36B21D7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Iot.Operations.ProtocolCompiler.UnitTests", "test\Azure.Iot.Operations.ProtocolCompiler.UnitTests\Azure.Iot.Operations.ProtocolCompiler.UnitTests.csproj", "{24D55C64-5869-C81B-611B-A84AB1D357C0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D100A0D2-9E52-4FF4-8964-C4DE816DC0D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D100A0D2-9E52-4FF4-8964-C4DE816DC0D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D100A0D2-9E52-4FF4-8964-C4DE816DC0D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D100A0D2-9E52-4FF4-8964-C4DE816DC0D5}.Release|Any CPU.Build.0 = Release|Any CPU + {9F1A48E4-A681-4FCF-B0FF-1ECBF57E614D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F1A48E4-A681-4FCF-B0FF-1ECBF57E614D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F1A48E4-A681-4FCF-B0FF-1ECBF57E614D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F1A48E4-A681-4FCF-B0FF-1ECBF57E614D}.Release|Any CPU.Build.0 = Release|Any CPU + {FAACC86C-C974-4FE5-B7C4-6C59D7983D82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAACC86C-C974-4FE5-B7C4-6C59D7983D82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAACC86C-C974-4FE5-B7C4-6C59D7983D82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAACC86C-C974-4FE5-B7C4-6C59D7983D82}.Release|Any CPU.Build.0 = Release|Any CPU + {6C3D81EA-5950-4AFA-AA0F-75EBFB5C1732}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C3D81EA-5950-4AFA-AA0F-75EBFB5C1732}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C3D81EA-5950-4AFA-AA0F-75EBFB5C1732}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C3D81EA-5950-4AFA-AA0F-75EBFB5C1732}.Release|Any CPU.Build.0 = Release|Any CPU + {AED5A5DA-CA6D-4FC6-B806-050603EF5416}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AED5A5DA-CA6D-4FC6-B806-050603EF5416}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AED5A5DA-CA6D-4FC6-B806-050603EF5416}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AED5A5DA-CA6D-4FC6-B806-050603EF5416}.Release|Any CPU.Build.0 = Release|Any CPU + {7A0261EF-4C5B-009B-1F89-A78993646A3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A0261EF-4C5B-009B-1F89-A78993646A3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A0261EF-4C5B-009B-1F89-A78993646A3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A0261EF-4C5B-009B-1F89-A78993646A3D}.Release|Any CPU.Build.0 = Release|Any CPU + {5A83D072-8E7F-424C-B009-4A6011A13CA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A83D072-8E7F-424C-B009-4A6011A13CA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A83D072-8E7F-424C-B009-4A6011A13CA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A83D072-8E7F-424C-B009-4A6011A13CA5}.Release|Any CPU.Build.0 = Release|Any CPU + {67B4CD3D-17A8-4D63-9996-79A424E628A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67B4CD3D-17A8-4D63-9996-79A424E628A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67B4CD3D-17A8-4D63-9996-79A424E628A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67B4CD3D-17A8-4D63-9996-79A424E628A1}.Release|Any CPU.Build.0 = Release|Any CPU + {F4163CB9-D895-4144-AEB5-CBDBC36B21D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4163CB9-D895-4144-AEB5-CBDBC36B21D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4163CB9-D895-4144-AEB5-CBDBC36B21D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4163CB9-D895-4144-AEB5-CBDBC36B21D7}.Release|Any CPU.Build.0 = Release|Any CPU + {24D55C64-5869-C81B-611B-A84AB1D357C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24D55C64-5869-C81B-611B-A84AB1D357C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24D55C64-5869-C81B-611B-A84AB1D357C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24D55C64-5869-C81B-611B-A84AB1D357C0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {24D55C64-5869-C81B-611B-A84AB1D357C0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DBD77462-D0B0-41D2-BC2B-EF17A519C667} + EndGlobalSection +EndGlobal diff --git a/codegen2/eval/.cargo/config.toml b/codegen2/eval/.cargo/config.toml new file mode 100644 index 0000000000..08eaec46f6 --- /dev/null +++ b/codegen2/eval/.cargo/config.toml @@ -0,0 +1,2 @@ +[registries] +aio-sdks = { index = "sparse+https://pkgs.dev.azure.com/azure-iot-sdks/iot-operations/_packaging/preview/Cargo/index/" } diff --git a/codegen2/eval/.gitignore b/codegen2/eval/.gitignore new file mode 100644 index 0000000000..0b42cea230 --- /dev/null +++ b/codegen2/eval/.gitignore @@ -0,0 +1,6 @@ +**/*.cs +**/*.csproj +**/*.rs +**/Cargo.toml +**/*.json +!wot/**/*.json diff --git a/codegen2/eval/SchemaNames.json b/codegen2/eval/SchemaNames.json new file mode 100644 index 0000000000..0157789b7a --- /dev/null +++ b/codegen2/eval/SchemaNames.json @@ -0,0 +1,129 @@ +{ + "suppressTitles": false, + "constantsSchema": "Constants", + "aggregateEventName": "Events", + "aggregateEventSchema": "EventCollection", + "aggregatePropName": "Properties", + "aggregatePropSchema": "PropertyCollection", + "aggregatePropWriteSchema": "PropertyUpdate", + "aggregatePropReadRespSchema": "PropertyCollectionReadRespSchema", + "aggregatePropWriteRespSchema": "PropertyCollectionWriteRespSchema", + "aggregatePropReadErrSchema": "PropertyCollectionReadError", + "aggregatePropWriteErrSchema": "PropertyCollectionWriteError", + "readRequesterBinder": "ReadRequester", + "readResponderBinder": "ReadResponder", + "writeRequesterBinder": "WriteRequester", + "writeResponderBinder": "WriteResponder", + "aggregateReadRespValueField": "_properties", + "aggregateRespErrorField": "_errors", + "eventSchema": { + "in": [ "eventName" ], + "out": "{eventName}Event", + "capitalize": true + }, + "eventValueSchema": { + "in": [ "eventName" ], + "out": "Event{eventName}Value", + "capitalize": true + }, + "eventSenderBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Sender", + "capitalize": true + }, + "eventReceiverBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Receiver", + "capitalize": true + }, + "propSchema": { + "in": [ "propName" ], + "out": "{propName}Property", + "capitalize": true + }, + "writablePropSchema": { + "in": [ "propName" ], + "out": "{propName}WritableProperty", + "capitalize": true + }, + "propReadRespSchema": { + "in": [ "propName" ], + "out": "{propName}ReadRespSchema", + "capitalize": true + }, + "propWriteRespSchema": { + "in": [ "propName" ], + "out": "{propName}WriteRespSchema", + "capitalize": true + }, + "propValueSchema": { + "in": [ "propName" ], + "out": "Property{propName}Value", + "capitalize": true + }, + "propReadActName": { + "in": [ "propName" ], + "out": "Read{propName}", + "capitalize": true + }, + "propWriteActName": { + "in": [ "propName" ], + "out": "Write{propName}", + "capitalize": true + }, + "propMaintainerBinder": { + "in": [ "propSchema" ], + "out": "{propSchema}Maintainer", + "capitalize": true + }, + "propConsumerBinder": { + "in": [ "propSchema" ], + "out": "{propSchema}Consumer", + "capitalize": true + }, + "actionInSchema": { + "in": [ "actionName" ], + "out": "{actionName}InputArguments", + "capitalize": true + }, + "actionOutSchema": { + "in": [ "actionName" ], + "out": "{actionName}OutputArguments", + "capitalize": true + }, + "actionRespSchema": { + "in": [ "actionName" ], + "out": "{actionName}ResponseSchema", + "capitalize": true + }, + "actionExecutorBinder": { + "in": [ "actionName" ], + "out": "{actionName}ActionExecutor", + "capitalize": true + }, + "actionInvokerBinder": { + "in": [ "actionName" ], + "out": "{actionName}ActionInvoker", + "capitalize": true + }, + "propReadRespErrorField": { + "in": [ "propName", "errorSchemaName" ], + "out": "_error", + "capitalize": false + }, + "propWriteRespErrorField": { + "in": [ "propName", "errorSchemaName" ], + "out": "_error", + "capitalize": false + }, + "actionRespErrorField": { + "in": [ "actionName", "errorSchemaName" ], + "out": "_error", + "capitalize": false + }, + "backupSchemaName": { + "in": [ "parentSchemaName", "childName" ], + "out": "{parentSchemaName}{childName}", + "capitalize": true + } +} diff --git a/codegen2/eval/aio/conv.sh b/codegen2/eval/aio/conv.sh new file mode 100755 index 0000000000..2f779dc9f5 --- /dev/null +++ b/codegen2/eval/aio/conv.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +conv=./codegen2/src/Dtdl2Wot/bin/Debug/net9.0/Dtdl2Wot + +$conv ./eng/dtdl/adr-base-service.json ./eng/dtdl +$conv ./eng/dtdl/device-discovery-service.json ./eng/dtdl +$conv ./eng/dtdl/SchemaRegistry-1.json ./eng/dtdl +$conv ./eng/dtdl/statestore.json ./eng/dtdl diff --git a/codegen2/eval/aio/gen.sh b/codegen2/eval/aio/gen.sh new file mode 100644 index 0000000000..758cb33c3a --- /dev/null +++ b/codegen2/eval/aio/gen.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +genFromDtdl=../../../codegen/src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler +genFromWot=../../src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler + +[[ -d ./eng/dtdl/FromDtdl/AssetAndDeviceRegistry ]] && rm -r ./eng/dtdl/FromDtdl/AssetAndDeviceRegistry +$genFromDtdl --modelFile ./eng/dtdl/adr-base-service.json --outDir ./eng/dtdl/FromDtdl/AssetAndDeviceRegistry --lang csharp --sdkPath ../../../dotnet/src/Azure.Iot.Operations.Protocol +$genFromDtdl --modelFile ./eng/dtdl/device-discovery-service.json --outDir ./eng/dtdl/FromDtdl/AssetAndDeviceRegistry --lang csharp --sdkPath ../../../dotnet/src/Azure.Iot.Operations.Protocol +$genFromDtdl --modelFile ./eng/dtdl/adr-base-service.json --outDir ./eng/dtdl/FromDtdl/AssetAndDeviceRegistry/adr_base_gen --lang rust --sdkPath ../../../rust +$genFromDtdl --modelFile ./eng/dtdl/device-discovery-service.json --outDir ./eng/dtdl/FromDtdl/AssetAndDeviceRegistry/device_discovery_gen --lang rust --sdkPath ../../../rust + +[[ -d ./eng/dtdl/FromWot/AssetAndDeviceRegistry ]] && rm -r ./eng/dtdl/FromWot/AssetAndDeviceRegistry +$genFromWot --thingFiles ./eng/dtdl/AdrBaseService.TM.json --outDir ./eng/dtdl/FromWot/AssetAndDeviceRegistry --lang csharp --namespace AdrBaseService --workingDir obj/akri/AdrBaseService --sdkPath ../../../dotnet/src/Azure.Iot.Operations.Protocol +$genFromWot --thingFiles ./eng/dtdl/DeviceDiscoveryService.TM.json --outDir ./eng/dtdl/FromWot/AssetAndDeviceRegistry --lang csharp --namespace DeviceDiscoveryService --workingDir obj/akri/DeviceDiscoveryService --sdkPath ../../../dotnet/src/Azure.Iot.Operations.Protocol +$genFromWot --thingFiles ./eng/dtdl/AdrBaseService.TM.json --outDir ./eng/dtdl/FromWot/AssetAndDeviceRegistry/adr_base_gen --lang rust --namespace AdrBaseService --workingDir target/akri/AdrBaseService --sdkPath ../../../rust +$genFromWot --thingFiles ./eng/dtdl/DeviceDiscoveryService.TM.json --outDir ./eng/dtdl/FromWot/AssetAndDeviceRegistry/device_discovery_gen --lang rust --namespace DeviceDiscoveryService --workingDir target/akri/DeviceDiscoveryService --sdkPath ../../../rust + +[[ -d ./eng/dtdl/FromDtdl/SchemaRegistry ]] && rm -r ./eng/dtdl/FromDtdl/SchemaRegistry +$genFromDtdl --modelFile ./eng/dtdl/SchemaRegistry-1.json --outDir ./eng/dtdl/FromDtdl/SchemaRegistry --lang csharp --sdkPath ../../../dotnet/src/Azure.Iot.Operations.Protocol +$genFromDtdl --modelFile ./eng/dtdl/SchemaRegistry-1.json --outDir ./eng/dtdl/FromDtdl/SchemaRegistry/schemaregistry_gen --lang rust --sdkPath ../../../rust + +[[ -d ./eng/dtdl/FromWot/SchemaRegistry ]] && rm -r ./eng/dtdl/FromWot/SchemaRegistry +$genFromWot --thingFiles ./eng/dtdl/SchemaRegistry.TM.json --outDir ./eng/dtdl/FromWot/SchemaRegistry --lang csharp --namespace SchemaRegistry --workingDir obj/akri/SchemaRegistry --sdkPath ../../../dotnet/src/Azure.Iot.Operations.Protocol +$genFromWot --thingFiles ./eng/dtdl/SchemaRegistry.TM.json --outDir ./eng/dtdl/FromWot/SchemaRegistry/schemaregistry_gen --lang rust --namespace SchemaRegistry --workingDir target/akri/SchemaRegistry --sdkPath ../../../rust + +[[ -d ./eng/dtdl/FromDtdl/StateStore ]] && rm -r ./eng/dtdl/FromDtdl/StateStore +$genFromDtdl --modelFile ./eng/dtdl/statestore.json --outDir ./eng/dtdl/FromDtdl/StateStore --lang csharp --sdkPath ../../../dotnet/src/Azure.Iot.Operations.Protocol +$genFromDtdl --modelFile ./eng/dtdl/statestore.json --outDir ./eng/dtdl/FromDtdl/StateStore/state_store_gen --lang rust --sdkPath ../../../rust + +[[ -d ./eng/dtdl/FromWot/StateStore ]] && rm -r ./eng/dtdl/FromWot/StateStore +$genFromWot --thingFiles ./eng/dtdl/StateStore.TM.json --outDir ./eng/dtdl/FromWot/StateStore --lang csharp --namespace StateStore --workingDir obj/akri/StateStore --sdkPath ../../../dotnet/src/Azure.Iot.Operations.Protocol +$genFromWot --thingFiles ./eng/dtdl/StateStore.TM.json --outDir ./eng/dtdl/FromWot/StateStore/state_store_gen --lang rust --namespace StateStore --workingDir obj/akri/StateStore --sdkPath ../../../dotnet/src/Azure.Iot.Operations.Protocol diff --git a/codegen2/eval/bld.sh b/codegen2/eval/bld.sh new file mode 100644 index 0000000000..f99c82036a --- /dev/null +++ b/codegen2/eval/bld.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +dotnet build dotnet/CommandComplexSchemasSample +dotnet build dotnet/CommandVariantsSample +dotnet build dotnet/Counters +dotnet build dotnet/TelemetryComplexSchemasSample +dotnet build dotnet/TelemetryPrimitiveSchemasSample + +cd rust/command_complex_schemas_gen +cargo build +cd ../.. + +cd rust/command_variants_gen +cargo build +cd ../.. + +cd rust/counters +cargo build +cd ../.. + +cd rust/telemetry_complex_schemas_gen +cargo build +cd ../.. + +cd rust/telemetry_primitive_schemas_gen +cargo build +cd ../.. diff --git a/codegen2/eval/conv.sh b/codegen2/eval/conv.sh new file mode 100644 index 0000000000..8427a66b31 --- /dev/null +++ b/codegen2/eval/conv.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +conv=../src/Dtdl2Wot/bin/Debug/net9.0/Dtdl2Wot.exe + +$conv ./dtdl/test/CommandComplexSchemas.json ./conv + +$conv ./dtdl/test/CommandRaw.json ./conv + +$conv ./dtdl/test/CommandVariants.json ./conv + +$conv ./dtdl/test/PropertySeparate.json ./conv + +$conv ./dtdl/test/PropertyTogether.json ./conv + +$conv ./dtdl/test/TelemetryAndCommand.json ./conv + +$conv ./dtdl/test/TelemetryComplexSchemas.json ./conv + +$conv ./dtdl/test/TelemetryPrimitiveSchemas.json ./conv ./dtdl/test/resolver.json + +$conv ./dtdl/test/TelemetryRawSeparate.json ./conv diff --git a/codegen2/eval/gen.sh b/codegen2/eval/gen.sh new file mode 100644 index 0000000000..8ecbc28e6e --- /dev/null +++ b/codegen2/eval/gen.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +gen=../src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler.exe + +[[ -d dotnet/CommandComplexSchemasSample ]] && rm -r dotnet/CommandComplexSchemasSample +$gen --thingFiles wot/CommandComplexSchemas.TM.json --outDir dotnet/CommandComplexSchemasSample --lang csharp --namespace CommandComplexSchemas --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/command_complex_schemas_gen ]] && rm -r rust/command_complex_schemas_gen +$gen --thingFiles wot/CommandComplexSchemas.TM.json --outDir rust/command_complex_schemas_gen --lang rust --namespace CommandComplexSchemas --sdkPath ../../rust + +[[ -d dotnet/CommandVariantsSample ]] && rm -r dotnet/CommandVariantsSample +$gen --thingFiles wot/CommandVariants.TM.json --outDir dotnet/CommandVariantsSample --lang csharp --namespace CommandVariants --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol --defaultImpl + +[[ -d rust/command_variants_gen ]] && rm -r rust/command_variants_gen +$gen --thingFiles wot/CommandVariants.TM.json --outDir rust/command_variants_gen --lang rust --namespace CommandVariants --sdkPath ../../rust + +[[ -d dotnet/Counters ]] && rm -r dotnet/Counters +$gen --thingFiles wot/CounterCollection.TM.json --outDir dotnet/Counters --lang csharp --namespace CounterCollection --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/counters ]] && rm -r rust/counters +$gen --thingFiles wot/CounterCollection.TM.json --outDir rust/counters --lang rust --namespace CounterCollection --sdkPath ../../rust + +[[ -d dotnet/TelemetryComplexSchemasSample ]] && rm -r dotnet/TelemetryComplexSchemasSample +$gen --thingFiles wot/TelemetryComplexSchemas.TM.json --outDir dotnet/TelemetryComplexSchemasSample --lang csharp --namespace TelemetryComplexSchemas --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/telemetry_complex_schemas_gen ]] && rm -r rust/telemetry_complex_schemas_gen +$gen --thingFiles wot/TelemetryComplexSchemas.TM.json --outDir rust/telemetry_complex_schemas_gen --lang rust --namespace TelemetryComplexSchemas --sdkPath ../../rust + +[[ -d dotnet/TelemetryPrimitiveSchemasSample ]] && rm -r dotnet/TelemetryPrimitiveSchemasSample +$gen --thingFiles wot/TelemetryPrimitiveSchemas.TM.json --outDir dotnet/TelemetryPrimitiveSchemasSample --lang csharp --namespace TelemetryPrimitiveSchemas --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/telemetry_primitive_schemas_gen ]] && rm -r rust/telemetry_primitive_schemas_gen +$gen --thingFiles wot/TelemetryPrimitiveSchemas.TM.json --outDir rust/telemetry_primitive_schemas_gen --lang rust --namespace TelemetryPrimitiveSchemas --sdkPath ../../rust + +[[ -d dotnet/PropertySeparateSample ]] && rm -r dotnet/PropertySeparateSample +$gen --thingFiles wot/PropertySeparate.TM.json --outDir dotnet/PropertySeparateSample --lang csharp --namespace PropertySeparate --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/property_separate_gen ]] && rm -r rust/property_separate_gen +$gen --thingFiles wot/PropertySeparate.TM.json --outDir rust/property_separate_gen --lang rust --namespace PropertySeparate --sdkPath ../../rust + +[[ -d dotnet/PropertyTogetherSample ]] && rm -r dotnet/PropertyTogetherSample +$gen --thingFiles wot/PropertyTogether.TM.json --outDir dotnet/PropertyTogetherSample --lang csharp --namespace PropertyTogether --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/property_together_gen ]] && rm -r rust/property_together_gen +$gen --thingFiles wot/PropertyTogether.TM.json --outDir rust/property_together_gen --lang rust --namespace PropertyTogether --sdkPath ../../rust + +[[ -d dotnet/TwoThingsSample ]] && rm -r dotnet/TwoThingsSample +$gen --thingFiles wot/TwoThings.TM.json --outDir dotnet/TwoThingsSample --lang csharp --namespace TwoThings --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/two_things_gen ]] && rm -r rust/two_things_gen +$gen --thingFiles wot/TwoThings.TM.json --outDir rust/two_things_gen --lang rust --namespace TwoThings --sdkPath ../../rust + +[[ -d dotnet/ExternalSchemasSample ]] && rm -r dotnet/ExternalSchemasSample +$gen --thingFiles wot/ExternalSchemas.TM.json --schemas wot/ExternalSchemas/*.json --outDir dotnet/ExternalSchemasSample --lang csharp --namespace ExternalSchemas --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/external_schemas_gen ]] && rm -r rust/external_schemas_gen +$gen --thingFiles wot/ExternalSchemas.TM.json --schemas wot/ExternalSchemas/*.json --outDir rust/external_schemas_gen --lang rust --namespace ExternalSchemas --sdkPath ../../rust + +[[ -d dotnet/ExternalSchemasOnlySample ]] && rm -r dotnet/ExternalSchemasOnlySample +$gen --schemas wot/ExternalSchemas/*.json --outDir dotnet/ExternalSchemasOnlySample --lang csharp --namespace ExternalSchemas --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/external_schemas_only_gen ]] && rm -r rust/external_schemas_only_gen +$gen --schemas wot/ExternalSchemas/*.json --outDir rust/external_schemas_only_gen --lang rust --namespace ExternalSchemas --sdkPath ../../rust diff --git a/codegen2/eval/gen0.sh b/codegen2/eval/gen0.sh new file mode 100644 index 0000000000..65fb84259e --- /dev/null +++ b/codegen2/eval/gen0.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +gen=../src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler.exe + +[[ -d dotnet/CommandComplexSchemasSample ]] && rm -r dotnet/CommandComplexSchemasSample +$gen --thingFiles conv/CommandComplexSchemas.TM.json --outDir dotnet/CommandComplexSchemasSample --lang csharp --namespace CommandComplexSchemas --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/command_complex_schemas_gen ]] && rm -r rust/command_complex_schemas_gen +$gen --thingFiles conv/CommandComplexSchemas.TM.json --outDir rust/command_complex_schemas_gen --lang rust --namespace CommandComplexSchemas --sdkPath ../../rust + +[[ -d dotnet/CommandRawSample ]] && rm -r dotnet/CommandRawSample +$gen --thingFiles conv/CommandRaw.TM.json --outDir dotnet/CommandRawSample --lang csharp --namespace CommandRaw --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/command_raw_gen ]] && rm -r rust/command_raw_gen +$gen --thingFiles conv/CommandRaw.TM.json --outDir rust/command_raw_gen --lang rust --namespace CommandRaw --sdkPath ../../rust + +[[ -d dotnet/CommandVariantsSample ]] && rm -r dotnet/CommandVariantsSample +$gen --thingFiles conv/CommandVariants.TM.json --outDir dotnet/CommandVariantsSample --lang csharp --namespace CommandVariants --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol --defaultImpl + +[[ -d rust/command_variants_gen ]] && rm -r rust/command_variants_gen +$gen --thingFiles conv/CommandVariants.TM.json --outDir rust/command_variants_gen --lang rust --namespace CommandVariants --sdkPath ../../rust + +[[ -d dotnet/PropertySeparateSample ]] && rm -r dotnet/PropertySeparateSample +$gen --thingFiles conv/PropertySeparate.TM.json --outDir dotnet/PropertySeparateSample --lang csharp --namespace PropertySeparate --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/property_separate_gen ]] && rm -r rust/property_separate_gen +$gen --thingFiles conv/PropertySeparate.TM.json --outDir rust/property_separate_gen --lang rust --namespace PropertySeparate --sdkPath ../../rust + +[[ -d dotnet/PropertyTogetherSample ]] && rm -r dotnet/PropertyTogetherSample +$gen --thingFiles conv/PropertyTogether.TM.json --outDir dotnet/PropertyTogetherSample --lang csharp --namespace PropertyTogether --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/property_together_gen ]] && rm -r rust/property_together_gen +$gen --thingFiles conv/PropertyTogether.TM.json --outDir rust/property_together_gen --lang rust --namespace PropertyTogether --sdkPath ../../rust + +[[ -d dotnet/TelemetryAndCommandSample ]] && rm -r dotnet/TelemetryAndCommandSample +$gen --thingFiles conv/TelemetryAndCommand.TM.json --outDir dotnet/TelemetryAndCommandSample --lang csharp --namespace TelemetryAndCommand --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/telemetry_and_command_gen ]] && rm -r rust/telemetry_and_command_gen +$gen --thingFiles conv/TelemetryAndCommand.TM.json --outDir rust/telemetry_and_command_gen --lang rust --namespace TelemetryAndCommand --sdkPath ../../rust + +[[ -d dotnet/TelemetryComplexSchemasSample ]] && rm -r dotnet/TelemetryComplexSchemasSample +$gen --thingFiles conv/TelemetryComplexSchemas.TM.json --outDir dotnet/TelemetryComplexSchemasSample --lang csharp --namespace TelemetryComplexSchemas --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/telemetry_complex_schemas_gen ]] && rm -r rust/telemetry_complex_schemas_gen +$gen --thingFiles conv/TelemetryComplexSchemas.TM.json --outDir rust/telemetry_complex_schemas_gen --lang rust --namespace TelemetryComplexSchemas --sdkPath ../../rust + +[[ -d dotnet/TelemetryPrimitiveSchemasSample ]] && rm -r dotnet/TelemetryPrimitiveSchemasSample +$gen --thingFiles conv/TelemetryPrimitiveSchemas.TM.json --outDir dotnet/TelemetryPrimitiveSchemasSample --lang csharp --namespace TelemetryPrimitiveSchemas --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/telemetry_primitive_schemas_gen ]] && rm -r rust/telemetry_primitive_schemas_gen +$gen --thingFiles conv/TelemetryPrimitiveSchemas.TM.json --outDir rust/telemetry_primitive_schemas_gen --lang rust --namespace TelemetryPrimitiveSchemas --sdkPath ../../rust + +[[ -d dotnet/TelemetryRawSeparateSample ]] && rm -r dotnet/TelemetryRawSeparateSample +$gen --thingFiles conv/TelemetryRawSeparate.TM.json --outDir dotnet/TelemetryRawSeparateSample --lang csharp --namespace TelemetryRawSeparate --sdkPath ../../dotnet/src/Azure.Iot.Operations.Protocol + +[[ -d rust/telemetry_raw_separate_gen ]] && rm -r rust/telemetry_raw_separate_gen +$gen --thingFiles conv/TelemetryRawSeparate.TM.json --outDir rust/telemetry_raw_separate_gen --lang rust --namespace TelemetryRawSeparate --sdkPath ../../rust diff --git a/codegen2/eval/wot/.vscode/settings.json b/codegen2/eval/wot/.vscode/settings.json new file mode 100644 index 0000000000..8ce7a1138b --- /dev/null +++ b/codegen2/eval/wot/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "json.schemaDownload.enable": true, + "json.schemas": [ + { + "fileMatch": [ "**/*.TM.json" ], + "url": "../../schema/aio-tm-json-schema.json" + } + ] +} diff --git a/codegen2/eval/wot/CommandComplexSchemas.SchemaNames.json b/codegen2/eval/wot/CommandComplexSchemas.SchemaNames.json new file mode 100644 index 0000000000..a2b5d5600d --- /dev/null +++ b/codegen2/eval/wot/CommandComplexSchemas.SchemaNames.json @@ -0,0 +1,22 @@ +{ + "actionInSchema": { + "in": [ "actionName" ], + "out": "{actionName}RequestPayload", + "capitalize": true + }, + "actionOutSchema": { + "in": [ "actionName" ], + "out": "{actionName}ResponsePayload", + "capitalize": true + }, + "actionExecutorBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandExecutor", + "capitalize": true + }, + "actionInvokerBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandInvoker", + "capitalize": true + } +} diff --git a/codegen2/eval/wot/CommandComplexSchemas.TM.json b/codegen2/eval/wot/CommandComplexSchemas.TM.json new file mode 100644 index 0000000000..d2ba9f5470 --- /dev/null +++ b/codegen2/eval/wot/CommandComplexSchemas.TM.json @@ -0,0 +1,108 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "CommandComplexSchemas", + "links": [ + { + "rel": "dtv:naming", + "href": "./CommandComplexSchemas.SchemaNames.json", + "type": "application/json" + } + ], + "schemaDefinitions": { + "WhenNow": { + "description": "Indicates that the action should be performed immediately.", + "type": "integer", + "const": 1 + }, + "WhenLater": { + "description": "Indicates that the action should be performed after a deferral.", + "type": "integer", + "const": 2 + }, + "Hello": { + "type": "string", + "const": "Hello, World!" + }, + "Raining": { + "type": "boolean", + "const": true + }, + "Sunny": { + "type": "boolean", + "const": false + } + }, + "actions": { + "doSomething": { + "input": { + "title": "DoSomethingRequestPayload", + "type": "object", + "required": [ "input" ], + "properties": { + "input": { + "type": "object", + "title": "DoSomethingRequestSchema", + "properties": { + "actions": { + "type": "array", + "items": { + "type": "string" + } + }, + "when": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + } + } + }, + "output": { + "title": "SomeResponseInfo", + "type": "object", + "description": "The result of the doSomething command.", + "properties": { + "details": { + "type": "object", + "dtv:additionalProperties": { + "title": "Results", + "type": "string", + "enum": [ + "success", + "failure" + ] + } + }, + "magnitude": { + "type": "string", + "pattern": "^(?:\\+|-)?(?:[1-9][0-9]*|0)(?:\\.[0-9]*)?$" + }, + "overallResult": { + "title": "Results", + "type": "string", + "enum": [ + "success", + "failure" + ] + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "samples/command/doSomething", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/eval/wot/CommandVariants.SchemaNames.json b/codegen2/eval/wot/CommandVariants.SchemaNames.json new file mode 100644 index 0000000000..a2b5d5600d --- /dev/null +++ b/codegen2/eval/wot/CommandVariants.SchemaNames.json @@ -0,0 +1,22 @@ +{ + "actionInSchema": { + "in": [ "actionName" ], + "out": "{actionName}RequestPayload", + "capitalize": true + }, + "actionOutSchema": { + "in": [ "actionName" ], + "out": "{actionName}ResponsePayload", + "capitalize": true + }, + "actionExecutorBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandExecutor", + "capitalize": true + }, + "actionInvokerBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandInvoker", + "capitalize": true + } +} diff --git a/codegen2/eval/wot/CommandVariants.TM.json b/codegen2/eval/wot/CommandVariants.TM.json new file mode 100644 index 0000000000..b8e04e10ee --- /dev/null +++ b/codegen2/eval/wot/CommandVariants.TM.json @@ -0,0 +1,97 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "CommandVariants", + "links": [ + { + "rel": "dtv:naming", + "href": "./CommandVariants.SchemaNames.json", + "type": "application/json" + } + ], + "actions": { + "noop": { + "idempotent": true, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "samples/command/noop/{executorId}", + "op": "invokeaction" + } + ] + }, + "peek": { + "output": { + "type": "object", + "required": [ "outVal" ], + "properties": { + "outVal": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + }, + "idempotent": true, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "samples/command/peek/{executorId}", + "dtv:serviceGroupId": "PeekCmdGroup", + "op": "invokeaction" + } + ] + }, + "poke": { + "input": { + "type": "object", + "required": [ "inVal" ], + "properties": { + "inVal": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "samples/command/poke/{executorId}", + "dtv:serviceGroupId": "PokeCmdGroup", + "op": "invokeaction" + } + ] + }, + "setColor": { + "input": { + "type": "object", + "required": [ "newColor" ], + "properties": { + "newColor": { + "type": "string" + } + } + }, + "output": { + "type": "object", + "required": [ "oldColor" ], + "properties": { + "oldColor": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "samples/command/setColor/{executorId}", + "op": "invokeaction" + } + ] + } + } +} diff --git a/codegen2/eval/wot/CounterCollection.SchemaNames.json b/codegen2/eval/wot/CounterCollection.SchemaNames.json new file mode 100644 index 0000000000..597f63c62e --- /dev/null +++ b/codegen2/eval/wot/CounterCollection.SchemaNames.json @@ -0,0 +1,27 @@ +{ + "actionInSchema": { + "in": [ "actionName" ], + "out": "{actionName}RequestPayload", + "capitalize": true + }, + "actionOutSchema": { + "in": [ "actionName" ], + "out": "{actionName}ResponsePayload", + "capitalize": true + }, + "actionExecutorBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandExecutor", + "capitalize": true + }, + "actionInvokerBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandInvoker", + "capitalize": true + }, + "actionRespErrorField": { + "in": [ "actionName", "errorSchemaName" ], + "out": "{errorSchemaName}", + "capitalize": false + } +} diff --git a/codegen2/eval/wot/CounterCollection.TM.json b/codegen2/eval/wot/CounterCollection.TM.json new file mode 100644 index 0000000000..f313fff7b6 --- /dev/null +++ b/codegen2/eval/wot/CounterCollection.TM.json @@ -0,0 +1,161 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "CounterCollection", + "links": [ + { + "rel": "dtv:naming", + "href": "./CounterCollection.SchemaNames.json", + "type": "application/json" + } + ], + "schemaDefinitions": { + "condition": { + "title": "ErrorCondition", + "type": "string", + "enum": [ + "NotFound", + "Overflow" + ] + }, + "errorCondition": { + "title": "ErrorCondition", + "type": "string", + "enum": [ + "NotFound", + "Overflow" + ] + }, + "availableCounters": { + "title": "CounterList", + "type": "object", + "properties": { + "counterNames": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "extantCounters": { + "title": "CounterList", + "type": "object", + "properties": { + "counterNames": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "locationError": { + "title": "CounterError", + "description": "The requested counter operation could not be completed.", + "type": "object", + "dtv:errorMessage": "explanation", + "properties": { + "explanation": { + "type": "string" + } + } + } + }, + "actions": { + "increment": { + "input": { + "type": "object", + "required": [ "counterName" ], + "properties": { + "counterName": { + "type": "string" + } + } + }, + "output": { + "type": "object", + "required": [ "counterValue" ], + "properties": { + "counterValue": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:headerInfo": [ + { + "contentType": "application/json", + "schema": "extantCounters" + } + ], + "dtv:headerCode": "errorCondition", + "dtv:topic": "test/CounterCollection/increment", + "op": "invokeaction" + } + ] + }, + "getLocation": { + "input": { + "type": "object", + "required": [ "counterName" ], + "properties": { + "counterName": { + "description": "Name of counter whose location to determine", + "type": "string" + } + } + }, + "output": { + "type": "object", + "properties": { + "counterLocation": { + "title": "CounterLocation", + "description": "Location of counter, null if not deployed", + "type": "object", + "required": [ "latitude", "longitude" ], + "properties": { + "latitude": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + }, + "longitude": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "locationError" + } + ], + "dtv:headerInfo": [ + { + "contentType": "application/json", + "schema": "availableCounters" + } + ], + "dtv:headerCode": "condition", + "dtv:topic": "test/CounterCollection/getLocation", + "op": "invokeaction" + } + ] + } + } +} diff --git a/codegen2/eval/wot/ExternalSchemas.TM.json b/codegen2/eval/wot/ExternalSchemas.TM.json new file mode 100644 index 0000000000..ee740dceef --- /dev/null +++ b/codegen2/eval/wot/ExternalSchemas.TM.json @@ -0,0 +1,143 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "ExternalSchemas", + "schemaDefinitions": { + "Foo": { + "description": "The requested property read/write could not be completed.", + "type": "object", + "properties": { + "explanation": { + "description": "The requested property read/write could not be completed.", + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "string" + } + } + }, + "Bar": { + "type": "array", + "items": { + "type": "string" + } + }, + "Baz": { + "type": "string", + "const": "Hello" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "test/PropertyTogether/read", + "additionalResponses": [ + { + "success": false + } + ], + "op": [ "readallproperties" ] + }, + { + "contentType": "application/json", + "dtv:topic": "test/PropertyTogether/write", + "additionalResponses": [ + { + "success": false + } + ], + "op": [ "writemultipleproperties" ] + } + ], + "actions": { + "DoSomething": { + "input": { + "dtv:ref": "./ExternalSchemas/DoSomethingInput.json" + }, + "output": { + "dtv:ref": "./ExternalSchemas/DoSomethingOuput.json" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/command/doSomething", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "Aleph": { + "dtv:ref": "./ExternalSchemas/AlephProp.json", + "readOnly": false, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/property/Alpha/{action}", + "additionalResponses": [ + { + "success": false, + "schema": "Foo" + } + ], + "op": [ "readproperty" ] + }, + { + "contentType": "application/json", + "dtv:topic": "sample/property/Alpha/{action}", + "additionalResponses": [ + { + "success": false, + "schema": "Foo" + } + ], + "op": [ "writeproperty" ] + } + ] + } + }, + "events": { + "Beth": { + "data": { + "dtv:ref": "./ExternalSchemas/BethData.json" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/Beth", + "op": "subscribeevent" + } + ] + }, + "Gimel": { + "data": { + "dtv:ref": "./ExternalSchemas/BethData.json#/definitions/Temperature" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/Gimel", + "op": "subscribeevent" + } + ] + }, + "Dalet": { + "data": { + "dtv:ref": "./ExternalSchemas/BethData.json#/definitions/org.opcfoundation.UA.DateTime" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/Dalet", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/eval/wot/ExternalSchemas.TypeNames.json b/codegen2/eval/wot/ExternalSchemas.TypeNames.json new file mode 100644 index 0000000000..09e72241e8 --- /dev/null +++ b/codegen2/eval/wot/ExternalSchemas.TypeNames.json @@ -0,0 +1,9 @@ +{ + "suppressTitles": false, + "nameRules": { + "^.*\\.([A-Za-z]+)$": "{1}", + "^.*/([A-Za-z]+)/([A-Za-z]+);i=([0-9]+)$": "{1}{2}{3}", + "^([A-Za-z][A-Za-z0-9]+)$": "{1}" + }, + "capitalizeCaptures": true +} diff --git a/codegen2/eval/wot/ExternalSchemas/AlephProp.json b/codegen2/eval/wot/ExternalSchemas/AlephProp.json new file mode 100644 index 0000000000..0e68901d78 --- /dev/null +++ b/codegen2/eval/wot/ExternalSchemas/AlephProp.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AlephProp", + "type": "object", + "additionalProperties": false, + "required": [ "epsilon", "zeta", "mu", "nu" ], + "properties": { + "epsilon": { + "description": "The 'epsilon' Field.", + "type": "string" + }, + "zeta": { + "description": "The 'zeta' Field.", + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "iota": { + "$ref": "./BethData.json#/definitions/HeaterState" + }, + "kappa": { + "$ref": "./BethData.json#/definitions/org.opcfoundation.UA.Int32" + }, + "lambda": { + "anyOf": [ + { "type": "null" }, + { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + ] + }, + "mu": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "type": "null" }, + { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + ] + } + }, + "nu": { + "type": "array", + "items": { + "anyOf": [ + { "type": "null" }, + { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + ] + } + } + } +} diff --git a/codegen2/eval/wot/ExternalSchemas/BethData.json b/codegen2/eval/wot/ExternalSchemas/BethData.json new file mode 100644 index 0000000000..1111c7eff4 --- /dev/null +++ b/codegen2/eval/wot/ExternalSchemas/BethData.json @@ -0,0 +1,125 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "BethDatata", + "type": "object", + "additionalProperties": false, + "required": [ "eta", "theta" ], + "properties": { + "eta": { + "description": "The 'eta' Field.", + "type": "string" + }, + "theta": { + "description": "The 'theta' Field.", + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "iota": { + "$ref": "#/definitions/org.opcfoundation.UA.DateTime" + }, + "kappa": { + "$ref": "#/definitions/Temperature" + }, + "lambda": { + "$ref": "#/definitions/HeaterState" + }, + "ManufacturerUri": { + "$ref": "#/definitions/nsu%3Dhttp%3A%2F%2Fmicrosoft.com%2FOpc%2FOpcPlc%2FBoiler%3Bi%3D6203" + }, + "ManufacturerUnencodedUri": { + "$ref": "#/definitions/nsu=http://microsoft.com/Opc/OpcPlc/Boiler;i=6203" + } + }, + "definitions": { + "nsu=http://microsoft.com/Opc/OpcPlc/Boiler;i=6203": { + "type": "object", + "title": "MicrosoftComOpcOpcPlcBoilerManufacturerUri", + "properties": { + "SourceTimestamp": { + "$ref": "#/definitions/org.opcfoundation.UA.DateTime" + }, + "Value": { + "$ref": "#/definitions/org.opcfoundation.UA.Int32" + }, + "StatusCode": { + "$ref": "#/definitions/org.opcfoundation.UA.Int32" + } + } + }, + "org.opcfoundation.UA.DateTime": { + "type": "string", + "title": "OpcUaDateTime", + "format": "date-time" + }, + "org.opcfoundation.UA.Int32": { + "type": "integer", + "title": "OpcUaInt32", + "format": "int32", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "Temperature": { + "type": "object", + "title": "Temperatura", + "properties": { + "Top": { + "$ref": "#/definitions/org.opcfoundation.UA.Int32" + }, + "Bottom": { + "$ref": "#/definitions/org.opcfoundation.UA.Int32" + }, + "ChillerState": { + "type": "object", + "additionalProperties": { + "type": "string", + "title": "ChillerState", + "enum": [ + "Off_0", + "On_1" + ] + } + }, + "GeoLocation": { + "type": "array", + "items": { + "type": "object", + "title": "Coordinates", + "additionalProperties": false, + "properties": { + "latitude": { + "type": "number", + "format": "double" + }, + "longitude": { + "type": "number", + "format": "double" + } + } + } + }, + "Something": { + "type": "object", + "additionalProperties": false, + "properties": { + "pressure": { + "type": "number", + "format": "double" + }, + "temperature": { + "$ref": "#/definitions/Temperature" + } + } + } + } + }, + "HeaterState": { + "type": "string", + "title": "HeaterState", + "enum": [ + "Off_0", + "On_1" + ] + } + } +} diff --git a/codegen2/eval/wot/ExternalSchemas/DoSomethingInput.json b/codegen2/eval/wot/ExternalSchemas/DoSomethingInput.json new file mode 100644 index 0000000000..6602997ab6 --- /dev/null +++ b/codegen2/eval/wot/ExternalSchemas/DoSomethingInput.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "DoSomethingInputArguments", + "description": "Input arguments for action 'DoSomething'", + "type": "object", + "additionalProperties": false, + "required": [ "alpha", "beta" ], + "properties": { + "alpha": { + "description": "The 'alpha' Field.", + "type": "string" + }, + "beta": { + "description": "The 'beta' Field.", + "type": "integer", "minimum": -2147483648, "maximum": 2147483647 + } + } +} diff --git a/codegen2/eval/wot/ExternalSchemas/DoSomethingOuput.json b/codegen2/eval/wot/ExternalSchemas/DoSomethingOuput.json new file mode 100644 index 0000000000..45a6a69fb7 --- /dev/null +++ b/codegen2/eval/wot/ExternalSchemas/DoSomethingOuput.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "DoSomethingOuput", + "description": "Output arguments for action 'DoSomething'", + "type": "object", + "additionalProperties": false, + "required": [ "gamma", "delta" ], + "properties": { + "delta": { + "description": "The 'delta' Field.", + "type": "integer", "minimum": -2147483648, "maximum": 2147483647 + }, + "gamma": { + "description": "The 'gamma' Field.", + "type": "string" + } + } +} diff --git a/codegen2/eval/wot/PropertySeparate.SchemaNames.json b/codegen2/eval/wot/PropertySeparate.SchemaNames.json new file mode 100644 index 0000000000..14b43a11d8 --- /dev/null +++ b/codegen2/eval/wot/PropertySeparate.SchemaNames.json @@ -0,0 +1,22 @@ +{ + "propMaintainerBinder": { + "in": [ "propSchema" ], + "out": "{propSchema}PropertyMaintainer", + "capitalize": true + }, + "propConsumerBinder": { + "in": [ "propSchema" ], + "out": "{propSchema}PropertyConsumer", + "capitalize": true + }, + "propReadRespErrorField": { + "in": [ "propName", "errorSchemaName" ], + "out": "propReadError", + "capitalize": false + }, + "propWriteRespErrorField": { + "in": [ "propName", "errorSchemaName" ], + "out": "propWriteError", + "capitalize": false + } +} diff --git a/codegen2/eval/wot/PropertySeparate.TM.json b/codegen2/eval/wot/PropertySeparate.TM.json new file mode 100644 index 0000000000..a065f1d78c --- /dev/null +++ b/codegen2/eval/wot/PropertySeparate.TM.json @@ -0,0 +1,183 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "PropertySeparate", + "links": [ + { + "rel": "dtv:naming", + "href": "./PropertySeparate.SchemaNames.json", + "type": "application/json" + } + ], + "schemaDefinitions": { + "PropertyError": { + "description": "The requested property read/write could not be completed.", + "type": "object", + "dtv:errorMessage": "explanation", + "properties": { + "explanation": { + "type": "string" + }, + "details": { + "type": "string" + } + } + }, + "MultiWriteErrorRef": { + "title": "MultiWriteError", + "description": "The requested multiple property write could not be completed.", + "type": "object", + "dtv:errorMessage": "explanation", + "properties": { + "explanation": { + "type": "string" + }, + "attemptedWriteCount": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "failedWriteCount": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + } + }, + "properties": { + "Alpha": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": true, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "test/PropertySeparate/Alpha/read", + "op": "readproperty" + } + ] + }, + "Beta": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": false, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "test/PropertySeparate/Beta/write", + "op": "writeproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "test/PropertySeparate/Beta/read", + "op": "readproperty" + } + ] + }, + "Gamma": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": true, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "PropertyError" + } + ], + "dtv:topic": "test/PropertySeparate/Gamma/read", + "op": "readproperty" + } + ] + }, + "Delta": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": false, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "PropertyError" + } + ], + "dtv:topic": "test/PropertySeparate/Delta/write", + "op": "writeproperty" + }, + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "PropertyError" + } + ], + "dtv:topic": "test/PropertySeparate/Delta/read", + "op": "readproperty" + } + ] + }, + "Zeta": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": false, + "dtv:placeholder": true, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "test/PropertySeparate/Zeta/write", + "op": "writeproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "test/PropertySeparate/Zeta/read", + "op": "readproperty" + } + ] + }, + "Eta": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": false, + "dtv:placeholder": true, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "MultiWriteErrorRef" + } + ], + "dtv:topic": "test/PropertySeparate/Eta/write", + "op": "writeproperty" + }, + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "PropertyError" + } + ], + "dtv:topic": "test/PropertySeparate/Eta/read", + "op": "readproperty" + } + ] + } + } +} diff --git a/codegen2/eval/wot/PropertyTogether.SchemaNames.json b/codegen2/eval/wot/PropertyTogether.SchemaNames.json new file mode 100644 index 0000000000..6256860571 --- /dev/null +++ b/codegen2/eval/wot/PropertyTogether.SchemaNames.json @@ -0,0 +1,21 @@ +{ + "aggregatePropName": "Properties", + "aggregateReadRespValueField": "Properties", + "aggregateRespErrorField": "Errors", + "propReadActName": { + "in": [ "propName" ], + "out": "ReadPropertyCollection" + }, + "propWriteActName": { + "in": [ "propName" ], + "out": "WritePropertyCollection" + }, + "propMaintainerBinder": { + "in": [ "propSchema" ], + "out": "PropertyMaintainer" + }, + "propConsumerBinder": { + "in": [ "propSchema" ], + "out": "PropertyConsumer" + } +} diff --git a/codegen2/eval/wot/PropertyTogether.TM.json b/codegen2/eval/wot/PropertyTogether.TM.json new file mode 100644 index 0000000000..5e24e56fba --- /dev/null +++ b/codegen2/eval/wot/PropertyTogether.TM.json @@ -0,0 +1,185 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "PropertyTogether", + "links": [ + { + "rel": "dtv:naming", + "href": "./PropertyTogether.SchemaNames.json", + "type": "application/json" + } + ], + "schemaDefinitions": { + "PropertyError": { + "description": "The requested property read/write could not be completed.", + "type": "object", + "dtv:errorMessage": "explanation", + "properties": { + "explanation": { + "type": "string" + }, + "details": { + "type": "string" + } + } + }, + "MultiWriteErrorRef": { + "title": "MultiWriteError", + "description": "The requested multiple property write could not be completed.", + "type": "object", + "dtv:errorMessage": "explanation", + "properties": { + "explanation": { + "type": "string" + }, + "attemptedWriteCount": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "failedWriteCount": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false + } + ], + "dtv:topic": "test/PropertyTogether/write", + "op": "writemultipleproperties" + }, + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false + } + ], + "dtv:topic": "test/PropertyTogether/read", + "op": "readallproperties" + } + ], + "properties": { + "Alpha": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": true, + "forms": [ + { + "op": "readproperty" + } + ] + }, + "Beta": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": false, + "forms": [ + { + "op": "writeproperty" + }, + { + "op": "readproperty" + } + ] + }, + "Gamma": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": true, + "forms": [ + { + "additionalResponses": [ + { + "success": false, + "schema": "PropertyError" + } + ], + "op": "readproperty" + } + ] + }, + "Delta": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": false, + "forms": [ + { + "additionalResponses": [ + { + "success": false, + "schema": "PropertyError" + } + ], + "op": "writeproperty" + }, + { + "additionalResponses": [ + { + "success": false, + "schema": "PropertyError" + } + ], + "op": "readproperty" + } + ] + }, + "Zeta": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": false, + "dtv:placeholder": true, + "forms": [ + { + "op": "writeproperty" + }, + { + "op": "readproperty" + } + ] + }, + "Eta": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": false, + "dtv:placeholder": true, + "forms": [ + { + "additionalResponses": [ + { + "success": false, + "schema": "MultiWriteErrorRef" + } + ], + "op": "writeproperty" + }, + { + "additionalResponses": [ + { + "success": false, + "schema": "PropertyError" + } + ], + "op": "readproperty" + } + ] + } + } +} diff --git a/codegen2/eval/wot/TelemetryAndCommand.TM.json b/codegen2/eval/wot/TelemetryAndCommand.TM.json new file mode 100644 index 0000000000..097ddc435c --- /dev/null +++ b/codegen2/eval/wot/TelemetryAndCommand.TM.json @@ -0,0 +1,92 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "TelemetryAndCommand", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{modelId}/{senderId}/telemetry", + "op": "subscribeallevents" + } + ], + "actions": { + "setColor": { + "input": { + "type": "object", + "required": [ "newColor" ], + "properties": { + "newColor": { + "type": "string" + } + } + }, + "output": { + "type": "object", + "required": [ "oldColor" ], + "properties": { + "oldColor": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:serviceGroupId": "MyCommandGroup", + "dtv:topic": "sample/{modelId}/command/setColor", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + "distance": { + "descriptions": { + "en": "The total distance from the origin." + }, + "data": { + "title": "Example", + "type": "object", + "properties": { + "latitude": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + }, + "longitude": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:serviceGroupId": "MyTelemetryGroup", + "op": "subscribeevent" + } + ] + }, + "color": { + "descriptions": { + "en": "The color currently being applied." + }, + "data": { + "dtv:ref": "Example1.json" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:serviceGroupId": "MyTelemetryGroup", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/eval/wot/TelemetryComplexSchemas.SchemaNames.json b/codegen2/eval/wot/TelemetryComplexSchemas.SchemaNames.json new file mode 100644 index 0000000000..71d9faab95 --- /dev/null +++ b/codegen2/eval/wot/TelemetryComplexSchemas.SchemaNames.json @@ -0,0 +1,17 @@ +{ + "aggregateEventName": "Telemetry", + "aggregateEventSchema": "TelemetryCollection", + "eventValueSchema": { + "in": [ "eventName" ], + "out": "{eventName}Schema", + "capitalize": true + }, + "eventSenderBinder": { + "in": [ "eventSchema" ], + "out": "TelemetrySender" + }, + "eventReceiverBinder": { + "in": [ "eventSchema" ], + "out": "TelemetryReceiver" + } +} diff --git a/codegen2/eval/wot/TelemetryComplexSchemas.TM.json b/codegen2/eval/wot/TelemetryComplexSchemas.TM.json new file mode 100644 index 0000000000..b4e55aa41e --- /dev/null +++ b/codegen2/eval/wot/TelemetryComplexSchemas.TM.json @@ -0,0 +1,157 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "TelemetryComplexSchemas", + "links": [ + { + "rel": "dtv:naming", + "href": "./TelemetryComplexSchemas.SchemaNames.json", + "type": "application/json" + } + ], + "schemaDefinitions": { + "near": { + "type": "integer", + "const": 1 + }, + "far": { + "type": "integer", + "const": 2 + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry", + "dtv:serviceGroupId": "AggregTelemGroup", + "op": "subscribeallevents" + } + ], + "actions": { + }, + "properties": { + }, + "events": { + "coordinates": { + "data": { + "type": "object", + "properties": { + "latitude": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + }, + "longitude": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + } + } + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + }, + "doubleArray2D": { + "data": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + } + } + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + }, + "doubleMap": { + "data": { + "type": "object", + "dtv:additionalProperties": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + } + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + }, + "doubleMapArray": { + "data": { + "type": "object", + "dtv:additionalProperties": { + "type": "array", + "items": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + } + } + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + }, + "proximity": { + "data": { + "description": "An enumerated value for expressing qualitative distance.", + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + }, + "resultArray": { + "data": { + "type": "array", + "items": { + "title": "Results", + "type": "string", + "enum": [ + "success", + "failure" + ] + } + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + }, + "speed": { + "data": { + "type": "string", + "enum": [ + "slow", + "fast" + ] + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/eval/wot/TelemetryPrimitiveSchemas.SchemaNames.json b/codegen2/eval/wot/TelemetryPrimitiveSchemas.SchemaNames.json new file mode 100644 index 0000000000..3e93e188b3 --- /dev/null +++ b/codegen2/eval/wot/TelemetryPrimitiveSchemas.SchemaNames.json @@ -0,0 +1,22 @@ +{ + "eventSchema": { + "in": [ "eventName" ], + "out": "{eventName}Telemetry", + "capitalize": true + }, + "eventValueSchema": { + "in": [ "eventName" ], + "out": "{eventName}Schema", + "capitalize": true + }, + "eventSenderBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}TelemetrySender", + "capitalize": true + }, + "eventReceiverBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}TelemetryReceiver", + "capitalize": true + } +} diff --git a/codegen2/eval/wot/TelemetryPrimitiveSchemas.TM.json b/codegen2/eval/wot/TelemetryPrimitiveSchemas.TM.json new file mode 100644 index 0000000000..f42b2bce34 --- /dev/null +++ b/codegen2/eval/wot/TelemetryPrimitiveSchemas.TM.json @@ -0,0 +1,179 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "TelemetryPrimitiveSchemas", + "links": [ + { + "rel": "dtv:naming", + "href": "./TelemetryPrimitiveSchemas.SchemaNames.json", + "type": "application/json" + } + ], + "actions": { + }, + "properties": { + }, + "events": { + "endDate": { + "data": { + "type": "string", + "format": "date" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/endDate", + "op": "subscribeevent" + } + ] + }, + "startDateTime": { + "data": { + "type": "string", + "format": "date-time" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/startDateTime", + "op": "subscribeevent" + } + ] + }, + "targetTime": { + "data": { + "type": "string", + "format": "time" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/targetTime", + "op": "subscribeevent" + } + ] + }, + "allottedDuration": { + "data": { + "type": "string", + "pattern": "^P(?!$)(?:(?:(?:(?:\\d+Y)|(?:\\d+\\.\\d+Y$))?(?:(?:\\d+M)|(?:\\d+\\.\\d+M$))?)|(?:(?:(?:\\d+W)|(?:\\d+\\.\\d+W$))?))(?:(?:\\d+D)|(?:\\d+\\.\\d+D$))?(?:T(?!$)(?:(?:\\d+H)|(?:\\d+\\.\\d+H$))?(?:(?:\\d+M)|(?:\\d+\\.\\d+M$))?(?:\\d+(?:\\.\\d+)?S)?)?$" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/allottedDuration", + "op": "subscribeevent" + } + ] + }, + "identifier": { + "data": { + "type": "string", + "format": "uuid" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/identifier", + "op": "subscribeevent" + } + ] + }, + "data": { + "data": { + "type": "string", + "contentEncoding": "base64" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/data", + "op": "subscribeevent" + } + ] + }, + "flipflop": { + "data": { + "type": "boolean" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/flipflop", + "op": "subscribeevent" + } + ] + }, + "absoluteDistance": { + "data": { + "type": "number", + "minimum": -1.80e+308, + "maximum": 1.80e+308 + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/absoluteDistance", + "op": "subscribeevent" + } + ] + }, + "relativeDistance": { + "data": { + "type": "number", + "minimum": -3.40e+38, + "maximum": 3.40e+38 + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/relativeDistance", + "op": "subscribeevent" + } + ] + }, + "count": { + "data": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/count", + "op": "subscribeevent" + } + ] + }, + "totalCount": { + "data": { + "type": "integer", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807 + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/totalCount", + "op": "subscribeevent" + } + ] + }, + "notes": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry/notes", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/eval/wot/ThingOne.SchemaNames.json b/codegen2/eval/wot/ThingOne.SchemaNames.json new file mode 100644 index 0000000000..2222caab41 --- /dev/null +++ b/codegen2/eval/wot/ThingOne.SchemaNames.json @@ -0,0 +1,14 @@ +{ + "aggregateEventName": "EventsOne", + "aggregateEventSchema": "EventCollectionOne", + "eventSchema": { + "in": [ "eventName" ], + "out": "{eventName}EventOne", + "capitalize": true + }, + "eventValueSchema": { + "in": [ "eventName" ], + "out": "EventOne{eventName}Value", + "capitalize": true + } +} diff --git a/codegen2/eval/wot/ThingTwo.SchemaNames.json b/codegen2/eval/wot/ThingTwo.SchemaNames.json new file mode 100644 index 0000000000..01bf2d9b3d --- /dev/null +++ b/codegen2/eval/wot/ThingTwo.SchemaNames.json @@ -0,0 +1,14 @@ +{ + "aggregateEventName": "EventsTwo", + "aggregateEventSchema": "EventCollectionTwo", + "eventSchema": { + "in": [ "eventName" ], + "out": "{eventName}EventTwo", + "capitalize": true + }, + "eventValueSchema": { + "in": [ "eventName" ], + "out": "EventTwo{eventName}Value", + "capitalize": true + } +} diff --git a/codegen2/eval/wot/TwoThings.TM.json b/codegen2/eval/wot/TwoThings.TM.json new file mode 100644 index 0000000000..e6b7812252 --- /dev/null +++ b/codegen2/eval/wot/TwoThings.TM.json @@ -0,0 +1,98 @@ +[ + { + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "ThingOne", + "links": [ + { + "rel": "dtv:naming", + "href": "./ThingOne.SchemaNames.json", + "type": "application/json" + } + ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry1", + "op": "subscribeallevents" + } + ], + "actions": { + }, + "properties": { + }, + "events": { + "Alpha": { + "data": { + "type": "string" + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + }, + "Beta": { + "data": { + "type": "string" + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + } + } + }, + { + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "ThingTwo", + "links": [ + { + "rel": "dtv:naming", + "href": "./ThingTwo.SchemaNames.json", + "type": "application/json" + } + ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry2", + "op": "subscribeallevents" + } + ], + "actions": { + }, + "properties": { + }, + "events": { + "Gamma": { + "data": { + "type": "string" + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + }, + "Beta": { + "data": { + "type": "string" + }, + "forms": [ + { + "op": "subscribeevent" + } + ] + } + } + } +] diff --git a/codegen2/res/conversion/SchemaNames.json b/codegen2/res/conversion/SchemaNames.json new file mode 100644 index 0000000000..19c8363589 --- /dev/null +++ b/codegen2/res/conversion/SchemaNames.json @@ -0,0 +1,52 @@ +{ + "aggregateEventName": "Telemetry", + "aggregateEventSchema": "TelemetryCollection", + "aggregatePropName": "Property", + "aggregateReadRespValueField": "Properties", + "aggregateRespErrorField": "Errors", + "eventSchema": { + "in": [ "eventName" ], + "out": "{eventName}Telemetry", + "capitalize": true + }, + "eventValueSchema": { + "in": [ "eventName" ], + "out": "{eventName}Schema", + "capitalize": true + }, + "eventSenderBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Sender", + "capitalize": true + }, + "eventReceiverBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Receiver", + "capitalize": true + }, + "actionInSchema": { + "in": [ "actionName" ], + "out": "{actionName}RequestPayload", + "capitalize": true + }, + "actionOutSchema": { + "in": [ "actionName" ], + "out": "{actionName}ResponsePayload", + "capitalize": true + }, + "actionExecutorBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandExecutor", + "capitalize": true + }, + "actionInvokerBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandInvoker", + "capitalize": true + }, + "actionRespErrorField": { + "in": [ "actionName", "errorSchemaName" ], + "out": "{actionName}Error", + "capitalize": false + } +} diff --git a/codegen2/res/rust/AVRO/common_types.rs b/codegen2/res/rust/AVRO/common_types.rs new file mode 100644 index 0000000000..ed2a18d83a --- /dev/null +++ b/codegen2/res/rust/AVRO/common_types.rs @@ -0,0 +1,7 @@ +/* This file will be copied into the folder for generated code. */ +pub mod b64; +pub mod date_only; +pub mod decimal; +pub mod empty_avro; +pub mod options; +pub mod time_only; diff --git a/codegen2/res/rust/AVRO/common_types/b64.rs b/codegen2/res/rust/AVRO/common_types/b64.rs new file mode 100644 index 0000000000..66a3fe1d2f --- /dev/null +++ b/codegen2/res/rust/AVRO/common_types/b64.rs @@ -0,0 +1,43 @@ +/* This file will be copied into the folder for generated code. */ + +use std::ops::{Deref, DerefMut}; + +use base64::prelude::*; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; +use serde_bytes; + +#[derive(Clone, Debug)] +pub struct Bytes(pub Vec); + +impl Deref for Bytes { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Bytes { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Serialize for Bytes { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + s.serialize_bytes(&self.0) + } +} + +impl<'de> Deserialize<'de> for Bytes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: Vec = serde_bytes::deserialize(deserializer)?; + Ok(Bytes(s)) + } +} diff --git a/codegen2/res/rust/AVRO/common_types/empty_avro.rs b/codegen2/res/rust/AVRO/common_types/empty_avro.rs new file mode 100644 index 0000000000..c12f0393a8 --- /dev/null +++ b/codegen2/res/rust/AVRO/common_types/empty_avro.rs @@ -0,0 +1,45 @@ +/* This file will be copied into the folder for generated code. */ + +use std::io::Cursor; + +use apache_avro; +use azure_iot_operations_protocol::common::payload_serialize::{ + DeserializationError, FormatIndicator, PayloadSerialize, SerializedPayload, +}; +use lazy_static; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct EmptyAvro { +} + +impl PayloadSerialize for EmptyAvro{ + type Error = String; + + fn serialize(self) -> Result { + Ok(SerializedPayload { + payload: apache_avro::to_avro_datum(&SCHEMA, apache_avro::to_value(self).unwrap()).unwrap(), + content_type: "application/avro".to_string(), + format_indicator: FormatIndicator::UnspecifiedBytes, + }) + } + + fn deserialize( + payload: &[u8], + _content_type: Option<&String>, + _format_indicator: &FormatIndicator, + ) -> Result> { + Ok(apache_avro::from_value(&apache_avro::from_avro_datum(&SCHEMA, &mut Cursor::new(payload), None).unwrap()).unwrap()) + } +} + +lazy_static::lazy_static! { pub static ref SCHEMA: apache_avro::Schema = apache_avro::Schema::parse_str(RAW_SCHEMA).unwrap(); } + +const RAW_SCHEMA: &str = r#" +{ + "namespace": "resources.common_types", + "name": "EmptyAvro", + "type": "record", + "fields": [] +} +"#; diff --git a/codegen2/res/rust/JSON/common_types.rs b/codegen2/res/rust/JSON/common_types.rs new file mode 100644 index 0000000000..6e6592aa9f --- /dev/null +++ b/codegen2/res/rust/JSON/common_types.rs @@ -0,0 +1,7 @@ +/* This file will be copied into the folder for generated code. */ +pub mod b64; +pub mod date_only; +pub mod decimal; +pub mod empty_json; +pub mod options; +pub mod time_only; diff --git a/codegen2/res/rust/JSON/common_types/b64.rs b/codegen2/res/rust/JSON/common_types/b64.rs new file mode 100644 index 0000000000..66453d9ccf --- /dev/null +++ b/codegen2/res/rust/JSON/common_types/b64.rs @@ -0,0 +1,44 @@ +/* This file will be copied into the folder for generated code. */ + +use std::ops::{Deref, DerefMut}; + +use base64::prelude::*; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; + +#[derive(Clone, Debug)] +pub struct Bytes(pub Vec); + +impl Deref for Bytes { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Bytes { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Serialize for Bytes { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + s.serialize_str(BASE64_STANDARD.encode(&self.0).as_str()) + } +} + +impl<'de> Deserialize<'de> for Bytes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + Ok(Bytes( + BASE64_STANDARD.decode(&s).map_err(de::Error::custom)?, + )) + } +} diff --git a/codegen2/res/rust/JSON/common_types/empty_json.rs b/codegen2/res/rust/JSON/common_types/empty_json.rs new file mode 100644 index 0000000000..755086513d --- /dev/null +++ b/codegen2/res/rust/JSON/common_types/empty_json.rs @@ -0,0 +1,28 @@ +/* This file will be copied into the folder for generated code. */ + +use azure_iot_operations_protocol::common::payload_serialize::{ + DeserializationError, FormatIndicator, PayloadSerialize, SerializedPayload, +}; + +#[derive(Debug, Clone)] +pub struct EmptyJson {} + +impl PayloadSerialize for EmptyJson { + type Error = String; + + fn serialize(self) -> Result { + Ok(SerializedPayload { + payload: "".as_bytes().to_owned(), + content_type: "application/json".to_string(), + format_indicator: FormatIndicator::Utf8EncodedCharacterData, + }) + } + + fn deserialize( + _payload: &[u8], + _content_type: Option<&String>, + _format_indicator: &FormatIndicator, + ) -> Result> { + Ok(Self {}) + } +} diff --git a/codegen2/res/rust/common/common_types.rs b/codegen2/res/rust/common/common_types.rs new file mode 100644 index 0000000000..2c16445370 --- /dev/null +++ b/codegen2/res/rust/common/common_types.rs @@ -0,0 +1,5 @@ +/* This file will be copied into the folder for generated code. */ +pub mod date_only; +pub mod decimal; +pub mod options; +pub mod time_only; diff --git a/codegen2/res/rust/common/common_types/date_only.rs b/codegen2/res/rust/common/common_types/date_only.rs new file mode 100644 index 0000000000..cdabee9cab --- /dev/null +++ b/codegen2/res/rust/common/common_types/date_only.rs @@ -0,0 +1,68 @@ +/* This file will be copied into the folder for generated code. */ + +use std::ops::{Deref, DerefMut}; + +use chrono::{TimeZone, Utc}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de, ser}; +use time::{self, format_description::well_known::Rfc3339}; + +#[derive(Clone, Debug)] +pub struct Date(time::Date); + +impl Deref for Date { + type Target = time::Date; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Date { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Serialize for Date { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + s.serialize_str(date_to_rfc3339(self).map_err(ser::Error::custom)?.as_str()) + } +} + +impl<'de> Deserialize<'de> for Date { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + rfc3339_to_date(&s).map_err(de::Error::custom) + } +} + +fn date_to_rfc3339(date: &Date) -> Result { + let date_time = Utc + .with_ymd_and_hms( + date.year(), + date.month() as u32, + u32::from(date.day()), + 0, + 0, + 0, + ) + .unwrap(); + let date_time_string = date_time.to_rfc3339(); + let t_ix = date_time_string + .find('T') + .ok_or("error serializing Date to RFC 3339 format")?; + Ok((date_time_string[..t_ix]).to_string()) +} + +fn rfc3339_to_date(date_str: &str) -> Result { + Ok(Date( + time::Date::parse(format!("{date_str}T00:00:00Z").as_str(), &Rfc3339) + .map_err(|_| "error deserializing Date from RFC 3339 format")?, + )) +} diff --git a/codegen2/res/rust/common/common_types/decimal.rs b/codegen2/res/rust/common/common_types/decimal.rs new file mode 100644 index 0000000000..bbd88ef785 --- /dev/null +++ b/codegen2/res/rust/common/common_types/decimal.rs @@ -0,0 +1,45 @@ +/* This file will be copied into the folder for generated code. */ + +use std::ops::{Deref, DerefMut}; +use std::str::FromStr; + +use bigdecimal; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; + +#[derive(Clone, Debug)] +pub struct Decimal(bigdecimal::BigDecimal); + +impl Deref for Decimal { + type Target = bigdecimal::BigDecimal; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Decimal { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Serialize for Decimal { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + s.serialize_str(self.to_string().as_str()) + } +} + +impl<'de> Deserialize<'de> for Decimal { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + Ok(Decimal( + bigdecimal::BigDecimal::from_str(&s).map_err(de::Error::custom)?, + )) + } +} diff --git a/codegen2/res/rust/common/common_types/options.rs b/codegen2/res/rust/common/common_types/options.rs new file mode 100644 index 0000000000..7b5de1594e --- /dev/null +++ b/codegen2/res/rust/common/common_types/options.rs @@ -0,0 +1,62 @@ +/* This file will be copied into the folder for generated code. */ + +use std::collections::HashMap; + +use derive_builder::Builder; + +#[allow(unused)] +#[derive(Builder, Clone)] +pub struct CommandExecutorOptions { + /// Optional Topic namespace to be prepended to the topic pattern + #[builder(default = "None")] + pub topic_namespace: Option, + /// Topic token keys/values to be replaced in the topic pattern + #[builder(default)] + pub topic_token_map: HashMap, +} + +#[allow(unused)] +#[derive(Builder, Clone)] +pub struct CommandInvokerOptions { + /// Optional Topic namespace to be prepended to the topic pattern + #[builder(default = "None")] + pub topic_namespace: Option, + /// Topic token keys/values to be replaced in the topic pattern + #[builder(default)] + pub topic_token_map: HashMap, + /// Prefix for the response topic. + /// If all response topic options are `None`, the response topic will be generated + /// based on the request topic in the form: `clients//` + #[builder(default = "None")] + pub response_topic_prefix: Option, + /// Suffix for the response topic. + /// If all response topic options are `None`, the response topic will be generated + /// based on the request topic in the form: `clients//` + #[builder(default = "None")] + pub response_topic_suffix: Option, +} + +#[allow(unused)] +#[derive(Builder, Clone)] +pub struct TelemetryReceiverOptions { + /// Optional Topic namespace to be prepended to the topic pattern + #[builder(default = "None")] + pub topic_namespace: Option, + /// Topic token keys/values to be replaced in the topic pattern + #[builder(default)] + pub topic_token_map: HashMap, + /// If true, telemetry messages are auto-acknowledged when received + #[builder(default = "true")] + pub auto_ack: bool, +} + +#[allow(unused)] +#[derive(Builder, Clone)] +pub struct TelemetrySenderOptions { + /// Optional Topic namespace to be prepended to the topic pattern + #[builder(default = "None")] + pub topic_namespace: Option, + /// Topic token keys/values to be replaced in the topic pattern + #[builder(default)] + pub topic_token_map: HashMap, +} diff --git a/codegen2/res/rust/common/common_types/time_only.rs b/codegen2/res/rust/common/common_types/time_only.rs new file mode 100644 index 0000000000..2de7500629 --- /dev/null +++ b/codegen2/res/rust/common/common_types/time_only.rs @@ -0,0 +1,74 @@ +/* This file will be copied into the folder for generated code. */ + +use std::ops::{Deref, DerefMut}; + +use chrono::{TimeZone, Utc}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de, ser}; +use time::{self, format_description::well_known::Rfc3339}; + +#[derive(Clone, Debug)] +pub struct Time(time::Time); + +impl Deref for Time { + type Target = time::Time; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Time { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Serialize for Time { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + s.serialize_str(time_to_rfc3339(self).map_err(ser::Error::custom)?.as_str()) + } +} + +impl<'de> Deserialize<'de> for Time { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = String::deserialize(deserializer)?; + rfc3339_to_time(&s).map_err(de::Error::custom) + } +} + +fn time_to_rfc3339(time: &Time) -> Result { + let date_time = Utc + .with_ymd_and_hms( + 0, + 1, + 1, + u32::from(time.hour()), + u32::from(time.minute()), + u32::from(time.second()), + ) + .unwrap(); + let date_time_string = date_time.to_rfc3339(); + let t_ix = date_time_string + .find('T') + .ok_or("error serializing Time to RFC 3339 format")?; + let plus_ix = date_time_string + .find('+') + .ok_or("error serializing Time to RFC 3339 format")?; + let time_str = &date_time_string[t_ix + 1..plus_ix]; + let mut time_string = time_str.to_string(); + time_string.push('Z'); + Ok(time_string) +} + +fn rfc3339_to_time(time_str: &str) -> Result { + Ok(Time( + time::Time::parse(format!("0000-01-01T{time_str}").as_str(), &Rfc3339) + .map_err(|_| "error deserializing Time from RFC 3339 format")?, + )) +} diff --git a/codegen2/res/rust/custom/common_types.rs b/codegen2/res/rust/custom/common_types.rs new file mode 100644 index 0000000000..c8c8466eaf --- /dev/null +++ b/codegen2/res/rust/custom/common_types.rs @@ -0,0 +1,3 @@ +/* This file will be copied into the folder for generated code. */ +pub mod custom_payload; +pub mod options; diff --git a/codegen2/res/rust/custom/common_types/custom_payload.rs b/codegen2/res/rust/custom/common_types/custom_payload.rs new file mode 100644 index 0000000000..7877d6d6f5 --- /dev/null +++ b/codegen2/res/rust/custom/common_types/custom_payload.rs @@ -0,0 +1,6 @@ +/* This file will be copied into the folder for generated code. */ + +use azure_iot_operations_protocol::common::payload_serialize::BypassPayload; + +/// A provided convenience struct for data that is externally serialized via custom code. +pub type CustomPayload = BypassPayload; diff --git a/codegen2/res/rust/raw/common_types.rs b/codegen2/res/rust/raw/common_types.rs new file mode 100644 index 0000000000..680494dd9e --- /dev/null +++ b/codegen2/res/rust/raw/common_types.rs @@ -0,0 +1,2 @@ +/* This file will be copied into the folder for generated code. */ +pub mod options; diff --git a/codegen2/schema/aio-tm-json-schema.json b/codegen2/schema/aio-tm-json-schema.json new file mode 100644 index 0000000000..979c1e95c4 --- /dev/null +++ b/codegen2/schema/aio-tm-json-schema.json @@ -0,0 +1,1339 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Thing Description with AIO Protocol Binding", + "version": "0.1", + "description": "JSON Schema for validating TD instances that employ the AIO WoT Protocol Binding", + "definitions": { + "title": { + "type": "string", + "pattern": "^[A-Z][A-Za-z0-9]*$", + "description": "The 'title' value is used as a codegen type name, so it should start with an uppercase letter and contain only alphanumeric characters" + }, + "dataSchema_boolean": { + "type": "object", + "properties": { + "title": { + "$ref": "#/definitions/title" + }, + "aov:namespace": { + "type": "string" + }, + "type": { + "type": "string", + "const": "boolean" + }, + "const": { + "type": "boolean" + } + } + }, + "dataSchema_integer": { + "type": "object", + "properties": { + "title": { + "$ref": "#/definitions/title" + }, + "aov:namespace": { + "type": "string" + }, + "type": { + "type": "string", + "const": "integer" + }, + "const": { + "type": "integer" + }, + "aov:scaleFactor": { + "description": "scale factor to apply to instance values", + "type": "integer" + }, + "aov:decimalPlaces": { + "description": "number of decimal places of significant precision of instance values", + "type": "integer" + }, + "minimum": { + "type": "integer", + "description": "inclusive minimum integer value", + "defaultSnippets": [ + { + "description": "minimum unsigned value", + "body": 0 + }, + { + "description": "minimum signed 1-byte value", + "body": -128 + }, + { + "description": "minimum signed 2-byte value", + "body": -32768 + }, + { + "description": "minimum signed 4-byte value", + "body": -2147483648 + }, + { + "description": "minimum signed 8-byte value", + "body": -9223372036854775808 + } + ] + }, + "maximum": { + "type": "integer", + "description": "inclusive maximum integer value", + "defaultSnippets": [ + { + "description": "maximum unsigned 1-byte value", + "body": 256 + }, + { + "description": "maximum unsigned 2-byte value", + "body": 65535 + }, + { + "description": "maximum unsigned 4-byte value", + "body": 4294967295 + }, + { + "description": "maximum unsigned 8-byte value", + "body": 9223372036854775807 + }, + { + "description": "maximum signed 1-byte value", + "body": 127 + }, + { + "description": "maximum signed 2-byte value", + "body": 32767 + }, + { + "description": "maximum signed 4-byte value", + "body": 2147483647 + }, + { + "description": "maximum signed 8-byte value", + "body": 18446744073709551615 + } + ] + } + } + }, + "dataSchema_number": { + "type": "object", + "properties": { + "title": { + "$ref": "#/definitions/title" + }, + "aov:namespace": { + "type": "string" + }, + "type": { + "type": "string", + "const": "number" + }, + "const": { + "type": "number" + }, + "aov:scaleFactor": { + "description": "scale factor to apply to instance values", + "type": "number" + }, + "aov:decimalPlaces": { + "description": "number of decimal places of significant precision of instance values", + "type": "integer" + }, + "minimum": { + "type": "number", + "description": "inclusive minimum numeric value", + "defaultSnippets": [ + { + "description": "minimum single-precision floating-point value", + "body": -3.40e+38 + }, + { + "description": "minimum double-precision floating-point value", + "body": -1.70e+308 + } + ] + }, + "maximum": { + "type": "number", + "description": "inclusive maximum numeric value", + "defaultSnippets": [ + { + "description": "maximum single-precision floating-point value", + "body": 3.40e+38 + }, + { + "description": "maximum double-precision floating-point value", + "body": 1.70e+308 + } + ] + } + } + }, + "dataSchema_string": { + "type": "object", + "properties": { + "title": { + "$ref": "#/definitions/title" + }, + "aov:namespace": { + "type": "string" + }, + "type": { + "type": "string", + "const": "string" + }, + "aov:typeRef": { + "description": "opaque reference to another type definition (not necessarily in WoT) that is congruent to this definition", + "type": "string" + }, + "enum": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string", + "enum": [ "date-time", "date", "time", "uuid" ] + }, + "pattern": { + "type": "string", + "description": "regular expression that expresses either an ISO 8601 duration or a decimal string", + "defaultSnippets": [ + { + "description": "ISO 8601 duration pattern", + "body": "^P(\\\\\\\\d+Y)?(\\\\\\\\d+M)?(\\\\\\\\d+W)?(\\\\\\\\d+D)?(T(\\\\\\\\d+H)?(\\\\\\\\d+M)?(\\\\\\\\d+S)?)?$" + }, + { + "description": "decimal string pattern", + "body": "^(?:\\\\\\\\+|-)?(?:[1-9][0-9]*|0)(?:\\\\\\\\.[0-9]*)?$" + } + ] + }, + "contentEncoding": { + "type": "string", + "const": "base64" + }, + "const": { + "type": "string" + } + }, + "dependentRequired": { + "aov:typeRef": [ "enum" ] + } + }, + "dataSchema_object": { + "type": "object", + "required": [ "properties" ], + "properties": { + "title": { + "$ref": "#/definitions/title" + }, + "aov:namespace": { + "type": "string" + }, + "type": { + "type": "string", + "const": "object" + }, + "aov:typeRef": { + "description": "opaque reference to another type definition (not necessarily in WoT) that is congruent to this structured object definition", + "type": "string" + }, + "properties": { + "description": "use 'properties' to indicate an object type and define fields for the object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "description": "array of 'properties' keys that indicate properties required to be included in any instance of the object", + "type": "array", + "items": { + "type": "string" + } + } + }, + "not": { + "anyOf": [ + { + "required": [ "dtv:additionalProperties" ] + } + ] + } + }, + "dataSchema_map": { + "type": "object", + "required": [ "dtv:additionalProperties" ], + "properties": { + "title": { + "$ref": "#/definitions/title" + }, + "aov:namespace": { + "type": "string" + }, + "type": { + "type": "string", + "const": "object" + }, + "aov:typeRef": { + "description": "opaque reference to another type definition (not necessarily in WoT) that is congruent to this map definition", + "type": "string" + }, + "dtv:additionalProperties": { + "description": "use 'dtv:additionalProperties' to indicate a map type and specify the data schema for the values therein", + "$ref": "#/definitions/dataSchema" + } + }, + "not": { + "anyOf": [ + { + "required": [ "properties" ] + }, + { + "required": [ "required" ] + } + ] + } + }, + "dataSchema_array": { + "type": "object", + "required": [ "items" ], + "properties": { + "title": { + "$ref": "#/definitions/title" + }, + "aov:namespace": { + "type": "string" + }, + "type": { + "type": "string", + "const": "array" + }, + "aov:typeRef": { + "description": "opaque reference to another type definition (not necessarily in WoT) that is congruent to this array definition", + "type": "string" + }, + "items": { + "description": "use 'items' to specify the data schema for the values in the array", + "$ref": "#/definitions/dataSchema" + } + } + }, + "dataSchema_base": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/dataSchema_boolean" }, + { "$ref": "#/definitions/dataSchema_integer" }, + { "$ref": "#/definitions/dataSchema_number" }, + { "$ref": "#/definitions/dataSchema_string" }, + { "$ref": "#/definitions/dataSchema_object" }, + { "$ref": "#/definitions/dataSchema_map" }, + { "$ref": "#/definitions/dataSchema_array" } + ], + "defaultSnippets": [ + { + "description": "Boolean type", + "body": { + "type": "boolean" + } + }, + { + "description": "signed 1-byte type", + "body": { + "type": "integer", + "minimum": -128, + "maximum": 127 + } + }, + { + "description": "unsigned 1-byte type", + "body": { + "type": "integer", + "minimum": 0, + "maximum": 255 + } + }, + { + "description": "signed 2-byte type", + "body": { + "type": "integer", + "minimum": -32768, + "maximum": 32767 + } + }, + { + "description": "unsigned 2-byte type", + "body": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + } + }, + { + "description": "signed 4-byte type", + "body": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + }, + { + "description": "unsigned 4-byte type", + "body": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + }, + { + "description": "signed 8-byte type", + "body": { + "type": "integer", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807 + } + }, + { + "description": "unsigned 8-byte type", + "body": { + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + }, + { + "description": "single-precision floating-point type", + "body": { + "type": "number", + "minimum": -3.40e+38, + "maximum": 3.40e+38 + } + }, + { + "description": "double-precision floating-point type", + "body": { + "type": "number", + "minimum": -1.70e+308, + "maximum": 1.70e+308 + } + }, + { + "description": "string type", + "body": { + "type": "string" + } + }, + { + "description": "enumerated type (with example values)", + "body": { + "type": "string", + "enum": [ "red", "green", "blue" ] + } + }, + { + "description": "date-time type", + "body": { + "type": "string", + "format": "date-time" + } + }, + { + "description": "date type", + "body": { + "type": "string", + "format": "date" + } + }, + { + "description": "time type", + "body": { + "type": "string", + "format": "time" + } + }, + { + "description": "UUID type", + "body": { + "type": "string", + "format": "uuid" + } + }, + { + "description": "byte stream type", + "body": { + "type": "string", + "contentEncoding": "base64" + } + }, + { + "description": "ISO 8601 duration string type", + "body": { + "type": "string", + "pattern": "^P(\\\\\\\\d+Y)?(\\\\\\\\d+M)?(\\\\\\\\d+W)?(\\\\\\\\d+D)?(T(\\\\\\\\d+H)?(\\\\\\\\d+M)?(\\\\\\\\d+S)?)?$" + } + }, + { + "description": "decimal string type", + "body": { + "type": "string", + "pattern": "^(?:\\\\\\\\+|-)?(?:[1-9][0-9]*|0)(?:\\\\\\\\.[0-9]*)?$" + } + }, + { + "description": "array type (example with string values)", + "body": { + "type": "array", + "items": { "type": "string" } + } + }, + { + "description": "object type (3 example float properties, 2 required)", + "body": { + "type": "object", + "required": [ "lat", "lon" ], + "properties": { + "lat": { + "type": "number", + "minimum": -3.40e+38, + "maximum": 3.40e+38 + }, + "lon": { + "type": "number", + "minimum": -3.40e+38, + "maximum": 3.40e+38 + }, + "alt": { + "type": "number", + "minimum": -3.40e+38, + "maximum": 3.40e+38 + } + } + } + }, + { + "description": "map type (example values are signed integers)", + "body": { + "type": "object", + "dtv:additionalProperties": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + } + ] + }, + "dataSchema": { + "allOf": [ { "$ref": "#/definitions/dataSchema_base" } ], + "type": "object", + "required": [ "type" ], + "not": { + "anyOf": [ + { + "required": [ "dtv:ref" ] + }, + { + "required": [ "dtv:errorMessage" ] + } + ] + } + }, + "dataSchema_affordance": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/dataSchema_base", + "required": [ "type" ], + "not": { + "anyOf": [ + { + "required": [ "dtv:ref" ] + } + ] + } + }, + { + "properties": { + "dtv:ref": { + "type": "string", + "description": "reference to an external schema definition to be used in place of an inline type definition" + } + }, + "required": [ "dtv:ref" ], + "not": { + "anyOf": [ + { + "required": [ "type" ] + } + ] + } + } + ], + "not": { + "anyOf": [ + { + "required": [ "dtv:errorMessage" ] + } + ] + }, + "defaultSnippets": [ + { + "description": "reference to an external schema definition", + "body": { "dtv:ref": "./EXAMPLESCHEMA.json" } + } + ] + }, + "dataSchema_action": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/dataSchema_object", + "required": [ "type" ], + "not": { + "anyOf": [ + { + "required": [ "dtv:ref" ] + } + ] + } + }, + { + "properties": { + "dtv:ref": { + "type": "string", + "description": "reference to an external schema definition to be used in place of an inline type definition" + } + }, + "required": [ "dtv:ref" ], + "not": { + "anyOf": [ + { + "required": [ "type" ] + } + ] + } + } + ], + "not": { + "anyOf": [ + { + "required": [ "dtv:errorMessage" ] + } + ] + } + }, + "form_element_base": { + "type": "object", + "properties": { + "contentType": { + "type": "string", + "description": "content type for communicated values, only 'application/json' supported at present", + "enum": [ "application/json" ] + }, + "dtv:topic": { + "type": "string", + "pattern": "^(?:(?:[!$-*,-.0-z|~]+|(?:{(?:[A-Za-z]+:)?(?:[A-Za-z]+)})))(?:\/(?:[!$-*,-.0-z|~]+|(?:{(?:[A-Za-z]+:)?(?:[A-Za-z]+)})))*$", + "description": "MQTT topic pattern -- slash-separated sequence of labels, each of which is a non-empty string of restricted characters or a brace-enclosed token", + "defaultSnippets": [ + { + "description": "MQTT topic pattern used for publishes and subscribes", + "body": "sample/action/noop/{modelId}" + } + ] + } + } + }, + "form_element_property": { + "allOf": [ { "$ref": "#/definitions/form_element_base" } ], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ "readproperty", "writeproperty" ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ "readproperty", "writeproperty" ] + }, + "minItems": 1 + } + ] + } + }, + "not": { + "anyOf": [ + { + "required": [ "dtv:serviceGroupId" ] + }, + { + "required": [ "dtv:headerCode" ] + }, + { + "required": [ "dtv:headerInfo" ] + } + ], + "description": "dtv:serviceGroupId, dtv:headerCode, and dtv:headerInfo are not allowed in property forms" + } + }, + "form_element_action": { + "allOf": [ { "$ref": "#/definitions/form_element_base" } ], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ "invokeaction" ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ "invokeaction" ] + }, + "minItems": 1 + } + ] + }, + "dtv:serviceGroupId": { + "description": "string to use for shared subscriptions when receiving server-side action requests", + "type": "string" + }, + "dtv:headerCode": { + "description": "reference to a key in the 'schemaDefinitions' property object, whose value is an object defining a string enum for a MQTT header value", + "type": "string" + }, + "dtv:headerInfo": { + "description": "schema for content embedded in a MQTT user header value", + "type": "array", + "items": { + "type": "object", + "properties": { + "contentType": { + "description": "content type for data embedded in header info value, only 'application/json' supported at present", + "type": "string", + "enum": [ "application/json" ] + }, + "schema": { + "type": "string", + "description": "reference to a key in the 'schemaDefinitions' property object" + }, + "success": { + "type": "boolean" + } + } + } + } + } + }, + "form_element_event": { + "allOf": [ { "$ref": "#/definitions/form_element_base" } ], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ "subscribeevent" ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ "subscribeevent" ] + }, + "minItems": 1 + } + ] + }, + "dtv:serviceGroupId": { + "description": "string to use for shared subscriptions when receiving client-side events", + "type": "string" + } + }, + "not": { + "anyOf": [ + { + "required": [ "additionalResponses" ] + }, + { + "required": [ "dtv:headerCode" ] + }, + { + "required": [ "dtv:headerInfo" ] + } + ], + "description": "dtv:headerCode and dtv:headerInfo are not allowed in event forms" + } + }, + "form_element_root": { + "allOf": [ { "$ref": "#/definitions/form_element_base" } ], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "subscribeallevents", + "readallproperties", + "writemultipleproperties" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "subscribeallevents" + ] + }, + "minItems": 1 + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "readallproperties", + "writemultipleproperties" + ] + }, + "minItems": 1 + } + ] + }, + "dtv:serviceGroupId": { + "description": "string to use for shared subscriptions when receiving client-side events", + "type": "string" + } + }, + "required": [ "op" ], + "not": { + "anyOf": [ + { + "required": [ "dtv:headerCode" ] + }, + { + "required": [ "dtv:headerInfo" ] + } + ], + "description": "dtv:headerCode and dtv:headerInfo are not allowed in root forms" + } + }, + "aioThingModel": { + "allOf": [ + { + "$ref": "./tm-json-schema-validation.json" + }, + { + "type": "object", + "properties": { + "@context": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "dtv": { + "type": "string", + "const": "http://azure.com/DigitalTwins/dtmi#" + }, + "aov": { + "type": "string", + "const": "http://azure.com/IoT/operations/tm#" + } + }, + "additionalProperties": { + "type": "string" + } + } + ] + }, + "contains": { + "type": "object", + "required": [ "dtv" ] + }, + "description": "Must include WoT TD context string \"https://www.w3.org/2022/wot/td/v1.1\" and local context object with key \"dtv\" and value \"http://azure.com/DigitalTwins/dtmi#\"" + }, + "title": { + "$ref": "#/definitions/title" + }, + "aov:typeRef": { + "description": "opaque reference to another definition (not necessarily in WoT) that is congruent to this Thing Model definition", + "type": "string" + }, + "aov:isComposite": { + "description": "true if the Thing Model represents a composite type", + "type": "boolean" + }, + "aov:isEvent": { + "description": "true if the Thing Model represents an event type", + "type": "boolean" + }, + "schemaDefinitions": { + "type": "object", + "description": "definitions of schemas that can be referenced via 'additionalResponses', 'dtv:headerCode', and 'dtv:headerInfo' properties in 'forms' elements", + "additionalProperties": { + "allOf": [ { "$ref": "#/definitions/dataSchema_base" } ], + "type": "object", + "required": [ "type" ], + "properties": { + "dtv:errorMessage": { + "type": "string", + "description": "name of a property whose value will hold an error message when this object is used as an error value" + } + }, + "not": { + "anyOf": [ + { + "required": [ "dtv:ref" ] + } + ] + } + } + }, + "links": { + "type": "array", + "description": "links to other JSON files that can be either Thing Models or naming rules", + "items": { + "type": "object", + "required": [ "rel", "href", "type" ], + "properties": { + "rel": { + "type": "string", + "enum": [ + "tm:extends", + "aov:reference", + "aov:typedReference", + "aov:capability", + "aov:component", + "dtv:naming" + ] + }, + "aov:refType": { + "type": "string", + "description": "custom reference type name, needed when 'rel' is 'aov:typedReference'" + }, + "href": { + "type": "string", + "description": "path to linked JSON file" + }, + "type": { + "type": "string" + } + }, + "allOf": [ + { + "if": { + "properties": { + "rel": { + "const": "aov:typedReference" + } + } + }, + "then": { + "required": [ "aov:refType" ] + }, + "else": { + "not": { + "required": [ "aov:refType" ] + } + } + }, + { + "if": { + "properties": { + "rel": { + "const": "dtv:naming" + } + } + }, + "then": { + "properties": { + "type": { + "description": "naming rules must be written in JSON format", + "const": "application/json" + } + } + }, + "else": { + "properties": { + "type": { + "description": "Thing Models must be written in JSON format", + "const": "application/tm+json" + } + } + } + } + ] + }, + "defaultSnippets": [ + { + "description": "example naming 'links' value -- replace './EXAMPLE.SchemaNames.json' with path to your own schema names declaration file", + "body": [ + { + "rel": "dtv:naming", + "href": "./EXAMPLE.SchemaNames.json", + "type": "application/json" + } + ] + }, + { + "description": "example extends 'links' value -- replace './EXAMPLE.ParentThingModel.TM.json' with path to a real Thing Model that is to be extended", + "body": [ + { + "rel": "tm:extends", + "href": "./EXAMPLE.ParentThingModel.TM.json", + "type": "application/tm+json" + } + ] + } + ] + }, + "forms": { + "type": "array", + "items": { + "$ref": "#/definitions/form_element_root" + }, + "defaultSnippets": [ + { + "description": "example root-level 'forms' values for interacting with all properties and events", + "body": [ + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/write", + "op": "writemultipleproperties" + }, + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/read", + "op": "readallproperties" + }, + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/AllEvents", + "op": "subscribeallevents" + } + ] + } + ] + }, + "properties": { + "type": "object", + "description": "properties are values stored on a server that can be read or written by clients", + "additionalProperties": { + "allOf": [ { "$ref": "#/definitions/dataSchema_affordance" } ], + "type": "object", + "properties": { + "readOnly": { + "type": "boolean", + "description": "true if the property is read-only" + }, + "aov:namespace": { + "type": "string" + }, + "aov:contains": { + "description": "list of names of other property affordances that this property logically contains", + "type": "array", + "items": { + "type": "string" + } + }, + "aov:containedIn": { + "description": "name of another property affordance in which this property is logically contained", + "type": "string" + }, + "dtv:placeholder": { + "type": "boolean", + "description": "true if the property is a placeholder for a dynamic collection of properties" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_property" + }, + "defaultSnippets": [ + { + "description": "example 'forms' values for properties", + "body": [ + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/MyProp1/write", + "op": "writeproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/MyProp1/read", + "op": "readproperty" + } + ] + } + ] + } + } + }, + "defaultSnippets": [ + { + "description": "example 'properties' value", + "body": { + "MyProperty": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647, + "readOnly": false, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/MyProp1/write", + "op": "writeproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/MyProp1/read", + "op": "readproperty" + } + ] + } + } + } + ] + }, + "actions": { + "type": "object", + "description": "actions are round-trip messages initiated by clients to request a server to perform an operation and return the result to the client", + "additionalProperties": { + "type": "object", + "properties": { + "aov:namespace": { + "type": "string" + }, + "input": { + "description": "input arguments as properties in object type", + "$ref": "#/definitions/dataSchema_action", + "defaultSnippets": [ + { + "body": { + "type": "object", + "properties": { + "arg1": { + "type": "string" + }, + "arg2": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + } + }, + { + "description": "reference to an external schema definition (must be an object)", + "body": { "dtv:ref": "./EXAMPLESCHEMA.json" } + } + ] + }, + "output": { + "description": "output values as properties in object type", + "$ref": "#/definitions/dataSchema_action", + "defaultSnippets": [ + { + "body": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "result": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + } + }, + { + "description": "reference to an external schema definition (must be an object)", + "body": { "dtv:ref": "./EXAMPLESCHEMA.json" } + } + ] + }, + "idempotent": { + "type": "boolean", + "description": "true if the action is idempotent" + }, + "safe": { + "type": "boolean", + "description": "true if caching the action's output is safe" + }, + "aov:memberOf": { + "description": "name of a group of actions to which this action belongs", + "type": "string" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_action" + }, + "defaultSnippets": [ + { + "description": "example 'forms' value for actions", + "body": [ + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/DoSomething", + "op": "invokeaction" + } + ] + } + ] + } + } + }, + "defaultSnippets": [ + { + "description": "example 'actions' value", + "body": { + "MyAction": { + "input": { + "type": "object", + "properties": { + "arg1": { + "type": "string" + }, + "arg2": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + }, + "output": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "result": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/DoSomething", + "op": "invokeaction" + } + ] + } + } + } + ] + }, + "events": { + "type": "object", + "description": "events are one-way messages sent from servers to clients to indicate occurrences and status changes", + "additionalProperties": { + "type": "object", + "required": [ "data" ], + "properties": { + "data": { + "$ref": "#/definitions/dataSchema_affordance", + "description": "the data schema of the event" + }, + "aov:namespace": { + "type": "string" + }, + "aov:contains": { + "description": "list of names of other event affordances that this event logically contains", + "type": "array", + "items": { + "type": "string" + } + }, + "aov:containedIn": { + "description": "name of another event affordance in which this event is logically contained", + "type": "string" + }, + "dtv:placeholder": { + "type": "boolean", + "description": "true if the event is a placeholder for a dynamic collection of events" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_event" + }, + "defaultSnippets": [ + { + "description": "example 'forms' value for events", + "body": [ + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/MyEvent", + "op": "subscribeevent" + } + ] + } + ] + } + } + }, + "defaultSnippets": [ + { + "description": "example 'events' value", + "body": { + "MyEvent": { + "data": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "example/SomeTopic/MyEvent", + "op": "subscribeevent" + } + ] + } + } + } + ] + } + } + } + ] + } + }, + "anyOf": [ + { + "$ref": "#/definitions/aioThingModel" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/aioThingModel" + } + } + ] +} diff --git a/codegen2/schema/td-json-schema-validation.json b/codegen2/schema/td-json-schema-validation.json new file mode 100644 index 0000000000..9dc9d2591c --- /dev/null +++ b/codegen2/schema/td-json-schema-validation.json @@ -0,0 +1,1318 @@ +{ + "title": "Thing Description", + "version": "1.1-12-March-2025", + "description": "JSON Schema for validating TD instances against the TD information model. TD instances can be with or without terms that have default values", + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/w3c/wot-thing-description/main/validation/td-json-schema-validation.json", + "definitions": { + "anyUri": { + "type": "string" + }, + "description": { + "type": "string" + }, + "descriptions": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "title": { + "type": "string" + }, + "titles": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "security": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + { + "type": "string" + } + ] + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "subprotocol": { + "type": "string", + "examples": ["longpoll", "websub", "sse"] + }, + "thing-context-td-uri-v1": { + "type": "string", + "const": "https://www.w3.org/2019/wot/td/v1" + }, + "thing-context-td-uri-v1.1": { + "type": "string", + "const": "https://www.w3.org/2022/wot/td/v1.1" + }, + "thing-context-td-uri-temp": { + "type": "string", + "const": "http://www.w3.org/ns/td" + }, + "thing-context": { + "anyOf": [ + { + "$comment": "New context URI with other vocabularies after it but not the old one", + "type": "array", + "items": [ + { + "$ref": "#/definitions/thing-context-td-uri-v1.1" + } + ], + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ], + "not": { + "$ref": "#/definitions/thing-context-td-uri-v1" + } + } + }, + { + "$comment": "Only the new context URI", + "$ref": "#/definitions/thing-context-td-uri-v1.1" + }, + { + "$comment": "Old context URI, followed by the new one and possibly other vocabularies. minItems and contains are required since prefixItems does not say all items should be provided", + "type": "array", + "items": [ + { + "$ref": "#/definitions/thing-context-td-uri-v1" + }, + { + "$ref": "#/definitions/thing-context-td-uri-v1.1" + } + ], + "minItems": 2, + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + } + }, + { + "$comment": "Old context URI, followed by possibly other vocabularies. minItems and contains are required since prefixItems does not say all items should be provided", + "type": "array", + "items": [ + { + "$ref": "#/definitions/thing-context-td-uri-v1" + } + ], + "minItems": 1, + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + } + }, + { + "$comment": "Only the old context URI", + "$ref": "#/definitions/thing-context-td-uri-v1" + } + ] + }, + "bcp47_string": { + "type": "string", + "pattern": "^(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$" + }, + "type_declaration": { + "oneOf": [ + { + "type": "string", + "not": { + "const": "tm:ThingModel" + } + }, + { + "type": "array", + "items": { + "type": "string", + "not": { + "const": "tm:ThingModel" + } + } + } + ] + }, + "dataSchema-type": { + "type": "string", + "enum": ["boolean", "integer", "number", "string", "object", "array", "null"] + }, + "dataSchema": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "title": { + "$ref": "#/definitions/title" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "contentEncoding": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + } + ] + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "additionalResponsesDefinition": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "schema": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + } + }, + "multipleOfDefinition": { + "type": ["integer", "number"], + "exclusiveMinimum": 0 + }, + "expectedResponse": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + } + }, + "required": ["contentType"] + }, + "form_element_base": { + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "$ref": "#/definitions/anyUri" + }, + "contentType": { + "type": "string" + }, + "contentCoding": { + "type": "string" + }, + "subprotocol": { + "$ref": "#/definitions/subprotocol" + }, + "security": { + "$ref": "#/definitions/security" + }, + "scopes": { + "$ref": "#/definitions/scopes" + }, + "response": { + "$ref": "#/definitions/expectedResponse" + }, + "additionalResponses": { + "$ref": "#/definitions/additionalResponsesDefinition" + } + }, + "required": ["href"], + "additionalProperties": true + }, + "form_element_property": { + "allOf": [{ "$ref": "#/definitions/form_element_base" }], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": ["readproperty", "writeproperty", "observeproperty", "unobserveproperty"] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": ["readproperty", "writeproperty", "observeproperty", "unobserveproperty"] + }, + "minItems": 1 + } + ] + } + }, + "additionalProperties": true + }, + "form_element_action": { + "allOf": [{ "$ref": "#/definitions/form_element_base" }], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": ["invokeaction", "queryaction", "cancelaction"] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": ["invokeaction", "queryaction", "cancelaction"] + }, + "minItems": 1 + } + ] + } + }, + "additionalProperties": true + }, + "form_element_event": { + "allOf": [{ "$ref": "#/definitions/form_element_base" }], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": ["subscribeevent", "unsubscribeevent"] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": ["subscribeevent", "unsubscribeevent"] + }, + "minItems": 1 + } + ] + } + }, + "additionalProperties": true + }, + "form_element_root": { + "allOf": [{ "$ref": "#/definitions/form_element_base" }], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] + }, + "minItems": 1 + } + ] + } + }, + "additionalProperties": true, + "required": ["op"] + }, + "form": { + "$comment": "This is NOT for validation purposes but for automatic generation of TS types. For more info, please see: https://github.com/w3c/wot-thing-description/pull/1319#issuecomment-994950057", + "oneOf": [ + { "$ref": "#/definitions/form_element_property" }, + { "$ref": "#/definitions/form_element_action" }, + { "$ref": "#/definitions/form_element_event" }, + { "$ref": "#/definitions/form_element_root" } + ] + }, + "property_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_property" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "observable": { + "type": "boolean" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + } + ] + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["forms"], + "additionalProperties": true + }, + "action_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_action" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "input": { + "$ref": "#/definitions/dataSchema" + }, + "output": { + "$ref": "#/definitions/dataSchema" + }, + "safe": { + "type": "boolean" + }, + "idempotent": { + "type": "boolean" + }, + "synchronous": { + "type": "boolean" + } + }, + "required": ["forms"], + "additionalProperties": true + }, + "event_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_event" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "subscription": { + "$ref": "#/definitions/dataSchema" + }, + "data": { + "$ref": "#/definitions/dataSchema" + }, + "dataResponse": { + "$ref": "#/definitions/dataSchema" + }, + "cancellation": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": ["forms"], + "additionalProperties": true + }, + "base_link_element": { + "type": "object", + "properties": { + "href": { + "$ref": "#/definitions/anyUri" + }, + "type": { + "type": "string" + }, + "rel": { + "type": "string" + }, + "anchor": { + "$ref": "#/definitions/anyUri" + }, + "hreflang": { + "anyOf": [ + { "$ref": "#/definitions/bcp47_string" }, + { + "type": "array", + "items": { + "$ref": "#/definitions/bcp47_string" + } + } + ] + } + }, + "required": ["href"], + "additionalProperties": true + }, + "link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "not": { + "description": "A basic link element should not contain sizes", + "type": "object", + "properties": { + "sizes": {} + }, + "required": ["sizes"] + } + }, + { + "not": { + "description": "A basic link element should not contain icon or tm:extends", + "properties": { + "rel": { + "enum": ["icon", "tm:extends"] + } + }, + "required": ["rel"] + } + } + ] + }, + "icon_link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "properties": { + "rel": { + "const": "icon" + }, + "sizes": { + "type": "string", + "pattern": "[0-9]*x[0-9]+" + } + }, + "required": ["rel"] + } + ] + }, + "additionalSecurityScheme": { + "description": "Applies to additional SecuritySchemes not defined in the WoT TD specification.", + "$comment": "Additional SecuritySchemes should always be defined via a context extension, using a prefixed value for the scheme. This prefix (e.g. 'ace', see the example below) must contain at least one character in order to reference a valid JSON-LD context extension.", + "examples": [ + { + "scheme": "ace:ACESecurityScheme", + "ace:as": "coaps://as.example.com/token", + "ace:audience": "coaps://rs.example.com", + "ace:scopes": ["limited", "special"], + "ace:cnonce": true + } + ], + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "pattern": ".+:.*" + } + }, + "required": ["scheme"], + "additionalProperties": true + }, + "noSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["nosec"] + } + }, + "required": ["scheme"], + "additionalProperties": true + }, + "autoSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["auto"] + } + }, + "not": { + "required": ["name"] + }, + "required": ["scheme"], + "additionalProperties": true + }, + "comboSecurityScheme": { + "oneOf": [ + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["combo"] + }, + "oneOf": { + "type": "array", + "minItems": 2, + "items": { + "type": "string" + } + } + }, + "required": ["scheme", "oneOf"], + "additionalProperties": true + }, + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["combo"] + }, + "allOf": { + "type": "array", + "minItems": 2, + "items": { + "type": "string" + } + } + }, + "required": ["scheme", "allOf"], + "additionalProperties": true + } + ] + }, + "basicSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["basic"] + }, + "in": { + "type": "string", + "enum": ["header", "query", "body", "cookie", "auto"] + }, + "name": { + "type": "string" + } + }, + "required": ["scheme"], + "additionalProperties": true + }, + "digestSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["digest"] + }, + "qop": { + "type": "string", + "enum": ["auth", "auth-int"] + }, + "in": { + "type": "string", + "enum": ["header", "query", "body", "cookie", "auto"] + }, + "name": { + "type": "string" + } + }, + "required": ["scheme"], + "additionalProperties": true + }, + "apiKeySecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["apikey"] + }, + "in": { + "type": "string", + "enum": ["header", "query", "body", "cookie", "uri", "auto"] + }, + "name": { + "type": "string" + } + }, + "required": ["scheme"], + "additionalProperties": true + }, + "bearerSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["bearer"] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "alg": { + "type": "string" + }, + "format": { + "type": "string" + }, + "in": { + "type": "string", + "enum": ["header", "query", "body", "cookie", "auto"] + }, + "name": { + "type": "string" + } + }, + "required": ["scheme"], + "additionalProperties": true + }, + "pskSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["psk"] + }, + "identity": { + "type": "string" + } + }, + "required": ["scheme"], + "additionalProperties": true + }, + "oAuth2SecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": ["oauth2"] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "token": { + "$ref": "#/definitions/anyUri" + }, + "refresh": { + "$ref": "#/definitions/anyUri" + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "flow": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": ["code", "client"] + } + ] + } + }, + "required": ["scheme"], + "additionalProperties": true + }, + "securityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/noSecurityScheme" + }, + { + "$ref": "#/definitions/autoSecurityScheme" + }, + { + "$ref": "#/definitions/comboSecurityScheme" + }, + { + "$ref": "#/definitions/basicSecurityScheme" + }, + { + "$ref": "#/definitions/digestSecurityScheme" + }, + { + "$ref": "#/definitions/apiKeySecurityScheme" + }, + { + "$ref": "#/definitions/bearerSecurityScheme" + }, + { + "$ref": "#/definitions/pskSecurityScheme" + }, + { + "$ref": "#/definitions/oAuth2SecurityScheme" + }, + { + "$ref": "#/definitions/additionalSecurityScheme" + } + ] + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/property_element" + } + }, + "actions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/action_element" + } + }, + "events": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/event_element" + } + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "version": { + "type": "object", + "properties": { + "instance": { + "type": "string" + } + }, + "required": ["instance"] + }, + "links": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/link_element" + }, + { + "$ref": "#/definitions/icon_link_element" + } + ] + } + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_root" + } + }, + "base": { + "$ref": "#/definitions/anyUri" + }, + "securityDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/securityScheme" + } + }, + "schemaDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "support": { + "$ref": "#/definitions/anyUri" + }, + "created": { + "type": "string", + "format": "date-time" + }, + "modified": { + "type": "string", + "format": "date-time" + }, + "profile": { + "oneOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/anyUri" + } + } + ] + }, + "security": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ] + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "@context": { + "$ref": "#/definitions/thing-context" + } + }, + "required": ["title", "security", "securityDefinitions", "@context"], + "additionalProperties": true +} diff --git a/codegen2/schema/tm-json-schema-validation.json b/codegen2/schema/tm-json-schema-validation.json new file mode 100644 index 0000000000..0b502d812e --- /dev/null +++ b/codegen2/schema/tm-json-schema-validation.json @@ -0,0 +1,2063 @@ +{ + "title": "Thing Model", + "version": "1.1-12-March-2025", + "description": "JSON Schema for validating Thing Models. This is automatically generated from the WoT TD Schema.", + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://raw.githubusercontent.com/w3c/wot-thing-description/main/validation/tm-json-schema-validation.json", + "definitions": { + "anyUri": { + "type": "string" + }, + "description": { + "type": "string" + }, + "descriptions": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "title": { + "type": "string" + }, + "titles": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "security": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "subprotocol": { + "type": "string", + "examples": [ + "longpoll", + "websub", + "sse" + ] + }, + "thing-context-td-uri-v1": { + "type": "string", + "const": "https://www.w3.org/2019/wot/td/v1" + }, + "thing-context-td-uri-v1.1": { + "type": "string", + "const": "https://www.w3.org/2022/wot/td/v1.1" + }, + "thing-context-td-uri-temp": { + "type": "string", + "const": "http://www.w3.org/ns/td" + }, + "thing-context": { + "anyOf": [ + { + "$comment": "New context URI with other vocabularies after it but not the old one", + "type": "array", + "items": [ + { + "$ref": "#/definitions/thing-context-td-uri-v1.1" + } + ], + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ], + "not": { + "$ref": "#/definitions/thing-context-td-uri-v1" + } + } + }, + { + "$comment": "Only the new context URI", + "$ref": "#/definitions/thing-context-td-uri-v1.1" + }, + { + "$comment": "Old context URI, followed by the new one and possibly other vocabularies. minItems and contains are required since prefixItems does not say all items should be provided", + "type": "array", + "items": [ + { + "$ref": "#/definitions/thing-context-td-uri-v1" + }, + { + "$ref": "#/definitions/thing-context-td-uri-v1.1" + } + ], + "minItems": 2, + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + } + }, + { + "$comment": "Old context URI, followed by possibly other vocabularies. minItems and contains are required since prefixItems does not say all items should be provided", + "type": "array", + "items": [ + { + "$ref": "#/definitions/thing-context-td-uri-v1" + } + ], + "minItems": 1, + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + } + }, + { + "$comment": "Only the old context URI", + "$ref": "#/definitions/thing-context-td-uri-v1" + } + ] + }, + "bcp47_string": { + "type": "string", + "pattern": "^(((([A-Za-z]{2,3}(-([A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-([A-Za-z]{4}))?(-([A-Za-z]{2}|[0-9]{3}))?(-([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3}))*(-([0-9A-WY-Za-wy-z](-[A-Za-z0-9]{2,8})+))*(-(x(-[A-Za-z0-9]{1,8})+))?)|(x(-[A-Za-z0-9]{1,8})+)|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$" + }, + "type_declaration": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "dataSchema-type": { + "type": "string", + "anyOf": [ + { + "enum": [ + "boolean", + "integer", + "number", + "string", + "object", + "array", + "null" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "dataSchema": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "title": { + "$ref": "#/definitions/title" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "writeOnly": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "readOnly": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "anyOf": [ + { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "contentEncoding": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + } + ] + }, + "maxItems": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "minItems": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "minimum": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "maximum": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "maxLength": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "additionalResponsesDefinition": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "schema": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + } + }, + "multipleOfDefinition": { + "anyOf": [ + { + "type": [ + "integer", + "number" + ], + "exclusiveMinimum": 0 + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "expectedResponse": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + } + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "form_element_base": { + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "href": { + "$ref": "#/definitions/anyUri" + }, + "contentType": { + "type": "string" + }, + "contentCoding": { + "type": "string" + }, + "subprotocol": { + "$ref": "#/definitions/subprotocol" + }, + "security": { + "$ref": "#/definitions/security" + }, + "scopes": { + "$ref": "#/definitions/scopes" + }, + "response": { + "$ref": "#/definitions/expectedResponse" + }, + "additionalResponses": { + "$ref": "#/definitions/additionalResponsesDefinition" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "form_element_property": { + "allOf": [ + { + "$ref": "#/definitions/form_element_base" + } + ], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "anyOf": [ + { + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + { + "type": "array", + "items": { + "type": "string", + "anyOf": [ + { + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "minItems": 1 + } + ] + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "form_element_action": { + "allOf": [ + { + "$ref": "#/definitions/form_element_base" + } + ], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "anyOf": [ + { + "enum": [ + "invokeaction", + "queryaction", + "cancelaction" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + { + "type": "array", + "items": { + "type": "string", + "anyOf": [ + { + "enum": [ + "invokeaction", + "queryaction", + "cancelaction" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "minItems": 1 + } + ] + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "form_element_event": { + "allOf": [ + { + "$ref": "#/definitions/form_element_base" + } + ], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "anyOf": [ + { + "enum": [ + "subscribeevent", + "unsubscribeevent" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + { + "type": "array", + "items": { + "type": "string", + "anyOf": [ + { + "enum": [ + "subscribeevent", + "unsubscribeevent" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "minItems": 1 + } + ] + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "form_element_root": { + "allOf": [ + { + "$ref": "#/definitions/form_element_base" + } + ], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "anyOf": [ + { + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + { + "type": "array", + "items": { + "type": "string", + "anyOf": [ + { + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "minItems": 1 + } + ] + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "form": { + "$comment": "This is NOT for validation purposes but for automatic generation of TS types. For more info, please see: https://github.com/w3c/wot-thing-description/pull/1319#issuecomment-994950057", + "oneOf": [ + { + "$ref": "#/definitions/form_element_property" + }, + { + "$ref": "#/definitions/form_element_action" + }, + { + "$ref": "#/definitions/form_element_event" + }, + { + "$ref": "#/definitions/form_element_root" + } + ] + }, + "property_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_property" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "observable": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "writeOnly": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "readOnly": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "anyOf": [ + { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + } + ] + }, + "maxItems": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "minItems": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "minimum": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "maximum": { + "anyOf": [ + { + "type": "number" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "maxLength": { + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "action_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_action" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "input": { + "$ref": "#/definitions/dataSchema" + }, + "output": { + "$ref": "#/definitions/dataSchema" + }, + "safe": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "idempotent": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "synchronous": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "event_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_event" + } + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "subscription": { + "$ref": "#/definitions/dataSchema" + }, + "data": { + "$ref": "#/definitions/dataSchema" + }, + "dataResponse": { + "$ref": "#/definitions/dataSchema" + }, + "cancellation": { + "$ref": "#/definitions/dataSchema" + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "base_link_element": { + "type": "object", + "properties": { + "href": { + "$ref": "#/definitions/anyUri" + }, + "type": { + "type": "string" + }, + "rel": { + "type": "string" + }, + "anchor": { + "$ref": "#/definitions/anyUri" + }, + "hreflang": { + "anyOf": [ + { + "$ref": "#/definitions/bcp47_string" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/bcp47_string" + } + } + ] + }, + "instanceName": { + "type": "string" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "not": { + "description": "A basic link element should not contain sizes", + "type": "object", + "properties": { + "sizes": {} + }, + "required": [ + "sizes" + ] + } + }, + { + "not": { + "description": "A basic link element should not contain icon", + "properties": { + "rel": { + "anyOf": [ + { + "enum": [ + "icon" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + } + }, + "required": [ + "rel" + ] + } + } + ] + }, + "icon_link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "properties": { + "rel": { + "const": "icon" + }, + "sizes": { + "type": "string", + "pattern": "[0-9]*x[0-9]+" + } + }, + "required": [ + "rel" + ] + } + ] + }, + "additionalSecurityScheme": { + "description": "Applies to additional SecuritySchemes not defined in the WoT TD specification.", + "$comment": "Additional SecuritySchemes should always be defined via a context extension, using a prefixed value for the scheme. This prefix (e.g. 'ace', see the example below) must contain at least one character in order to reference a valid JSON-LD context extension.", + "examples": [ + { + "scheme": "ace:ACESecurityScheme", + "ace:as": "coaps://as.example.com/token", + "ace:audience": "coaps://rs.example.com", + "ace:scopes": [ + "limited", + "special" + ], + "ace:cnonce": true + } + ], + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "pattern": ".+:.*" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "noSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "nosec" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "autoSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "auto" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + } + }, + "not": { + "required": [ + "name" + ] + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "comboSecurityScheme": { + "oneOf": [ + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "combo" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "oneOf": { + "type": "array", + "minItems": 2, + "items": { + "type": "string" + } + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true + }, + { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "combo" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "allOf": { + "type": "array", + "minItems": 2, + "items": { + "type": "string" + } + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true + } + ] + }, + "basicSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "basic" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "in": { + "type": "string", + "anyOf": [ + { + "enum": [ + "header", + "query", + "body", + "cookie", + "auto" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "name": { + "type": "string" + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "digestSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "digest" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "qop": { + "type": "string", + "anyOf": [ + { + "enum": [ + "auth", + "auth-int" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "in": { + "type": "string", + "anyOf": [ + { + "enum": [ + "header", + "query", + "body", + "cookie", + "auto" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "name": { + "type": "string" + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "apiKeySecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "apikey" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "in": { + "type": "string", + "anyOf": [ + { + "enum": [ + "header", + "query", + "body", + "cookie", + "uri", + "auto" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "name": { + "type": "string" + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "bearerSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "bearer" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "alg": { + "type": "string" + }, + "format": { + "type": "string" + }, + "in": { + "type": "string", + "anyOf": [ + { + "enum": [ + "header", + "query", + "body", + "cookie", + "auto" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "name": { + "type": "string" + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "pskSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "psk" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "identity": { + "type": "string" + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "oAuth2SecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "anyOf": [ + { + "enum": [ + "oauth2" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "token": { + "$ref": "#/definitions/anyUri" + }, + "refresh": { + "$ref": "#/definitions/anyUri" + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "flow": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "anyOf": [ + { + "enum": [ + "code", + "client" + ] + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + } + ] + }, + "tm:ref": { + "$ref": "#/definitions/tm_ref" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "securityScheme": { + "anyOf": [ + { + "$ref": "#/definitions/noSecurityScheme" + }, + { + "$ref": "#/definitions/autoSecurityScheme" + }, + { + "$ref": "#/definitions/comboSecurityScheme" + }, + { + "$ref": "#/definitions/basicSecurityScheme" + }, + { + "$ref": "#/definitions/digestSecurityScheme" + }, + { + "$ref": "#/definitions/apiKeySecurityScheme" + }, + { + "$ref": "#/definitions/bearerSecurityScheme" + }, + { + "$ref": "#/definitions/pskSecurityScheme" + }, + { + "$ref": "#/definitions/oAuth2SecurityScheme" + }, + { + "$ref": "#/definitions/additionalSecurityScheme" + } + ] + }, + "tm_type_declaration": { + "oneOf": [ + { + "type": "string", + "const": "tm:ThingModel" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "contains": { + "const": "tm:ThingModel" + } + } + ] + }, + "placeholder-pattern": { + "type": "string", + "pattern": "^.*[{]{2}[ -~]+[}]{2}.*$" + }, + "tm_optional": { + "type": "array", + "items": { + "$comment": "this first checks for the general structure of /properties/myProp and then prohibits using / 3 times", + "allOf": [ + { + "type": "string", + "pattern": "^((/properties/)|(/actions/)|(/events/))(([^/]))", + "$comment": "regex tests available at https://regex101.com/r/UgOzrJ/1" + }, + { + "not": { + "type": "string", + "pattern": "(/)(.*/){2}", + "$comment": "regex tests available at https://regex101.com/r/r7vB0r/2" + } + } + ] + } + }, + "tm_ref": { + "type": "string", + "format": "uri-reference" + } + }, + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/property_element" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "actions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/action_element" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "events": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/event_element" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "version": { + "anyOf": [ + { + "type": "object", + "properties": { + "model": { + "type": "string" + } + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + }, + "not": { + "type": "object", + "properties": { + "instance": { + "type": "string" + } + }, + "required": [ + "instance" + ] + } + }, + { + "$ref": "#/definitions/placeholder-pattern" + } + ] + }, + "links": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/link_element" + }, + { + "$ref": "#/definitions/icon_link_element" + } + ] + } + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_root" + } + }, + "base": { + "$ref": "#/definitions/anyUri" + }, + "securityDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/securityScheme" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "schemaDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "support": { + "$ref": "#/definitions/anyUri" + }, + "created": { + "type": "string" + }, + "modified": { + "type": "string" + }, + "profile": { + "oneOf": [ + { + "$ref": "#/definitions/anyUri" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/anyUri" + } + } + ] + }, + "security": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + } + ] + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + }, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + } + }, + "@type": { + "$ref": "#/definitions/tm_type_declaration" + }, + "@context": { + "$ref": "#/definitions/thing-context" + }, + "tm:optional": { + "$ref": "#/definitions/tm_optional" + } + }, + "additionalProperties": true, + "propertyNames": { + "not": { + "$ref": "#/definitions/placeholder-pattern" + } + }, + "required": [ + "@context", + "@type" + ] +} \ No newline at end of file diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/Azure.Iot.Operations.CodeGeneration.csproj b/codegen2/src/Azure.Iot.Operations.CodeGeneration/Azure.Iot.Operations.CodeGeneration.csproj new file mode 100644 index 0000000000..183228517d --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/Azure.Iot.Operations.CodeGeneration.csproj @@ -0,0 +1,12 @@ + + + + net9.0 + enable + + + + + + + diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/CodeName.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/CodeName.cs new file mode 100644 index 0000000000..cdadaa714d --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/CodeName.cs @@ -0,0 +1,257 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + public class CodeName : ITypeName, IEquatable + { + private readonly string givenName; + private readonly string[] components; + private readonly string lowerName; + private readonly string pascalName; + private readonly string camelName; + private readonly string snakeName; + + public CodeName(string givenName = "") + : this(givenName, Decompose(givenName)) + { + } + + public CodeName(string baseName, string suffix1, string? suffix2 = null, string? suffix3 = null) + : this(Extend(baseName, suffix1, suffix2, suffix3), DecomposeAndExtend(baseName, suffix1, suffix2, suffix3)) + { + } + + public CodeName(CodeName baseName, string suffix1) + : this(Extend(baseName.AsGiven, suffix1, null, null), baseName.AsComponents.Append(suffix1)) + { + } + + public CodeName(CodeName baseName, string suffix1, string suffix2) + : this(Extend(baseName.AsGiven, suffix1, suffix2, null), baseName.AsComponents.Append(suffix1).Append(suffix2)) + { + } + + public CodeName(CodeName baseName, string suffix1, string suffix2, string suffix3) + : this(Extend(baseName.AsGiven, suffix1, suffix2, suffix3), baseName.AsComponents.Append(suffix1).Append(suffix2).Append(suffix3)) + { + } + + public CodeName(string givenName, IEnumerable components) + { + this.givenName = givenName; + this.components = components.ToArray(); + lowerName = string.Concat(components); + pascalName = string.Concat(components.Select(c => char.ToUpper(c[0]) + c.Substring(1))); + camelName = pascalName.Length > 0 ? char.ToLower(pascalName[0]) + pascalName.Substring(1) : string.Empty; + snakeName = string.Join('_', components); + } + + public override string ToString() + { + throw new Exception($"ToString() called on CodeName({AsGiven})"); + } + + public override bool Equals(object? obj) + { + return Equals(obj as CodeName); + } + + public bool Equals(CodeName? other) + { + return !ReferenceEquals(null, other) && AsGiven == other.AsGiven; + } + + public override int GetHashCode() + { + return AsGiven.GetHashCode(); + } + + public string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false) => language switch + { + TargetLanguage.CSharp => Escape(language, AsPascal(suffix1, suffix2, suffix3, suffix4)), + TargetLanguage.Rust => Escape(language, AsPascal(suffix1, suffix2, suffix3, suffix4)), + _ => AsPascal(suffix1, suffix2, suffix3, suffix4), + }; + + public string GetFieldName(TargetLanguage language) => language switch + { + TargetLanguage.CSharp => Escape(language, AsPascal()), + TargetLanguage.Rust => Escape(language, AsSnake()), + _ => throw new ArgumentOutOfRangeException(nameof(language)), + }; + + public string GetMethodName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? prefix = null) => language switch + { + TargetLanguage.CSharp => Escape(language, AsPascal(suffix1, suffix2, suffix3, prefix: prefix)), + TargetLanguage.Rust => Escape(language, AsSnake(suffix1, suffix2, suffix3, prefix: prefix)), + _ => throw new ArgumentOutOfRangeException(nameof(language)), + }; + + public string GetVariableName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? prefix = null) => language switch + { + TargetLanguage.CSharp => Escape(language, AsCamel(suffix1, suffix2, suffix3, prefix: prefix)), + TargetLanguage.Rust => Escape(language, AsSnake(suffix1, suffix2, suffix3, prefix: prefix)), + _ => throw new ArgumentOutOfRangeException(nameof(language)), + }; + + public string GetConstantName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? prefix = null) => language switch + { + TargetLanguage.CSharp => Escape(language, AsPascal(suffix1, suffix2, suffix3, prefix: prefix)), + TargetLanguage.Rust => Escape(language, AsScreamingSnake(suffix1, suffix2, suffix3, prefix: prefix)), + _ => throw new ArgumentOutOfRangeException(nameof(language)), + }; + + public string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null) => language switch + { + TargetLanguage.CSharp => AsPascal(suffix1, suffix2, suffix3), + TargetLanguage.Rust => AsSnake(suffix1, suffix2, suffix3), + _ => throw new ArgumentOutOfRangeException(nameof(language)), + }; + + public string GetFolderName(TargetLanguage language) => language switch + { + TargetLanguage.CSharp => AsPascal(), + TargetLanguage.Rust => AsSnake(), + _ => throw new ArgumentOutOfRangeException(nameof(language)), + }; + + public bool IsEmpty => string.IsNullOrEmpty(givenName); + + public string AsGiven => givenName; + + private string[] AsComponents => components; + + private string AsLower(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + return prefix ?? string.Empty + lowerName + suffix1 ?? string.Empty + suffix2 ?? string.Empty + suffix3 ?? string.Empty + suffix4 ?? string.Empty; + } + + private string AsPascal(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + return GetCapitalized(prefix) + pascalName + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + + private string AsCamel(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + if (!string.IsNullOrEmpty(prefix)) + { + return prefix + pascalName + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + else if (!string.IsNullOrEmpty(givenName)) + { + return camelName + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + else + { + return suffix1 + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + } + + private string AsSnake(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + return GetSnakePrefix(prefix) + snakeName + GetSnakeSuffix(suffix1) + GetSnakeSuffix(suffix2) + GetSnakeSuffix(suffix3) + GetSnakeSuffix(suffix4); + } + + private string AsScreamingSnake(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + return AsSnake(suffix1, suffix2, suffix3, suffix4, prefix: prefix).ToUpperInvariant(); + } + + private static string Extend(string baseName, string suffix1, string? suffix2, string? suffix3) + { + bool snakeWise = baseName.Contains('_'); + StringBuilder givenName = new(baseName); + givenName.Append(Extension(suffix1, snakeWise)); + + if (suffix2 != null) + { + givenName.Append(Extension(suffix2, snakeWise)); + } + + if (suffix3 != null) + { + givenName.Append(Extension(suffix3, snakeWise)); + } + + return givenName.ToString(); + } + + private static List DecomposeAndExtend(string baseName, string suffix1, string? suffix2, string? suffix3) + { + List components = Decompose(baseName); + components.Add(suffix1); + + if (suffix2 != null) + { + components.Add(suffix2); + } + + if (suffix3 != null) + { + components.Add(suffix3); + } + + return components; + } + + private static List Decompose(string givenName) + { + List components = new(); + StringBuilder stringBuilder = new(); + char p = '\0'; + + foreach (char c in givenName) + { + if (((char.IsUpper(c) && char.IsLower(p)) || c == '_') && stringBuilder.Length > 0) + { + components.Add(stringBuilder.ToString()); + stringBuilder.Clear(); + } + + if (c != '_') + { + stringBuilder.Append(char.ToLower(c)); + } + + p = c; + } + + if (stringBuilder.Length > 0) + { + components.Add(stringBuilder.ToString()); + } + + return components; + } + + private static string Extension(string suffix, bool snakeWise) + { + return snakeWise ? GetSnakeSuffix(suffix) : GetCapitalized(suffix); + } + + private static string GetCapitalized(string? suffix) + { + return suffix == null ? string.Empty : char.ToUpperInvariant(suffix[0]) + suffix.Substring(1); + } + + private static string GetSnakeSuffix(string? suffix) + { + return suffix == null ? string.Empty : $"_{suffix}"; + } + + private static string GetSnakePrefix(string? prefix) + { + return prefix == null ? string.Empty : $"{prefix}_"; + } + + private static string Escape(TargetLanguage language, string name) => language switch + { + TargetLanguage.CSharp => ReservedCSharp.Keywords.Contains(name) ? $"@{name}" : name, + TargetLanguage.Rust => ReservedRust.Keywords.Contains(name) ? $"r#{name}" : name, + _ => name, + }; + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/CustomTypeName.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/CustomTypeName.cs new file mode 100644 index 0000000000..09405f19cc --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/CustomTypeName.cs @@ -0,0 +1,53 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System; + + public class CustomTypeName : ITypeName + { + public static CustomTypeName Instance = new(); + + public string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false) + { + if (suffix1 != null) + { + return "CustomPayload" + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + else + { + return language switch + { + TargetLanguage.CSharp => "CustomPayload", + TargetLanguage.Rust => "CustomPayload", + _ => throw new InvalidOperationException($"There is no {language} representation for {typeof(CustomTypeName)}"), + }; + } + } + + public string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null) + { + if (suffix1 != null) + { + return language switch + { + TargetLanguage.CSharp => "CustomPayload" + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3), + TargetLanguage.Rust => "custom_payload" + GetSnakeSuffix(suffix1) + GetSnakeSuffix(suffix2) + GetSnakeSuffix(suffix3), + _ => throw new InvalidOperationException($"There is no {language} representation for {typeof(CustomTypeName)}"), + }; + } + else + { + throw new InvalidOperationException($"{typeof(CustomTypeName)} should not be used for a file name without a suffix"); + } + } + + private static string GetCapitalized(string? suffix) + { + return suffix == null ? string.Empty : char.ToUpperInvariant(suffix[0]) + suffix.Substring(1); + } + + private static string GetSnakeSuffix(string? suffix) + { + return suffix == null ? string.Empty : $"_{suffix}"; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/EmptyTypeName.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/EmptyTypeName.cs new file mode 100644 index 0000000000..e008613f95 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/EmptyTypeName.cs @@ -0,0 +1,146 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System; + + public abstract class EmptyTypeName: ITypeName + { + public static EmptyAvroTypeName AvroInstance = new(); + public static EmptyCborTypeName CborInstance = new(); + public static EmptyJsonTypeName JsonInstance = new(); + public static EmptyProtoTypeName ProtoInstance = new(); + public static EmptyRawTypeName RawInstance = new(); + public static EmptyCustomTypeName CustomInstance = new(); + + public class EmptyAvroTypeName : EmptyTypeName + { + public override string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false) => (suffix1, suffix2, suffix3, suffix4, language) switch + { + (null, null, null, null, TargetLanguage.CSharp) => "EmptyAvro", + (null, null, null, null, TargetLanguage.Rust) => "EmptyAvro", + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyAvroTypeName)} cannot take a suffix" : $"There is no {language} representation for {typeof(EmptyAvroTypeName)}"), + }; + + public override string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null) => (suffix1, suffix2, suffix3, language) switch + { + (null, null, null, TargetLanguage.Rust) => "empty_avro", + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyAvroTypeName)} cannot take a suffix" : $"There is no {language} file name for {typeof(EmptyAvroTypeName)}"), + }; + + public override string GetAllocator(TargetLanguage language) => language switch + { + TargetLanguage.CSharp => "new EmptyAvro()", + TargetLanguage.Rust => "EmptyAvro {}", + _ => throw new InvalidOperationException($"There is no {language} allocator for {typeof(EmptyAvroTypeName)}"), + }; + } + + public class EmptyCborTypeName : EmptyTypeName + { + public override string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false) => (suffix1, suffix2, suffix3, suffix4, language) switch + { + (null, null, null, null, TargetLanguage.CSharp) => "EmptyCbor", + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyCborTypeName)} cannot take a suffix" : $"There is no {language} representation for {typeof(EmptyCborTypeName)}"), + }; + + public override string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null) => (suffix1, suffix2, suffix3, language) switch + { + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyCborTypeName)} cannot take a suffix" : $"There is no {language} file name for {typeof(EmptyCborTypeName)}"), + }; + + public override string GetAllocator(TargetLanguage language) => language switch + { + TargetLanguage.CSharp => "new EmptyCbor()", + _ => throw new InvalidOperationException($"There is no {language} allocator for {typeof(EmptyAvroTypeName)}"), + }; + } + + public class EmptyJsonTypeName : EmptyTypeName + { + public override string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false) => (suffix1, suffix2, suffix3, suffix4, language) switch + { + (null, null, null, null, TargetLanguage.CSharp) => "EmptyJson", + (null, null, null, null, TargetLanguage.Rust) => "EmptyJson", + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyJsonTypeName)} cannot take a suffix" : $"There is no {language} representation for {typeof(EmptyJsonTypeName)}"), + }; + + public override string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null) => (suffix1, suffix2, suffix3, language) switch + { + (null, null, null, TargetLanguage.Rust) => "empty_json", + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyJsonTypeName)} cannot take a suffix" : $"There is no {language} file name for {typeof(EmptyJsonTypeName)}"), + }; + + public override string GetAllocator(TargetLanguage language) => language switch + { + TargetLanguage.CSharp => "new EmptyJson()", + TargetLanguage.Rust => "EmptyJson {}", + _ => throw new InvalidOperationException($"There is no {language} allocator for {typeof(EmptyAvroTypeName)}"), + }; + } + + public class EmptyProtoTypeName : EmptyTypeName + { + public override string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false) => (suffix1, suffix2, suffix3, suffix4, language) switch + { + (null, null, null, null, TargetLanguage.CSharp) => "Google.Protobuf.WellKnownTypes.Empty", + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyProtoTypeName)} cannot take a suffix" : $"There is no {language} representation for {typeof(EmptyProtoTypeName)}"), + }; + + public override string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null) => (suffix1, suffix2, suffix3, language) switch + { + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyProtoTypeName)} cannot take a suffix" : $"There is no {language} file name for {typeof(EmptyProtoTypeName)}"), + }; + + public override string GetAllocator(TargetLanguage language) => language switch + { + TargetLanguage.CSharp => "new Google.Protobuf.WellKnownTypes.Empty()", + _ => throw new InvalidOperationException($"There is no {language} allocator for {typeof(EmptyAvroTypeName)}"), + }; + } + + public class EmptyRawTypeName : EmptyTypeName + { + public override string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false) => (suffix1, suffix2, suffix3, suffix4, language) switch + { + (null, null, null, null, TargetLanguage.CSharp) => "byte[]", + (null, null, null, null, TargetLanguage.Rust) => "byte[]", + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyRawTypeName)} cannot take a suffix" : $"There is no {language} representation for {typeof(EmptyRawTypeName)}"), + }; + + public override string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null) => + throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyRawTypeName)} cannot take a suffix" : $"There is no {language} file name for {typeof(EmptyRawTypeName)}"); + + public override string GetAllocator(TargetLanguage language) => language switch + { + TargetLanguage.CSharp => "Array.Empty()", + TargetLanguage.Rust => "byte[] {}", + _ => throw new InvalidOperationException($"There is no {language} allocator for {typeof(EmptyAvroTypeName)}"), + }; + } + + public class EmptyCustomTypeName : EmptyTypeName + { + public override string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false) => (suffix1, suffix2, suffix3, suffix4, language) switch + { + (null, null, null, null, TargetLanguage.CSharp) => "CustomPayload", + (null, null, null, null, TargetLanguage.Rust) => "CustomPayload", + _ => throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyCustomTypeName)} cannot take a suffix" : $"There is no {language} representation for {typeof(EmptyCustomTypeName)}"), + }; + + public override string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null) => + throw new InvalidOperationException(suffix1 != null ? $"{typeof(EmptyCustomTypeName)} cannot take a suffix" : $"There is no {language} file name for {typeof(EmptyCustomTypeName)}"); + + public override string GetAllocator(TargetLanguage language) => language switch + { + TargetLanguage.CSharp => "ExternalSerializer.EmptyValue", + TargetLanguage.Rust => "CustomPayload {}", + _ => throw new InvalidOperationException($"There is no {language} allocator for {typeof(EmptyAvroTypeName)}"), + }; + } + + public abstract string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false); + + public abstract string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null); + + public abstract string GetAllocator(TargetLanguage language); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorCondition.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorCondition.cs new file mode 100644 index 0000000000..a262c756a1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorCondition.cs @@ -0,0 +1,20 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + public enum ErrorCondition + { + None, + Duplication, + ElementMissing, + ElementsPlural, + ItemNotFound, + JsonInvalid, + PropertyMissing, + PropertyEmpty, + PropertyInvalid, + PropertyUnsupported, + PropertyUnsupportedValue, + TypeMismatch, + Unusable, + ValuesInconsistent, + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorLevel.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorLevel.cs new file mode 100644 index 0000000000..86e0ff12f1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorLevel.cs @@ -0,0 +1,9 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + public enum ErrorLevel + { + Warning, + Error, + Fatal, + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorLog.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorLog.cs new file mode 100644 index 0000000000..1bc63cd61d --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorLog.cs @@ -0,0 +1,233 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.TDParser.Model; + + public class ErrorLog + { + private readonly Dictionary> referencesFromThings; + private readonly Dictionary<(string, string), List> typedReferencesFromThings; + private readonly Dictionary> namesOfThings; + private readonly Dictionary> namesInThings; + private readonly Dictionary> topicsInThings; + private readonly Dictionary> schemaNames; + private readonly string defaultFolder; + + public string Phase { get; set; } = "Initialization"; + + public HashSet Warnings { get; init; } + + public HashSet Errors { get; init; } + + public ErrorRecord? FatalError { get; private set; } + + public bool HasErrors => Errors.Count > 0 || FatalError != null; + + public ErrorLog(string defaultFolder) + { + this.referencesFromThings = new Dictionary>(); + this.typedReferencesFromThings = new Dictionary<(string, string), List>(); + this.namesOfThings = new Dictionary>(); + this.namesInThings = new Dictionary>(); + this.topicsInThings = new Dictionary>(); + this.schemaNames = new Dictionary>(); + this.defaultFolder = defaultFolder; + + Errors = new HashSet(); + Warnings = new HashSet(); + FatalError = null; + } + + public void CheckForDuplicatesInThings() + { + foreach (var (name, nameSites) in namesOfThings) + { + if (nameSites.Count > 1) + { + foreach (var (filename, lineNumber) in nameSites) + { + AddError(ErrorLevel.Error, ErrorCondition.Duplication, $"Duplicate use of Thing name '{name}'.", filename, lineNumber, crossRef: name); + } + } + } + + foreach (var (name, nameSites) in namesInThings) + { + if (nameSites.Count > 1) + { + foreach (var (thingName, (filename, lineNumber)) in nameSites) + { + AddError(ErrorLevel.Error, ErrorCondition.Duplication, $"Duplicate use of generated name '{name}' across Thing Descriptions.", filename, lineNumber, crossRef: name); + } + } + } + + foreach (var (topic, topicReferences) in topicsInThings) + { + if (topicReferences.Count > 1) + { + foreach (ValueReference reference in topicReferences) + { + string description; + string citation; + if (topic == reference.Value) + { + description = "Topic"; + citation = string.Empty; + } + else + { + description = topic.Contains('{') ? "Partially resolved topic" : "Resolved topic"; + citation = $" (in model as \"{reference.Value}\")"; + } + + AddError(ErrorLevel.Error, ErrorCondition.Duplication, $"{description} '{topic}' used by multiple affordances{citation}.", reference.Filename, reference.LineNumber, crossRef: topic); + } + } + } + } + + public void CheckForDuplicatesInSchemas() + { + foreach (var (name, nameSites) in schemaNames) + { + if (nameSites.Count > 1) + { + foreach (var (filename, lineNumber) in nameSites) + { + AddError(ErrorLevel.Error, ErrorCondition.Duplication, $"Duplicate use of generated name '{name}' across schema definitions.", filename, lineNumber, crossRef: name); + } + } + } + } + + public void RegisterReferenceFromThing(string refPath, string filename, int lineNumber, string refValue) + { + string fullPath = Path.GetFullPath(Path.Combine(this.defaultFolder, refPath)).Replace('\\', '/'); + + if (!referencesFromThings.TryGetValue(fullPath, out List? references)) + { + references = new(); + referencesFromThings[fullPath] = references; + } + references.Add(new ValueReference(filename, lineNumber, refValue)); + } + + public void RegisterTypedReferenceFromThing(string refPath, string filename, int lineNumber, string type, string refValue) + { + string fullPath = Path.GetFullPath(Path.Combine(this.defaultFolder, refPath)).Replace('\\', '/'); + var key = (fullPath, type); + + if (!typedReferencesFromThings.TryGetValue(key, out List? typedReferences)) + { + typedReferences = new(); + typedReferencesFromThings[key] = typedReferences; + } + typedReferences.Add(new ValueReference(filename, lineNumber, refValue)); + } + + public void RegisterNameOfThing(string name, string filename, int lineNumber) + { + if (!namesOfThings.TryGetValue(name, out List<(string, int)>? nameSites)) + { + nameSites = new(); + namesOfThings[name] = nameSites; + } + nameSites.Add((filename, lineNumber)); + } + + public void RegisterNameInThing(string name, string thingName, string filename, int lineNumber) + { + if (!namesInThings.TryGetValue(name, out Dictionary? nameSites)) + { + nameSites = new(); + namesInThings[name] = nameSites; + } + + if (!nameSites.ContainsKey(thingName)) + { + nameSites[thingName] = (filename, lineNumber); + } + } + + public void RegisterTopicInThing(string resolvedTopic, string filename, int lineNumber, string rawTopic) + { + if (!topicsInThings.TryGetValue(resolvedTopic, out List? topicReferences)) + { + topicReferences = new(); + topicsInThings[resolvedTopic] = topicReferences; + } + topicReferences.Add(new ValueReference(filename, lineNumber, rawTopic)); + } + + public void RegisterSchemaName(string name, string filename, string dirpath, int lineNumber) + { + if (!schemaNames.TryGetValue(name, out List<(string, int)>? nameSites)) + { + nameSites = new(); + schemaNames[name] = nameSites; + } + + if (dirpath.Equals(this.defaultFolder) && namesInThings.TryGetValue(name, out Dictionary? thingNameSites)) + { + foreach (KeyValuePair thingNameSite in thingNameSites) + { + nameSites.Add(thingNameSite.Value); + } + } + else + { + nameSites.Add((filename, lineNumber)); + } + } + + public void AddError(ErrorLevel level, ErrorCondition condition, string message, string filename, int lineNumber, int cfLineNumber = 0, string crossRef = "") + { + ErrorRecord errorRecord = new(condition, message, filename, lineNumber, cfLineNumber, crossRef); + switch (level) + { + case ErrorLevel.Warning: + this.Warnings.Add(errorRecord); + break; + case ErrorLevel.Error: + this.Errors.Add(errorRecord); + break; + case ErrorLevel.Fatal: + FatalError = errorRecord; + break; + } + } + + public void AddReferenceError(string refPath, string description, string reason, string filename, string dirpath, int lineNumber, string refValue) + { + if (dirpath.Equals(this.defaultFolder) && referencesFromThings.TryGetValue(refPath, out List? references)) + { + foreach (ValueReference reference in references) + { + AddError(ErrorLevel.Error, ErrorCondition.ItemNotFound, $"External schema reference \"{reference.Value}\" not resolvable; {reason}", reference.Filename, reference.LineNumber); + } + } + else + { + AddError(ErrorLevel.Error, ErrorCondition.ItemNotFound, $"{description} \"{refValue}\" not resolvable; {reason}", filename, lineNumber); + } + } + + public void AddReferenceTypeError(string refPath, string description, string filename, string dirpath, int lineNumber, string refValue, string refType, string actualType) + { + var key = (refPath, refType); + if (dirpath.Equals(this.defaultFolder) && typedReferencesFromThings.TryGetValue(key, out List? typedReferences)) + { + foreach (ValueReference reference in typedReferences) + { + AddError(ErrorLevel.Error, ErrorCondition.TypeMismatch, $"External schema reference \"{reference.Value}\" is expected to define a schema of type \"{refType}\", but it defines a schema of type \"{actualType}\"", reference.Filename, reference.LineNumber); + } + } + else + { + AddError(ErrorLevel.Error, ErrorCondition.TypeMismatch, $"{description} \"{refValue}\" is expected to define a schema of type \"{refType}\", but it defines a schema of type \"{actualType}\"", filename, lineNumber); + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorRecord.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorRecord.cs new file mode 100644 index 0000000000..586e8a4806 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorRecord.cs @@ -0,0 +1,4 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + public record ErrorRecord(ErrorCondition Condition, string Message, string Filename, int LineNumber, int CfLineNumber, string CrossRef); +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorReporter.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorReporter.cs new file mode 100644 index 0000000000..13b7e9d304 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ErrorReporter.cs @@ -0,0 +1,119 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System; + using System.IO; + using System.Text.RegularExpressions; + using Azure.Iot.Operations.TDParser; + + public class ErrorReporter + { + private static readonly Regex JsonMessageRegex = new(@"(.+)\. LineNumber: (\d+) \| BytePositionInLine: \d+\.", RegexOptions.Compiled); + + private ErrorLog errorLog; + private string filename; + private string basePath; + private byte[] byteStream; + + public ErrorReporter(ErrorLog errorLog, string filePath, byte[] byteStream) + { + this.errorLog = errorLog; + this.filename = Path.GetFileName(filePath); + this.basePath = Path.GetDirectoryName(filePath)!; + this.byteStream = byteStream; + } + + public void RegisterReferenceFromThing(long byteIndex, string refValue) + { + string refPath = refValue.Contains('/') ? Path.GetFullPath(Path.Combine(this.basePath, refValue)).Replace('\\', '/') : refValue; + this.errorLog.RegisterReferenceFromThing(refPath, this.filename, GetLineNumber(byteIndex), refValue); + } + + public void RegisterTypedReferenceFromThing(long byteIndex, string type, string refValue) + { + string refPath = refValue.Contains('/') ? Path.GetFullPath(Path.Combine(this.basePath, refValue)).Replace('\\', '/') : refValue; + this.errorLog.RegisterTypedReferenceFromThing(refPath, this.filename, GetLineNumber(byteIndex), type, refValue); + } + + public void RegisterNameOfThing(string name, long byteIndex) + { + this.errorLog.RegisterNameOfThing(name, this.filename, GetLineNumber(byteIndex)); + } + + public void RegisterNameInThing(string name, string thingName, long byteIndex) + { + this.errorLog.RegisterNameInThing(name, thingName, this.filename, GetLineNumber(byteIndex)); + } + + public void RegisterTopicInThing(string resolvedTopic, long byteIndex, string rawTopic) + { + this.errorLog.RegisterTopicInThing(resolvedTopic, this.filename, GetLineNumber(byteIndex), rawTopic); + } + + public void RegisterSchemaName(string name, long byteIndex) + { + this.errorLog.RegisterSchemaName(name, this.filename, this.basePath, GetLineNumber(byteIndex)); + } + + public void ReportError(ErrorCondition condition, string message, long byteIndex, long cfByteIndex = -1, ErrorLevel level = ErrorLevel.Error) + { + this.errorLog.AddError(level, condition, message, this.filename, GetLineNumber(byteIndex), GetLineNumber(cfByteIndex)); + } + + public void ReportWarning(string message, long byteIndex, long cfByteIndex = -1) + { + ReportError(ErrorCondition.None, message, byteIndex, cfByteIndex, ErrorLevel.Warning); + } + + public void ReportFatal(ErrorCondition condition, string message, long byteIndex, long cfByteIndex = -1) + { + ReportError(condition, message, byteIndex, cfByteIndex, ErrorLevel.Fatal); + } + + public void ReportReferenceError(string description, string reason, string refValue, long byteIndex) + { + string refPath = Path.GetFullPath(Path.Combine(this.basePath, refValue)).Replace('\\', '/'); + this.errorLog.AddReferenceError(refPath, description, reason, this.filename, this.basePath, GetLineNumber(byteIndex), refValue); + } + + public void ReportReferenceTypeError(string description, string refValue, long byteIndex, string refType, string actualType) + { + string refPath = Path.GetFullPath(Path.Combine(this.basePath, refValue)).Replace('\\', '/'); + this.errorLog.AddReferenceTypeError(refPath, description, this.filename, this.basePath, GetLineNumber(byteIndex), refValue, refType, actualType); + } + + public void ReportJsonException(Exception ex) + { + Match? match = JsonMessageRegex.Match(ex.Message); + if (match.Success) + { + string innerMessage = match.Groups[1].Captures[0].Value; + string message = $"JSON syntax error: {innerMessage}."; + int lineNumber = int.Parse(match.Groups[2].Captures[0].Value) + 1; + this.errorLog.AddError(ErrorLevel.Fatal, ErrorCondition.JsonInvalid, message, this.filename, lineNumber); + } + else + { + this.errorLog.AddError(ErrorLevel.Fatal, ErrorCondition.JsonInvalid, ex.Message, this.filename, -1); + } + } + + private int GetLineNumber(long byteIndex) + { + if (byteIndex < 0 || byteIndex >= this.byteStream.Length) + { + return 0; + } + + int lineNum = 1; + for (long ix = 0; ix < byteIndex; ++ix) + { + if (this.byteStream[ix] == '\n') + { + ++lineNum; + } + } + + return lineNum; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/FormInfo.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/FormInfo.cs new file mode 100644 index 0000000000..fcfb4eb507 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/FormInfo.cs @@ -0,0 +1,72 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + public record FormInfo( + SerializationFormat Format, + bool HasErrorResponse, + string? ErrorRespName, + ValueTracker? ErrorRespSchema, + SerializationFormat ErrorRespFormat, + string? HeaderInfoName, + ValueTracker? HeaderInfoSchema, + SerializationFormat HeaderInfoFormat, + string? HeaderCodeName, + ValueTracker? HeaderCodeSchema, + string? ServiceGroupId, + string? TopicPattern) + { + public static FormInfo? CreateFromForm(ErrorReporter errorReporter, TDForm? form, Dictionary>? schemaDefinitions) + { + if (form == null) + { + return null; + } + + SerializationFormat format = ThingSupport.ContentTypeToFormat(errorReporter, form.ContentType); + + bool hasErrorResponse = form.AdditionalResponses?.Elements?.Any(r => !(r.Value.Success?.Value.Value ?? false)) ?? false; + + ValueTracker? errorSchemaRef = form.AdditionalResponses?.Elements?.FirstOrDefault(r => !(r.Value.Success?.Value.Value ?? false) && r.Value.Schema != null); + var (errorRespName, errorRespSchema, errorRespFormat) = GetSchemaAndFormat(errorReporter, errorSchemaRef?.Value, form, schemaDefinitions); + + ValueTracker? headerSchemaRef = form.HeaderInfo?.Elements?.FirstOrDefault(r => r.Value.Schema != null); + var (headerInfoName, headerInfoSchema, headerInfoFormat) = GetSchemaAndFormat(errorReporter, headerSchemaRef?.Value, form, schemaDefinitions); + + ValueTracker? headerCodeSchema = GetSchema(form.HeaderCode?.Value?.Value, schemaDefinitions); + + return new FormInfo( + format, + hasErrorResponse, + errorRespName, + errorRespSchema, + errorRespFormat, + headerInfoName, + headerInfoSchema, + headerInfoFormat, + form.HeaderCode?.Value?.Value, + headerCodeSchema, + form.ServiceGroupId?.Value?.Value, + form.Topic?.Value?.Value); + } + + private static (string?, ValueTracker?, SerializationFormat) GetSchemaAndFormat(ErrorReporter errorReporter, TDSchemaReference? schemaRef, TDForm? form, Dictionary>? schemaDefinitions) + { + string? schemaName = schemaRef?.Schema?.Value?.Value; + SerializationFormat schemaFormat = ThingSupport.ContentTypeToFormat(errorReporter, schemaRef?.ContentType ?? form?.ContentType); + + ValueTracker? schema = null; + schemaDefinitions?.TryGetValue(schemaName ?? string.Empty, out schema); + + return (schemaName, schema, schemaFormat); + } + + private static ValueTracker? GetSchema(string? schemaName, Dictionary>? schemaDefinitions) + { + return schemaDefinitions?.TryGetValue(schemaName ?? string.Empty, out ValueTracker? schema) ?? false ? schema : null; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/FormatSupport.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/FormatSupport.cs new file mode 100644 index 0000000000..27c8045fce --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/FormatSupport.cs @@ -0,0 +1,38 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + public static class FormatSupport + { + public static string GetSerializerSubNamespace(this SerializationFormat format) + { + return format switch + { + SerializationFormat.Json => "JSON", + SerializationFormat.Raw => "raw", + SerializationFormat.Custom => "custom", + _ => string.Empty, + }; + } + + public static string GetSerializerClassName(this SerializationFormat format) + { + return format switch + { + SerializationFormat.Json => "Utf8JsonSerializer", + SerializationFormat.Raw => "PassthroughSerializer", + SerializationFormat.Custom => "ExternalSerializer", + _ => string.Empty, + }; + } + + public static EmptyTypeName GetEmptyTypeName(this SerializationFormat format) + { + return format switch + { + SerializationFormat.Json => EmptyTypeName.JsonInstance, + SerializationFormat.Raw => EmptyTypeName.RawInstance, + SerializationFormat.Custom => EmptyTypeName.CustomInstance, + _ => EmptyTypeName.JsonInstance, + }; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/FuncInfo.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/FuncInfo.cs new file mode 100644 index 0000000000..3a24632aa0 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/FuncInfo.cs @@ -0,0 +1,16 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Text.Json.Serialization; + + public class FuncInfo + { + [JsonPropertyName("in")] + public string[]? Input { get; set; } + + [JsonPropertyName("out")] + public string? Output { get; set; } + + [JsonPropertyName("capitalize")] + public bool Capitalize { get; set; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/GeneratedItem.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/GeneratedItem.cs new file mode 100644 index 0000000000..45e276a959 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/GeneratedItem.cs @@ -0,0 +1,4 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + public record GeneratedItem(string Content, string FileName, string FolderPath = ""); +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ITypeName.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ITypeName.cs new file mode 100644 index 0000000000..36bc3c5bf0 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ITypeName.cs @@ -0,0 +1,9 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + public interface ITypeName + { + public string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false); + + public string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/MqttTopicTokens.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/MqttTopicTokens.cs new file mode 100644 index 0000000000..ad987f42f8 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/MqttTopicTokens.cs @@ -0,0 +1,56 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + /// + /// Static class that defines string values of the replaceable components used in topic patterns. + /// + public static class MqttTopicTokens + { + /// + /// Prefix for a custom token. + /// + public const string PrefixCustom = "ex:"; + + /// + /// Token representing the ID of an Action executor, should be used only in Action topic patterns. + /// + public const string ActionExecutorId = "executorId"; + + /// + /// Token representing the MQTT Client ID of an Action invoker, should be used only in Action topic patterns. + /// + public const string ActionInvokerId = "invokerClientId"; + + /// + /// Token representing the ID of an Event sender, should be used only in Event topic patterns. + /// + public const string EventSenderId = "senderId"; + + /// + /// Token representing the ID of a Property maintainer, should be used only in Property topic patterns. + /// + public const string PropertyMaintainerId = "maintainerId"; + + /// + /// Token representing the MQTT Client ID of a Property consumer, should be used only in Property topic patterns. + /// + public const string PropertyConsumerId = "consumerClientId"; + + /// + /// Token representing a Property action, 'read' or 'write'. + /// + public const string PropertyAction = "action"; + + public static class PropertyActionValues + { + /// + /// Token value indicating a Property read action, 'readproperty' or 'readallproperties'. + /// + public const string Read = "read"; + + /// + /// Token value indicating a Property write action, 'writeproperty' or 'writemultipleproperties'. + /// + public const string Write = "write"; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ParsedThing.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ParsedThing.cs new file mode 100644 index 0000000000..90034641fc --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ParsedThing.cs @@ -0,0 +1,6 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using Azure.Iot.Operations.TDParser.Model; + + public record ParsedThing(TDThing Thing, string FileName, string DirectoryName, SchemaNamer SchemaNamer, ErrorReporter ErrorReporter); +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/RawTypeName.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/RawTypeName.cs new file mode 100644 index 0000000000..3184b307cc --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/RawTypeName.cs @@ -0,0 +1,53 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System; + + public class RawTypeName : ITypeName + { + public static RawTypeName Instance = new(); + + public string GetTypeName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, bool local = false) + { + if (suffix1 != null) + { + return "RawBytes" + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + else + { + return language switch + { + TargetLanguage.CSharp => "byte[]", + TargetLanguage.Rust => "Vec", + _ => throw new InvalidOperationException($"There is no {language} representation for {typeof(RawTypeName)}"), + }; + } + } + + public string GetFileName(TargetLanguage language, string? suffix1 = null, string? suffix2 = null, string? suffix3 = null) + { + if (suffix1 != null) + { + return language switch + { + TargetLanguage.CSharp => "RawBytes" + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3), + TargetLanguage.Rust => "raw_bytes" + GetSnakeSuffix(suffix1) + GetSnakeSuffix(suffix2) + GetSnakeSuffix(suffix3), + _ => throw new InvalidOperationException($"There is no {language} representation for {typeof(RawTypeName)}"), + }; + } + else + { + throw new InvalidOperationException($"{typeof(RawTypeName)} should not be used for a file name without a suffix"); + } + } + + private static string GetCapitalized(string? suffix) + { + return suffix == null ? string.Empty : char.ToUpperInvariant(suffix[0]) + suffix.Substring(1); + } + + private static string GetSnakeSuffix(string? suffix) + { + return suffix == null ? string.Empty : $"_{suffix}"; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ReservedCSharp.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ReservedCSharp.cs new file mode 100644 index 0000000000..48d07dd524 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ReservedCSharp.cs @@ -0,0 +1,133 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Collections.Generic; + + public static class ReservedCSharp + { + public static HashSet Keywords = new HashSet + { + "abstract", + "add", + "alias", + "allows", + "and", + "args", + "as", + "ascending", + "async", + "await", + "base", + "bool", + "break", + "by", + "byte", + "case", + "catch", + "char", + "checked", + "class", + "const", + "continue", + "decimal", + "default", + "delegate", + "descending", + "do", + "double", + "dynamic", + "else", + "enum", + "equals", + "event", + "explicit", + "extern", + "false", + "field", + "file", + "finally", + "fixed", + "float", + "for", + "foreach", + "from", + "get", + "global", + "goto", + "group", + "if", + "implicit", + "in", + "init", + "int", + "interface", + "internal", + "into", + "is", + "join", + "let", + "lock", + "long", + "managed", + "nameof", + "namespace", + "new", + "nint", + "not", + "notnull", + "nuint", + "null", + "object", + "on", + "operator", + "or", + "orderby", + "out", + "override", + "params", + "partial", + "private", + "protected", + "public", + "readonly", + "record", + "ref", + "remove", + "required", + "return", + "sbyte", + "scoped", + "sealed", + "select", + "set", + "short", + "sizeof", + "stackalloc", + "static", + "string", + "struct", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "uint", + "ulong", + "unchecked", + "unmanaged", + "unsafe", + "ushort", + "using", + "value", + "var", + "virtual", + "void", + "volatile", + "when", + "where", + "while", + "with", + "yield", + }; + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ReservedRust.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ReservedRust.cs new file mode 100644 index 0000000000..d7273c1dfb --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ReservedRust.cs @@ -0,0 +1,67 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Collections.Generic; + + public static class ReservedRust + { + public static HashSet Keywords = new HashSet + { + "abstract", + "as", + "async", + "await", + "become", + "box", + "break", + "const", + "continue", + "crate", + "do", + "dyn", + "else", + "enum", + "extern", + "false", + "final", + "fn", + "for", + "gen", + "if", + "impl", + "in", + "let", + "loop", + "macro", + "macro_rules", + "match", + "mod", + "move", + "mut", + "override", + "priv", + "pub", + "raw", + "ref", + "return", + "safe", + "self", + "Self", + "static", + "struct", + "super", + "trait", + "true", + "try", + "type", + "typeof", + "union", + "unsafe", + "unsized", + "use", + "virtual", + "where", + "while", + "yield", + }; + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/SchemaNameInfo.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/SchemaNameInfo.cs new file mode 100644 index 0000000000..2b5831bc39 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/SchemaNameInfo.cs @@ -0,0 +1,124 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Text.Json.Serialization; + + public class SchemaNameInfo + { + [JsonPropertyName("suppressTitles")] + public bool SuppressTitles { get; set; } + + [JsonPropertyName("constantsSchema")] + public string? ConstantsSchema { get; set; } + + [JsonPropertyName("aggregateEventName")] + public string? AggregateEventName { get; set; } + + [JsonPropertyName("aggregateEventSchema")] + public string? AggregateEventSchema { get; set; } + + [JsonPropertyName("aggregatePropName")] + public string? AggregatePropName { get; set; } + + [JsonPropertyName("aggregatePropSchema")] + public string? AggregatePropSchema { get; set; } + + [JsonPropertyName("aggregatePropWriteSchema")] + public string? AggregatePropWriteSchema { get; set; } + + [JsonPropertyName("aggregatePropReadRespSchema")] + public string? AggregatePropReadRespSchema { get; set; } + + [JsonPropertyName("aggregatePropWriteRespSchema")] + public string? AggregatePropWriteRespSchema { get; set; } + + [JsonPropertyName("aggregatePropReadErrSchema")] + public string? AggregatePropReadErrSchema { get; set; } + + [JsonPropertyName("aggregatePropWriteErrSchema")] + public string? AggregatePropWriteErrSchema { get; set; } + + [JsonPropertyName("readRequesterBinder")] + public string? ReadRequesterBinder { get; set; } + + [JsonPropertyName("readResponderBinder")] + public string? ReadResponderBinder { get; set; } + + [JsonPropertyName("writeRequesterBinder")] + public string? WriteRequesterBinder { get; set; } + + [JsonPropertyName("writeResponderBinder")] + public string? WriteResponderBinder { get; set; } + + [JsonPropertyName("aggregateReadRespValueField")] + public string? AggregateReadRespValueField { get; set; } + + [JsonPropertyName("aggregateRespErrorField")] + public string? AggregateRespErrorField { get; set; } + + [JsonPropertyName("eventSchema")] + public FuncInfo? EventSchema { get; set; } + + [JsonPropertyName("eventValueSchema")] + public FuncInfo? EventValueSchema { get; set; } + + [JsonPropertyName("eventSenderBinder")] + public FuncInfo? EventSenderBinder { get; set; } + + [JsonPropertyName("eventReceiverBinder")] + public FuncInfo? EventReceiverBinder { get; set; } + + [JsonPropertyName("propSchema")] + public FuncInfo? PropSchema { get; set; } + + [JsonPropertyName("writablePropSchema")] + public FuncInfo? WritablePropSchema { get; set; } + + [JsonPropertyName("propReadRespSchema")] + public FuncInfo? PropReadRespSchema { get; set; } + + [JsonPropertyName("propWriteRespSchema")] + public FuncInfo? PropWriteRespSchema { get; set; } + + [JsonPropertyName("propValueSchema")] + public FuncInfo? PropValueSchema { get; set; } + + [JsonPropertyName("propReadActName")] + public FuncInfo? PropReadActName { get; set; } + + [JsonPropertyName("propWriteActName")] + public FuncInfo? PropWriteActName { get; set; } + + [JsonPropertyName("propMaintainerBinder")] + public FuncInfo? PropMaintainerBinder { get; set; } + + [JsonPropertyName("propConsumerBinder")] + public FuncInfo? PropConsumerBinder { get; set; } + + [JsonPropertyName("actionInSchema")] + public FuncInfo? ActionInSchema { get; set; } + + [JsonPropertyName("actionOutSchema")] + public FuncInfo? ActionOutSchema { get; set; } + + [JsonPropertyName("actionRespSchema")] + public FuncInfo? ActionRespSchema { get; set; } + + [JsonPropertyName("actionExecutorBinder")] + public FuncInfo? ActionExecutorBinder { get; set; } + + [JsonPropertyName("actionInvokerBinder")] + public FuncInfo? ActionInvokerBinder { get; set; } + + [JsonPropertyName("backupSchemaName")] + public FuncInfo? BackupSchemaName { get; set; } + + [JsonPropertyName("propReadRespErrorField")] + public FuncInfo? PropReadRespErrorField { get; set; } + + [JsonPropertyName("propWriteRespErrorField")] + public FuncInfo? PropWriteRespErrorField { get; set; } + + [JsonPropertyName("actionRespErrorField")] + public FuncInfo? ActionRespErrorField { get; set; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/SchemaNamer.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/SchemaNamer.cs new file mode 100644 index 0000000000..90ccaecade --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/SchemaNamer.cs @@ -0,0 +1,125 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text.Json; + using Azure.Iot.Operations.TDParser.Model; + + public class SchemaNamer + { + private SchemaNameInfo? schemaNameInfo; + private bool suppressTitles; + + public SchemaNamer(string? schemaNameInfoText = null) + { + this.schemaNameInfo = schemaNameInfoText != null ? JsonSerializer.Deserialize(schemaNameInfoText) : null; + this.suppressTitles = this.schemaNameInfo?.SuppressTitles ?? false; + } + + public string ConstantsSchema { get => this.schemaNameInfo?.ConstantsSchema ?? "Constants"; } + + public string AggregateEventName { get => this.schemaNameInfo?.AggregateEventName ?? "Events"; } + + public string AggregateEventSchema { get => this.schemaNameInfo?.AggregateEventSchema ?? "EventCollection"; } + + public string AggregatePropName { get => this.schemaNameInfo?.AggregatePropName ?? "Properties"; } + + public string AggregatePropSchema { get => this.schemaNameInfo?.AggregatePropSchema ?? "PropertyCollection"; } + + public string AggregatePropWriteSchema { get => this.schemaNameInfo?.AggregatePropWriteSchema ?? "PropertyUpdate"; } + + public string AggregatePropReadRespSchema { get => this.schemaNameInfo?.AggregatePropReadRespSchema ?? "PropertyCollectionReadResponseSchema"; } + + public string AggregatePropWriteRespSchema { get => this.schemaNameInfo?.AggregatePropWriteRespSchema ?? "PropertyCollectionWriteResponseSchema"; } + + public string AggregatePropReadErrSchema { get => this.schemaNameInfo?.AggregatePropReadErrSchema ?? "PropertyCollectionReadError"; } + + public string AggregatePropWriteErrSchema { get => this.schemaNameInfo?.AggregatePropWriteErrSchema ?? "PropertyCollectionWriteError"; } + + public string ReadRequesterBinder { get => this.schemaNameInfo?.ReadRequesterBinder ?? "ReadRequester"; } + + public string ReadResponderBinder { get => this.schemaNameInfo?.ReadResponderBinder ?? "ReadResponder"; } + + public string WriteRequesterBinder { get => this.schemaNameInfo?.WriteRequesterBinder ?? "WriteRequester"; } + + public string WriteResponderBinder { get => this.schemaNameInfo?.WriteResponderBinder ?? "WriteResponder"; } + + public string AggregateReadRespValueField { get => this.schemaNameInfo?.AggregateReadRespValueField ?? "_properties"; } + + public string AggregateRespErrorField { get => this.schemaNameInfo?.AggregateRespErrorField ?? "_errors"; } + + public string GetEventSchema(string eventName) => Expand(null, this.schemaNameInfo?.EventSchema, $"{Cap(eventName)}Event", eventName); + + public string GetEventValueSchema(string eventName) => Expand(null, this.schemaNameInfo?.EventValueSchema, $"{Cap(eventName)}Value", eventName); + + public string GetEventSenderBinder(string eventSchema) => Expand(null, this.schemaNameInfo?.EventSenderBinder, $"{Cap(eventSchema)}Sender", eventSchema); + + public string GetEventReceiverBinder(string eventSchema) => Expand(null, this.schemaNameInfo?.EventReceiverBinder, $"{Cap(eventSchema)}Receiver", eventSchema); + + public string GetPropSchema(string propName) => Expand(null, this.schemaNameInfo?.PropSchema, $"{Cap(propName)}Property", propName); + + public string GetWritablePropSchema(string propName) => Expand(null, this.schemaNameInfo?.WritablePropSchema, $"{Cap(propName)}WritableProperty", propName); + + public string GetPropReadRespSchema(string propName) => Expand(null, this.schemaNameInfo?.PropReadRespSchema, $"{Cap(propName)}ReadResponseSchema", propName); + + public string GetPropWriteRespSchema(string propName) => Expand(null, this.schemaNameInfo?.PropWriteRespSchema, $"{Cap(propName)}WriteResponseSchema", propName); + + public string GetPropValueSchema(string propName) => Expand(null, this.schemaNameInfo?.PropValueSchema, $"Property{Cap(propName)}Value", propName); + + public string GetPropReadActName(string propName) => Expand(null, this.schemaNameInfo?.PropReadActName, $"Read{Cap(propName)}", propName); + + public string GetPropWriteActName(string propName) => Expand(null, this.schemaNameInfo?.PropWriteActName, $"Write{Cap(propName)}", propName); + + public string GetPropMaintainerBinder(string propSchema) => Expand(null, this.schemaNameInfo?.PropMaintainerBinder, $"{Cap(propSchema)}Maintainer", propSchema); + + public string GetPropConsumerBinder(string propSchema) => Expand(null, this.schemaNameInfo?.PropConsumerBinder, $"{Cap(propSchema)}Consumer", propSchema); + + public string GetActionInSchema(TDDataSchema? dataSchema, string actionName) => Expand(dataSchema, this.schemaNameInfo?.ActionInSchema, $"{Cap(actionName)}InputArguments", actionName); + + public string GetActionOutSchema(TDDataSchema? dataSchema, string actionName) => Expand(dataSchema, this.schemaNameInfo?.ActionOutSchema, $"{Cap(actionName)}OutputArguments", actionName); + + public string GetActionRespSchema(string actionName) => Expand(null, this.schemaNameInfo?.ActionRespSchema, $"{Cap(actionName)}ResponseSchema", actionName); + + public string GetActionExecutorBinder(string actionName) => Expand(null, this.schemaNameInfo?.ActionExecutorBinder, $"{Cap(actionName)}ActionExecutor", actionName); + + public string GetActionInvokerBinder(string actionName) => Expand(null, this.schemaNameInfo?.ActionInvokerBinder, $"{Cap(actionName)}ActionInvoker", actionName); + + public string GetPropReadRespErrorField(string propName, string errorSchemaName) => Expand(null, this.schemaNameInfo?.PropReadRespErrorField, "_error", propName, errorSchemaName); + + public string GetPropWriteRespErrorField(string propName, string errorSchemaName) => Expand(null, this.schemaNameInfo?.PropWriteRespErrorField, "_error", propName, errorSchemaName); + + public string GetActionRespErrorField(string actionName, string errorSchemaName) => Expand(null, this.schemaNameInfo?.ActionRespErrorField, "_error", actionName, errorSchemaName); + + public string GetBackupSchemaName(string parentSchemaName, string childName) => Expand(null, this.schemaNameInfo?.BackupSchemaName, $"{Cap(parentSchemaName)}{Cap(childName)}", parentSchemaName, childName); + + public string ApplyBackupSchemaName(string? title, string backupName) => ChooseTitleOrName(title, backupName); + + [return: NotNullIfNotNull(nameof(name))] + public string? ChooseTitleOrName(string? title, string? name) => this.suppressTitles ? name : title ?? name; + + private string Expand(TDDataSchema? dataSchema, FuncInfo? funcInfo, string defaultOut, params string[] args) + { + if (!this.suppressTitles && dataSchema?.Ref == null && dataSchema?.Title?.Value != null) + { + return dataSchema.Title.Value.Value; + } + + if (funcInfo == null || funcInfo.Output == null || funcInfo.Input == null || funcInfo.Input.Length < args.Length) + { + return defaultOut; + } + + string outString = funcInfo.Output; + foreach (int ix in Enumerable.Range(0, funcInfo.Input.Length)) + { + outString = outString.Replace($"{{{funcInfo.Input[ix]}}}", MaybeCap(args[ix], funcInfo.Capitalize)); + } + + return outString; + } + + private static string Cap(string input) => input.Length == 0 ? input : char.ToUpper(input[0]) + input.Substring(1); + + private static string MaybeCap(string input, bool capitalize) => capitalize ? Cap(input) : input; + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/SerializationFormat.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/SerializationFormat.cs new file mode 100644 index 0000000000..8fef4b4c6e --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/SerializationFormat.cs @@ -0,0 +1,10 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + public enum SerializationFormat + { + None, + Json, + Raw, + Custom, + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/TDValues.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/TDValues.cs new file mode 100644 index 0000000000..59ce3a1042 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/TDValues.cs @@ -0,0 +1,75 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Collections.Generic; + + public static class TDValues + { + public const string ContextUriWotTd = "https://www.w3.org/2022/wot/td/v1.1"; + public const string ContextUriAioProtocol = "http://azure.com/DigitalTwins/dtmi#"; + public const string ContextUriAioPlatform = "http://azure.com/IoT/operations/tm#"; + public const string ContextPrefixAioProtocol = "dtv"; + public const string ContextPrefixAioPlatform = "aov"; + + public const string RelationExtends = "tm:extends"; + public const string RelationReference = "aov:reference"; + public const string RelationTypedReference = "aov:typedReference"; + public const string RelationCapability = "aov:capability"; + public const string RelationComponent = "aov:component"; + public const string RelationSchemaNaming = "dtv:naming"; + + public const string ContentTypeTmJson = "application/tm+json"; + public const string ContentTypeJson = "application/json"; + public const string ContentTypeRaw = "application/octet-stream"; + public const string ContentTypeCustom = ""; + + public const string TypeThingModel = "tm:ThingModel"; + + public const string OpInvokeAction = "invokeaction"; + public const string OpReadProp = "readproperty"; + public const string OpWriteProp = "writeproperty"; + public const string OpSubEvent = "subscribeevent"; + public const string OpReadAllProps = "readallproperties"; + public const string OpWriteMultProps = "writemultipleproperties"; + public const string OpSubAllEvents = "subscribeallevents"; + + public const string TypeObject = "object"; + public const string TypeArray = "array"; + public const string TypeString = "string"; + public const string TypeNumber = "number"; + public const string TypeInteger = "integer"; + public const string TypeBoolean = "boolean"; + public const string TypeNull = "null"; + + public const string FormatDateTime = "date-time"; + public const string FormatDate = "date"; + public const string FormatTime = "time"; + public const string FormatUuid = "uuid"; + + public const string ContentEncodingBase64 = "base64"; + + public static readonly HashSet OpValues = new HashSet() { + OpReadProp, + OpWriteProp, + OpReadAllProps, + OpWriteMultProps, + OpSubAllEvents, + }; + + public static readonly HashSet TypeValues = new HashSet() { + TypeObject, + TypeArray, + TypeString, + TypeNumber, + TypeInteger, + TypeBoolean, + TypeNull, + }; + + public static readonly HashSet FormatValues = new HashSet() { + FormatDateTime, + FormatDate, + FormatTime, + FormatUuid, + }; + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/TargetLanguage.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/TargetLanguage.cs new file mode 100644 index 0000000000..b244acc13b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/TargetLanguage.cs @@ -0,0 +1,9 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + public enum TargetLanguage + { + CSharp, + Rust, + None, + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ThingSupport.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ThingSupport.cs new file mode 100644 index 0000000000..2a9b326461 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ThingSupport.cs @@ -0,0 +1,123 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + public static class ThingSupport + { + public static List GetSerializationFormats(ErrorReporter errorReporter, List things) + { + HashSet formats = new(); + + foreach (TDThing thing in things) + { + AddFormatsFromLinks(errorReporter, thing.Links?.Elements, formats); + AddFormatsFromForms(errorReporter, thing.Forms?.Elements, formats); + + foreach (KeyValuePair> actionKvp in thing.Actions?.Entries ?? new()) + { + TDAction? action = actionKvp.Value.Value; + if (action != null) + { + AddFormatsFromForms(errorReporter, action.Forms?.Elements, formats); + } + } + + foreach (KeyValuePair> propKvp in thing.Properties?.Entries ?? new()) + { + TDProperty? property = propKvp.Value.Value; + if (property != null) + { + AddFormatsFromForms(errorReporter, property.Forms?.Elements, formats); + } + } + + foreach (KeyValuePair> eventKvp in thing.Events?.Entries ?? new()) + { + TDEvent? eachEvent = eventKvp.Value.Value; + if (eachEvent != null) + { + AddFormatsFromForms(errorReporter, eachEvent.Forms?.Elements, formats); + } + } + } + + return formats.ToList(); + } + + public static SerializationFormat ContentTypeToFormat(ErrorReporter errorReporter, ValueTracker? contentType) + { + if (contentType?.Value == null) + { + return SerializationFormat.None; + } + + SerializationFormat format = ContentTypeToFormat(contentType.Value.Value); + + if (format == SerializationFormat.None) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Unsupported content type '{contentType.Value.Value}'.", contentType.TokenIndex); + } + + return format; + } + + public static SerializationFormat ContentTypeToFormat(string contentType) + { + return contentType switch + { + TDValues.ContentTypeJson => SerializationFormat.Json, + TDValues.ContentTypeRaw => SerializationFormat.Raw, + TDValues.ContentTypeCustom => SerializationFormat.Custom, + _ => SerializationFormat.None, + }; + } + + private static void AddFormatsFromForms(ErrorReporter errorReporter, IEnumerable>? forms, HashSet formats) + { + if (forms == null) + { + return; + } + + foreach (ValueTracker form in forms) + { + AddFormatFromContentType(errorReporter, form.Value.ContentType, formats); + AddFormatsFromSchemaReferences(errorReporter, form.Value.AdditionalResponses?.Elements, formats); + AddFormatsFromSchemaReferences(errorReporter, form.Value.HeaderInfo?.Elements, formats); + } + } + + private static void AddFormatsFromLinks(ErrorReporter errorReporter, IEnumerable>? links, HashSet formats) + { + if (links != null) + { + foreach (ValueTracker link in links) + { + AddFormatFromContentType(errorReporter, link.Value.Type, formats); + } + } + } + + private static void AddFormatsFromSchemaReferences(ErrorReporter errorReporter, IEnumerable>? schemaRefs, HashSet formats) + { + if (schemaRefs != null) + { + foreach (ValueTracker resp in schemaRefs) + { + AddFormatFromContentType(errorReporter, resp.Value.ContentType, formats); + } + } + } + + private static void AddFormatFromContentType(ErrorReporter errorReporter, ValueTracker? contentType, HashSet formats) + { + if (contentType?.Value != null) + { + formats.Add(ContentTypeToFormat(errorReporter, contentType)); + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ThingValidator.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ThingValidator.cs new file mode 100644 index 0000000000..f72e9254d0 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ThingValidator.cs @@ -0,0 +1,2652 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.RegularExpressions; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + public class ThingValidator + { + private const string Iso8601DurationExample = "P3Y6M4DT12H30M5S"; + private const string DecimalExample = "1234567890.0987654321"; + private const string AnArbitraryString = "Pretty12345Tricky67890"; + + private static readonly Regex TitleRegex = new(@"^[A-Z][A-Za-z0-9]*$", RegexOptions.Compiled); + private static readonly Regex RefCharRegex = new(@"^(?:[!#$&-;=?-\[\]_a-z~]|\%[0-9a-fA-F]{2})+$", RegexOptions.Compiled); + private static readonly Regex EnumValueRegex = new(@"^[A-Za-z][A-Za-z0-9_]*$", RegexOptions.Compiled); + + private ErrorReporter errorReporter; + + public ThingValidator(ErrorReporter errorReporter) + { + this.errorReporter = errorReporter; + } + + public bool TryValidateThng(TDThing thing, HashSet serializationFormats) + { + bool hasError = false; + + if (!TryValidateContext(thing.Context, out bool platContextPresent)) + { + hasError = true; + } + long contextTokenIndex = thing.Context?.TokenIndex ?? -1; + + if (!TryValidateType(thing.Type)) + { + hasError = true; + } + + if (!TryValidateTitle(thing.Title)) + { + hasError = true; + } + + if (!TryValidateCompositeAndEvent(thing.IsComposite, thing.IsEvent, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + if (!TryValidateTypeRef(thing.TypeRef, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + if (!TryValidateLinks(thing.Links, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + if (!TryValidateSchemaDefinitions(thing.SchemaDefinitions, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + if (!TryValidateRootForms(thing.Forms, thing.SchemaDefinitions, serializationFormats)) + { + hasError = true; + } + + if (!TryValidateActions(thing.Actions, thing.SchemaDefinitions, serializationFormats, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + if (!TryValidateProperties(thing.Properties, thing.SchemaDefinitions, serializationFormats, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + if (!TryValidateEvents(thing.Events, thing.SchemaDefinitions, serializationFormats, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + if (hasError) + { + return false; + } + + if (!TryValidateCrossFormConsistency(thing.Forms, thing.Actions)) + { + hasError = true; + } + + if (!TryValidateCrossFormConsistency(thing.Forms, thing.Properties)) + { + hasError = true; + } + + if (!TryValidateCrossFormConsistency(thing.Forms, thing.Events)) + { + hasError = true; + } + + if (!TryValidateThingPropertyNames(thing.PropertyNames)) + { + hasError = true; + } + + CheckSchemaDefinitionsCoverage(thing.SchemaDefinitions, thing.Actions, thing.Properties); + + if ((thing.Actions?.Entries?.Count ?? 0) == 0 && (thing.Properties?.Entries?.Count ?? 0) == 0 && (thing.Events?.Entries?.Count ?? 0) == 0) + { + errorReporter.ReportWarning("Thing Model has no actions, properties, or events defined.", -1); + } + + return !hasError; + } + + private bool TryValidateThingPropertyNames(Dictionary propertyNames) + { + bool hasError = false; + + foreach (KeyValuePair propertyName in propertyNames) + { + if (propertyName.Key != TDThing.ContextName && + propertyName.Key != TDThing.TypeName && + propertyName.Key != TDThing.TitleName && + propertyName.Key != TDThing.DescriptionName && + propertyName.Key != TDThing.LinksName && + propertyName.Key != TDThing.SchemaDefinitionsName && + propertyName.Key != TDThing.FormsName && + propertyName.Key != TDThing.OptionalName && + propertyName.Key != TDThing.ActionsName && + propertyName.Key != TDThing.PropertiesName && + propertyName.Key != TDThing.EventsName && + propertyName.Key != TDThing.IsCompositeName && + propertyName.Key != TDThing.IsEventName && + propertyName.Key != TDThing.TypeRefName) + { + if (propertyName.Key.Contains(':') && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioProtocol}:") && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioPlatform}:")) + { + errorReporter.ReportWarning($"Thing has unrecognized '{propertyName.Key}' property, which will be ignored.", propertyName.Value); + } + else + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Thing has '{propertyName.Key}' property, which is not supported.", propertyName.Value); + hasError = true; + } + } + } + + return !hasError; + } + + private void CheckSchemaDefinitionsCoverage(MapTracker? schemaDefinitions, MapTracker? actions, MapTracker? properties) + { + if (schemaDefinitions?.Entries == null) + { + return; + } + + HashSet unreferencedSchemaKeys = new(schemaDefinitions.Entries.Where(d => d.Value.Value.Const == null).Select(d => d.Key)); + + if (actions?.Entries != null) + { + foreach (ValueTracker action in actions.Entries.Values) + { + foreach (ValueTracker form in action.Value.Forms?.Elements ?? new()) + { + foreach (ValueTracker schemaReference in form.Value.AdditionalResponses?.Elements ?? new()) + { + if (schemaReference.Value.Schema?.Value != null) + { + unreferencedSchemaKeys.Remove(schemaReference.Value.Schema.Value.Value); + } + } + + foreach (ValueTracker schemaReference in form.Value.HeaderInfo?.Elements ?? new()) + { + if (schemaReference.Value.Schema?.Value != null) + { + unreferencedSchemaKeys.Remove(schemaReference.Value.Schema.Value.Value); + } + } + + if (form.Value.HeaderCode?.Value != null) + { + unreferencedSchemaKeys.Remove(form.Value.HeaderCode.Value.Value); + } + } + } + } + + if (properties?.Entries != null) + { + foreach (ValueTracker property in properties.Entries.Values) + { + foreach (ValueTracker form in property.Value.Forms?.Elements ?? new()) + { + foreach (ValueTracker schemaReference in form.Value.AdditionalResponses?.Elements ?? new()) + { + if (schemaReference.Value.Schema?.Value != null) + { + unreferencedSchemaKeys.Remove(schemaReference.Value.Schema.Value.Value); + } + } + } + } + } + + foreach (string unreferencedSchemaKey in unreferencedSchemaKeys) + { + errorReporter.ReportWarning($"'{TDThing.SchemaDefinitionsName}' key '{unreferencedSchemaKey}' has a value that is neither a constant declaration nor a type that is referenced by any action or property; definition will be ignored.", schemaDefinitions.Entries[unreferencedSchemaKey].TokenIndex); + } + } + + private bool TryValidateRootForms(ArrayTracker? forms, MapTracker? schemaDefinitions, HashSet serializationFormats) + { + if (!TryValidateForms(forms, FormsKind.Root, schemaDefinitions, out ValueTracker? contentType)) + { + return false; + } + + if (contentType != null) + { + serializationFormats.Add(ThingSupport.ContentTypeToFormat(contentType.Value.Value)); + } + + List> aggregateOps = forms?.Elements?.SelectMany(form => form.Value.Op?.Elements ?? new()).ToList() ?? new(); + ValueTracker? writeMultiOp = aggregateOps.FirstOrDefault(op => op.Value.Value == TDValues.OpWriteMultProps); + ValueTracker? readAllOp = aggregateOps.FirstOrDefault(op => op.Value.Value == TDValues.OpReadAllProps); + if (writeMultiOp != null && readAllOp == null) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"'{TDThing.FormsName}' array contains '{TDForm.OpName}' property with value '{TDValues.OpWriteMultProps}' but no '{TDForm.OpName}' property with value '{TDValues.OpReadAllProps}'.", writeMultiOp.TokenIndex, forms?.TokenIndex ?? -1); + return false; + } + + return true; + } + + private bool TryValidateCrossFormConsistency(ArrayTracker? rootForms, MapTracker? actions) + { + bool hasError = false; + + if (actions?.Entries != null) + { + foreach (KeyValuePair> action in actions.Entries) + { + if (!(action.Value.Value.Forms?.Elements?.Any(f => f.Value.Topic != null) ?? false)) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Action '{action.Key}' has no '{TDAction.FormsName}' element with a '{TDForm.TopicName}' property, so it cannot be invoked.", action.Value.TokenIndex); + hasError = true; + } + } + } + + return !hasError; + } + + private bool TryValidateCrossFormConsistency(ArrayTracker? rootForms, MapTracker? properties) + { + bool hasError = false; + + ValueTracker? readAllForm = rootForms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements != null && f.Value.Op.Elements.Any(op => op.Value.Value == TDValues.OpReadAllProps)); + ValueTracker? writeMultiForm = rootForms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements != null && f.Value.Op.Elements.Any(op => op.Value.Value == TDValues.OpWriteMultProps)); + + bool aggregateReadHasAdditionalResponses = (readAllForm?.Value.AdditionalResponses?.Elements?.Count ?? 0) > 0; + bool aggregateWriteHasAdditionalResponses = (writeMultiForm?.Value.AdditionalResponses?.Elements?.Count ?? 0) > 0; + + if (readAllForm != null) + { + if (properties?.Entries == null || properties.Entries.Count == 0) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Root-level form has '{TDForm.OpName}' property with value '{TDValues.OpReadAllProps}' to read the aggregation of all properties, but Thing Model has no properties defined.", + readAllForm.Value.Op!.Elements!.First(op => op.Value.Value == TDValues.OpReadAllProps).TokenIndex, + properties?.TokenIndex ?? -1); + hasError = true; + } + else if (aggregateReadHasAdditionalResponses && !properties.Entries.Any(p => p.Value.Value.Forms?.Elements?.Any(f => (f.Value.Op?.Elements?.Any(op => op.Value.Value == TDValues.OpReadProp) ?? true) && (f.Value.AdditionalResponses?.Elements?.Count ?? 0) > 0) ?? false)) + { + errorReporter.ReportWarning($"Root-level form has '{TDForm.OpName}' value of '{TDValues.OpReadAllProps}' and an '{TDForm.AdditionalResponsesName}' value; however, no readable '{TDThing.PropertiesName}' element has a form with an '{TDForm.AdditionalResponsesName}' value to aggregate.", + readAllForm.TokenIndex, + properties?.TokenIndex ?? -1); + } + } + + if (writeMultiForm != null) + { + if (properties?.Entries == null || properties.Entries.Count(p => p.Value.Value.ReadOnly?.Value.Value != true && (p.Value.Value.Forms?.Elements?.Any(f => f.Value.Op?.Elements?.Any(op => op.Value.Value == TDValues.OpWriteProp) ?? true) ?? true)) == 0) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Root-level form has '{TDForm.OpName}' property with value '{TDValues.OpWriteMultProps}' to write a selected aggregation of writable properties, but Thing Model has no writable properties.", + writeMultiForm.Value.Op!.Elements!.First(op => op.Value.Value == TDValues.OpWriteMultProps).TokenIndex, + properties?.TokenIndex ?? -1); + hasError = true; + } + else if (aggregateWriteHasAdditionalResponses && !properties.Entries.Any(p => p.Value.Value.ReadOnly?.Value.Value != true && (p.Value.Value.Forms?.Elements?.Any(f => (f.Value.Op?.Elements?.Any(op => op.Value.Value == TDValues.OpWriteProp) ?? true) && (f.Value.AdditionalResponses?.Elements?.Count ?? 0) > 0) ?? false))) + { + errorReporter.ReportWarning($"Root-level form has '{TDForm.OpName}' value of '{TDValues.OpWriteMultProps}' and an '{TDForm.AdditionalResponsesName}' value; however, no writable '{TDThing.PropertiesName}' element has a form with an '{TDForm.AdditionalResponsesName}' value to aggregate.", + writeMultiForm.TokenIndex, + properties?.TokenIndex ?? -1); + } + } + + if (properties?.Entries != null) + { + foreach (KeyValuePair> prop in properties.Entries) + { + if (prop.Value.Value.Forms?.Elements == null) + { + if (readAllForm == null) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Property '{prop.Key}' has no '{TDProperty.FormsName}' property, so it cannot be read individually; however, there is no root-level form with an '{TDForm.OpName}' property that has value '{TDValues.OpReadAllProps}', so this property also cannot be read in aggregate.", + prop.Value.TokenIndex, + rootForms?.TokenIndex ?? -1); + hasError = true; + } + } + else + { + foreach (ValueTracker form in prop.Value.Value.Forms.Elements) + { + bool propFormHasAdditionalResponses = (form.Value.AdditionalResponses?.Elements?.Count ?? 0) > 0; + + if (form.Value.Topic == null) + { + if (form.Value.Op == null) + { + if (readAllForm == null) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Property '{prop.Key}' has '{TDProperty.FormsName}' element with no '{TDForm.TopicName}' property, so it cannot be read individually; however, there is no root-level form with an '{TDForm.OpName}' property that has value '{TDValues.OpReadAllProps}', so this property also cannot be read in aggregate.", + form.TokenIndex, + rootForms?.TokenIndex ?? -1); + hasError = true; + } + else if (propFormHasAdditionalResponses && !aggregateReadHasAdditionalResponses && !aggregateWriteHasAdditionalResponses) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Property '{prop.Key}' has '{TDProperty.FormsName}' element with no '{TDForm.TopicName}' property, so its '{TDForm.AdditionalResponsesName}' value cannot be returned on an individual read or write, nor can it be returned on an aggregate read or write because no root-level form with '{TDForm.OpName}' value of '{TDValues.OpReadAllProps}' or '{TDValues.OpWriteMultProps}' has an '{TDForm.AdditionalResponsesName}' value.", + form.TokenIndex, + rootForms?.TokenIndex ?? -1); + hasError = true; + } + } + + if (form.Value.Op?.Elements != null && form.Value.Op.Elements.Any(op => op.Value.Value == TDValues.OpReadProp)) + { + if (readAllForm == null) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Property '{prop.Key}' has '{TDProperty.FormsName}' element with '{TDForm.OpName}' value of '{TDValues.OpReadProp}' but no '{TDForm.TopicName}' property, so it cannot be read individually; however, there is no root-level form with an '{TDForm.OpName}' property that has value '{TDValues.OpReadAllProps}', so this property also cannot be read in aggregate.", + form.TokenIndex, + rootForms?.TokenIndex ?? -1); + hasError = true; + } + else if (propFormHasAdditionalResponses && !aggregateReadHasAdditionalResponses) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Property '{prop.Key}' has '{TDProperty.FormsName}' element with '{TDForm.OpName}' value of '{TDValues.OpReadProp}' but no '{TDForm.TopicName}' property, so its '{TDForm.AdditionalResponsesName}' value cannot be returned on an individual read, nor can it be returned on an aggregate read because the root-level form with '{TDForm.OpName}' value of '{TDValues.OpReadAllProps}' has no '{TDForm.AdditionalResponsesName}' value.", + form.TokenIndex, + readAllForm.TokenIndex); + hasError = true; + } + } + + if (form.Value.Op?.Elements != null && form.Value.Op.Elements.Any(op => op.Value.Value == TDValues.OpWriteProp)) + { + if (writeMultiForm == null) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Property '{prop.Key}' has '{TDProperty.FormsName}' element with '{TDForm.OpName}' value of '{TDValues.OpWriteProp}' but no '{TDForm.TopicName}' property, so it cannot be written individually; however, there is no root-level form with an '{TDForm.OpName}' property that has value '{TDValues.OpWriteMultProps}', so this property also cannot be written in aggregate.", + form.TokenIndex, + rootForms?.TokenIndex ?? -1); + hasError = true; + } + else if (propFormHasAdditionalResponses && (writeMultiForm.Value.AdditionalResponses?.Elements?.Count ?? 0) == 0) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Property '{prop.Key}' has '{TDProperty.FormsName}' element with '{TDForm.OpName}' value of '{TDValues.OpWriteProp}' but no '{TDForm.TopicName}' property, so its '{TDForm.AdditionalResponsesName}' value cannot be returned on an individual write, nor can it be returned on an aggregate write because the root-level form with '{TDForm.OpName}' value of '{TDValues.OpWriteMultProps}' has no '{TDForm.AdditionalResponsesName}' value.", + form.TokenIndex, + writeMultiForm.TokenIndex); + hasError = true; + } + } + } + } + } + } + } + + return !hasError; + } + + private bool TryValidateCrossFormConsistency(ArrayTracker? rootForms, MapTracker? events) + { + ValueTracker? subAllForm = rootForms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements != null && f.Value.Op.Elements.Any(op => op.Value.Value == TDValues.OpSubAllEvents)); + + bool hasError = false; + + if (events?.Entries == null || events.Entries.Count == 0) + { + if (subAllForm != null) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Root-level form has '{TDForm.OpName}' property with value '{TDValues.OpSubAllEvents}' to subscribe to the aggregation of all events, but Thing Model has no events defined.", + subAllForm.Value.Op!.Elements!.First(op => op.Value.Value == TDValues.OpSubAllEvents).TokenIndex, + events?.TokenIndex ?? -1); + hasError = true; + } + } + else if (subAllForm == null) + { + foreach (KeyValuePair> evt in events.Entries) + { + if (!(evt.Value.Value.Forms?.Elements?.Any(f => f.Value.Topic != null) ?? false)) + { + errorReporter.ReportError(ErrorCondition.Unusable, $"Event '{evt.Key}' has no '{TDEvent.FormsName}' element with a '{TDForm.TopicName}' property, so it cannot be subscribed individually; however, there is no root-level form with an '{TDForm.OpName}' property that has value '{TDValues.OpSubAllEvents}', so this event also cannot be subscribed in aggregate.", + evt.Value.TokenIndex, + rootForms?.TokenIndex ?? -1); + hasError = true; + } + } + } + + return !hasError; + } + + private bool TryValidateContext(ArrayTracker? context, out bool platContextPresent) + { + platContextPresent = false; + bool protContextPresent = false; + bool tdContextPresent = false; + bool hasError = false; + + if (context?.Elements == null) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Thing Model is missing required '{TDThing.ContextName}' property.", -1); + return false; + } + + foreach (ValueTracker contextSpecifier in context.Elements) + { + if (contextSpecifier.Value?.Remote?.Value != null) + { + string remoteContext = contextSpecifier.Value.Remote.Value.Value; + if (remoteContext != TDValues.ContextUriWotTd) + { + errorReporter.ReportWarning($"Unrecognized remote {TDThing.ContextName} \"{remoteContext}\"; value will be ignored.", contextSpecifier.TokenIndex); + } + else + { + tdContextPresent = true; + } + } + else if (contextSpecifier.Value?.Local?.Entries != null) + { + foreach (KeyValuePair> localContext in contextSpecifier.Value.Local.Entries) + { + switch (localContext.Key) + { + case TDValues.ContextPrefixAioProtocol: + protContextPresent = true; + if (localContext.Value.Value.Value != TDValues.ContextUriAioProtocol) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Local {TDThing.ContextName} term \"{localContext.Key}\" has incorrect URI value \"{localContext.Value.Value.Value}\"; value must be \"{TDValues.ContextUriAioProtocol}\".", contextSpecifier.TokenIndex); + hasError = true; + } + break; + case TDValues.ContextPrefixAioPlatform: + platContextPresent = true; + if (localContext.Value.Value.Value != TDValues.ContextUriAioPlatform) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Local {TDThing.ContextName} term \"{localContext.Key}\" has incorrect URI value \"{localContext.Value.Value.Value}\"; value must be \"{TDValues.ContextUriAioPlatform}\".", contextSpecifier.TokenIndex); + hasError = true; + } + break; + default: + errorReporter.ReportWarning($"Unrecognized local {TDThing.ContextName} term \"{localContext.Key}\"; value will be ignored.", contextSpecifier.TokenIndex); + break; + } + } + } + } + + if (!tdContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Thing Model is missing required '{TDThing.ContextName}' remote URI \"{TDValues.ContextUriWotTd}\".", context.TokenIndex); + hasError = true; + } + + if (!protContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Thing Model is missing required '{TDThing.ContextName}' local term \"{TDValues.ContextPrefixAioProtocol}\" with URI value \"{TDValues.ContextUriAioProtocol}\".", context.TokenIndex); + hasError = true; + } + + return !hasError; + } + + private bool TryValidateType(ValueTracker? type) + { + if (type == null) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Thing Model is missing required '{TDThing.TypeName}' property.", -1); + return false; + } + + if (string.IsNullOrWhiteSpace(type.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Thing Model '{TDThing.TypeName}' property has empty value.", type.TokenIndex); + return false; + } + + if (type.Value.Value != TDValues.TypeThingModel) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"Thing Model '{TDThing.TypeName}' property value '{type.Value.Value}' is not correct; value must be `{TDValues.TypeThingModel}`.", type.TokenIndex); + return false; + } + + return true; + } + + private bool TryValidateTitle(ValueTracker? title) + { + if (title == null) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Thing Model is missing required '{TDThing.TitleName}' property.", -1); + return false; + } + + if (string.IsNullOrWhiteSpace(title.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Thing Model '{TDThing.TitleName}' property has empty value.", title.TokenIndex); + return false; + } + + if (!TitleRegex.IsMatch(title.Value.Value)) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"Thing Model '{TDThing.TitleName}' property value \"{title.Value.Value}\" does not conform to codegen type naming rules -- it must start with an uppercase letter and contain only alphanumeric characters", title.TokenIndex); + return false; + } + + return true; + } + + private bool TryValidateCompositeAndEvent(ValueTracker? isComposite, ValueTracker? isEvent, bool platContextPresent, long contextTokenIndex) + { + bool hasError = false; + + if (isComposite != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Thing Model '{TDThing.IsCompositeName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", isComposite.TokenIndex, contextTokenIndex); + hasError = true; + } + } + + if (isEvent != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Thing Model '{TDThing.IsEventName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", isEvent.TokenIndex, contextTokenIndex); + hasError = true; + } + } + + if (isComposite?.Value.Value == true && isEvent?.Value.Value == true) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Thing Model '{TDThing.IsCompositeName}' property cannot be true if '{TDThing.IsEventName}' property is true.", isComposite.TokenIndex, isEvent.TokenIndex); + hasError = true; + } + + return !hasError; + } + + private bool TryValidateTypeRef(ValueTracker? typeRef, bool platContextPresent, long contextTokenIndex) + { + if (typeRef == null) + { + return true; + } + + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Thing Model '{TDThing.TypeRefName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", typeRef.TokenIndex, contextTokenIndex); + return false; + } + + if (string.IsNullOrWhiteSpace(typeRef.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Thing Model '{TDThing.TypeRefName}' property has empty value.", typeRef.TokenIndex); + return false; + } + + return true; + } + + private bool TryValidateLinks(ArrayTracker? links, bool platContextPresent, long contextTokenIndex) + { + if (links?.Elements == null) + { + return true; + } + + bool hasError = false; + int relSchemaNamerCount = 0; + + foreach (ValueTracker link in links.Elements) + { + if (link.Value.Rel == null) + { + errorReporter.ReportWarning($"Link element is missing '{TDLink.RelName}' property; element will be ignored.", link.TokenIndex); + continue; + } + + if (link.Value.Rel.Value.Value != TDValues.RelationExtends && + link.Value.Rel.Value.Value != TDValues.RelationReference && + link.Value.Rel.Value.Value != TDValues.RelationTypedReference && + link.Value.Rel.Value.Value != TDValues.RelationCapability && + link.Value.Rel.Value.Value != TDValues.RelationComponent && + link.Value.Rel.Value.Value != TDValues.RelationSchemaNaming) + { + errorReporter.ReportWarning($"Link element '{TDLink.RelName}' property has unrecognized value '{link.Value.Rel.Value.Value}'; element will be ignored.", link.Value.Rel.TokenIndex); + continue; + } + + if (link.Value.Rel.Value.Value.StartsWith($"{TDValues.ContextPrefixAioPlatform}:") && !platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Link element '{TDLink.RelName}' property has value '{link.Value.Rel.Value.Value}', which requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", link.Value.Rel.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (link.Value.Rel.Value.Value == TDValues.RelationSchemaNaming) + { + relSchemaNamerCount++; + } + + string requiredContentType = link.Value.Rel.Value.Value switch + { + TDValues.RelationExtends => TDValues.ContentTypeTmJson, + TDValues.RelationReference => TDValues.ContentTypeTmJson, + TDValues.RelationTypedReference => TDValues.ContentTypeTmJson, + TDValues.RelationCapability => TDValues.ContentTypeTmJson, + TDValues.RelationComponent => TDValues.ContentTypeTmJson, + TDValues.RelationSchemaNaming => TDValues.ContentTypeJson, + _ => throw new NotSupportedException($"Unsupported '{TDLink.RelName}' property value '{link.Value.Rel.Value.Value}'"), + }; + + if (link.Value.RefType == null) + { + if (link.Value.Rel.Value.Value == TDValues.RelationTypedReference) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Link element with {TDLink.RelName}='{link.Value.Rel.Value.Value}' is missing required '{TDLink.RefTypeName}' property.", link.TokenIndex, link.Value.Rel.TokenIndex); + hasError = true; + } + } + else + { + if (link.Value.Rel.Value.Value != TDValues.RelationTypedReference) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Link element with {TDLink.RelName}='{link.Value.Rel.Value.Value}' does not support '{TDLink.RefTypeName}' property.", link.Value.RefType.TokenIndex, link.Value.Rel.TokenIndex); + hasError = true; + } + else if (string.IsNullOrWhiteSpace(link.Value.RefType.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Link element with {TDLink.RelName}='{link.Value.Rel.Value.Value}' has empty '{TDLink.RefTypeName}' property value.", link.Value.RefType.TokenIndex); + hasError = true; + } + } + + if (link.Value.Href == null) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Link element with {TDLink.RelName}='{link.Value.Rel.Value.Value}' is missing required '{TDLink.HrefName}' property.", link.TokenIndex); + hasError = true; + } + else if (string.IsNullOrWhiteSpace(link.Value.Href.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Link element with {TDLink.RelName}='{link.Value.Rel.Value.Value}' has empty '{TDLink.HrefName}' property value.", link.Value.Href.TokenIndex); + hasError = true; + } + + if (link.Value.Type == null) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Link element with {TDLink.RelName}='{link.Value.Rel.Value.Value}' is missing required '{TDLink.TypeName}' property.", link.TokenIndex); + hasError = true; + } + else if (string.IsNullOrWhiteSpace(link.Value.Type.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Link element with {TDLink.RelName}='{link.Value.Rel.Value.Value}' has empty '{TDLink.TypeName}' property value.", link.Value.Type.TokenIndex); + hasError = true; + } + else if (link.Value.Type.Value.Value != requiredContentType) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Link element with {TDLink.RelName}='{link.Value.Rel.Value.Value}' has '{TDLink.TypeName}' property with unsupported value '{link.Value.Type.Value.Value}'; expected '{requiredContentType}'.", link.Value.Type.TokenIndex, link.Value.Rel.TokenIndex); + hasError = true; + } + + foreach (KeyValuePair propertyName in link.Value.PropertyNames) + { + if (propertyName.Key != TDLink.HrefName && propertyName.Key != TDLink.TypeName && propertyName.Key != TDLink.RelName && propertyName.Key != TDLink.RefTypeName) + { + if (propertyName.Key.Contains(':') && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioProtocol}:") && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioPlatform}:")) + { + errorReporter.ReportWarning($"Link has unrecognized '{propertyName.Key}' property, which will be ignored.", propertyName.Value); + } + else + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Link has '{propertyName.Key}' property, which is not supported.", propertyName.Value); + hasError = true; + } + } + } + } + + if (relSchemaNamerCount > 1) + { + errorReporter.ReportError(ErrorCondition.Duplication, $"Thing Model has multiple links with '{TDLink.RelName}' property value '{TDValues.RelationSchemaNaming}'; only one is allowed.", links.TokenIndex); + hasError = true; + } + + return !hasError; + } + + private bool TryValidateSchemaDefinitions(MapTracker? schemaDefinitions, bool platContextPresent, long contextTokenIndex) + { + if (schemaDefinitions?.Entries == null) + { + return true; + } + + bool hasError = false; + + foreach (KeyValuePair> schemaDefinition in schemaDefinitions.Entries) + { + if (!TryValidateDataSchema(schemaDefinition.Value, null, platContextPresent, contextTokenIndex, DataSchemaKind.SchemaDefinition)) + { + hasError = true; + } + } + + return !hasError; + } + + private bool TryValidateActions(MapTracker? actions, MapTracker? schemaDefinitions, HashSet serializationFormats, bool platContextPresent, long contextTokenIndex) + { + if (actions?.Entries == null) + { + return true; + } + + bool hasError = false; + + foreach (KeyValuePair> action in actions.Entries) + { + if (!TryValidateAction(action.Key, action.Value, schemaDefinitions, out ValueTracker? contentType, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + else if (contentType != null) + { + serializationFormats.Add(ThingSupport.ContentTypeToFormat(contentType.Value.Value)); + } + } + + return !hasError; + } + + private bool TryValidateAction(string name, ValueTracker action, MapTracker? schemaDefinitions, out ValueTracker? contentType, bool platContextPresent, long contextTokenIndex) + { + if (!TryValidateForms(action.Value.Forms, FormsKind.Action, schemaDefinitions, out contentType)) + { + return false; + } + + bool hasError = false; + + if (action.Value.Namespace != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Action element '{TDAction.NamespaceName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", action.Value.Namespace.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (string.IsNullOrWhiteSpace(action.Value.Namespace.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Action element '{TDAction.NamespaceName}' property has empty value.", action.Value.Namespace.TokenIndex); + hasError = true; + } + } + + if (action.Value.MemberOf != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Action element '{TDAction.MemberOfName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", action.Value.MemberOf.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (string.IsNullOrWhiteSpace(action.Value.MemberOf.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Action element '{TDAction.MemberOfName}' property has empty value.", action.Value.MemberOf.TokenIndex); + hasError = true; + } + } + + if (action.Value.Input != null && !TryValidateActionDataSchema(action.Value.Input, TDAction.InputName, contentType, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + if (action.Value.Output != null && !TryValidateActionDataSchema(action.Value.Output, TDAction.OutputName, contentType, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + foreach (KeyValuePair propertyName in action.Value.PropertyNames) + { + if (propertyName.Key != TDAction.DescriptionName && propertyName.Key != TDAction.InputName && propertyName.Key != TDAction.OutputName && propertyName.Key != TDAction.IdempotentName && propertyName.Key != TDAction.SafeName && propertyName.Key != TDAction.FormsName && propertyName.Key != TDAction.NamespaceName && propertyName.Key != TDAction.MemberOfName) + { + if (propertyName.Key.Contains(':') && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioProtocol}:") && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioPlatform}:")) + { + errorReporter.ReportWarning($"Action '{name}' has unrecognized '{propertyName.Key}' property, which will be ignored.", propertyName.Value); + } + else + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Action '{name}' has '{propertyName.Key}' property, which is not supported.", propertyName.Value); + hasError = true; + } + } + } + + return !hasError; + } + + private bool TryValidateActionDataSchema(ValueTracker dataSchema, string propertyName, ValueTracker? contentType, bool platContextPresent, long contextTokenIndex) + where T : TDDataSchema, IDeserializable + { + bool isStructuredObject = dataSchema.Value.Type?.Value.Value == TDValues.TypeObject && dataSchema.Value.Properties != null; + bool isNull = dataSchema.Value.Type?.Value.Value == TDValues.TypeNull; + bool isReference = dataSchema.Value.Ref != null; + if (!isStructuredObject && !isNull && !isReference) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"'{TDThing.ActionsName}' element '{propertyName}' property must have a schema of (or a reference to) a structured object type, or no schema at all via a '{TDDataSchema.TypeName}' value of '{TDValues.TypeNull}'.", dataSchema.TokenIndex); + return false; + } + + return TryValidateDataSchema(dataSchema, null, platContextPresent, contextTokenIndex, DataSchemaKind.Action, contentType); + } + + private bool TryValidateProperties(MapTracker? properties, MapTracker? schemaDefinitions, HashSet serializationFormats, bool platContextPresent, long contextTokenIndex) + { + if (properties?.Entries == null) + { + return true; + } + + bool hasError = false; + + foreach (KeyValuePair> property in properties.Entries) + { + if (!TryValidateProperty(property.Key, property.Value, schemaDefinitions, out ValueTracker? contentType, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + else if (contentType != null) + { + serializationFormats.Add(ThingSupport.ContentTypeToFormat(contentType.Value.Value)); + } + } + + if (!hasError) + { + Dictionary> containsMap = properties.Entries.Where(e => e.Value.Value.Contains != null).ToDictionary(e => e.Key, e => e.Value.Value.Contains!); + Dictionary> containedInMap = properties.Entries.Where(e => e.Value.Value.ContainedIn != null).ToDictionary(e => e.Key, e => e.Value.Value.ContainedIn!); + + if (!TryValidateContainmentConsistency("Property", containsMap, containedInMap, properties.Entries.Keys, properties.TokenIndex)) + { + hasError = true; + } + } + + return !hasError; + } + + private bool TryValidateProperty(string name, ValueTracker property, MapTracker? schemaDefinitions, out ValueTracker? contentType, bool platContextPresent, long contextTokenIndex) + { + if (!TryValidatePropertyForms(name, property.Value.Forms, schemaDefinitions, property.Value.ReadOnly, out contentType)) + { + return false; + } + + bool hasError = false; + + if (property.Value.Namespace != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Property element '{TDProperty.NamespaceName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", property.Value.Namespace.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (string.IsNullOrWhiteSpace(property.Value.Namespace.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Property element '{TDProperty.NamespaceName}' property has empty value.", property.Value.Namespace.TokenIndex); + hasError = true; + } + } + + if (property.Value.Contains?.Elements != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Property element '{TDProperty.ContainsName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", property.Value.Contains.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (property.Value.Contains.Elements.Count == 0) + { + errorReporter.ReportError(ErrorCondition.ElementMissing, $"Property element '{TDProperty.ContainsName}' array value contains no elements.", property.Value.Contains.TokenIndex); + hasError = true; + } + } + + if (property.Value.ContainedIn != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Property element '{TDProperty.ContainedInName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", property.Value.ContainedIn.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (string.IsNullOrWhiteSpace(property.Value.ContainedIn.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Property element '{TDProperty.ContainedInName}' property has empty value.", property.Value.ContainedIn.TokenIndex); + hasError = true; + } + } + + if (!TryValidateDataSchema(property, (propName) => propName == TDProperty.ReadOnlyName || propName == TDProperty.PlaceholderName || propName == TDProperty.FormsName || propName == TDProperty.ContainsName || propName == TDProperty.ContainedInName || propName == TDProperty.NamespaceName, platContextPresent, contextTokenIndex, DataSchemaKind.Property, contentType)) + { + hasError = true; + } + + return !hasError; + } + + private bool TryValidatePropertyForms(string name, ArrayTracker? forms, MapTracker? schemaDefinitions, ValueTracker? readOnly, out ValueTracker? contentType) + { + bool isReadOnly = readOnly?.Value.Value == true; + + if (!TryValidateForms(forms, FormsKind.Property, schemaDefinitions, out contentType, isReadOnly)) + { + return false; + } + + bool hasError = false; + + List> aggregateOps = forms?.Elements?.SelectMany(form => form.Value.Op?.Elements ?? new())?.ToList() ?? new(); + ValueTracker? writeOp = aggregateOps.FirstOrDefault(op => op.Value.Value == TDValues.OpWriteProp); + ValueTracker? readOp = aggregateOps.FirstOrDefault(op => op.Value.Value == TDValues.OpReadProp); + + if (writeOp != null) + { + if (isReadOnly) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"'{TDProperty.FormsName}' array contains '{TDForm.OpName}' property with value '{TDValues.OpWriteProp}' but the property has '{TDProperty.ReadOnlyName}' true.", writeOp.TokenIndex, readOnly?.TokenIndex ?? -1); + hasError = true; + } + if (readOp == null) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"'{TDProperty.FormsName}' array contains '{TDForm.OpName}' property with value '{TDValues.OpWriteProp}' but no '{TDForm.OpName}' property with value '{TDValues.OpReadProp}'.", writeOp.TokenIndex, forms?.TokenIndex ?? -1); + hasError = true; + } + else + { + ValueTracker? topicalWriteForm = forms?.Elements?.FirstOrDefault(form => form.Value.Topic != null && (form.Value.Op?.Elements?.Any(op => op.Value.Value == TDValues.OpWriteProp) ?? false)); + bool hasTopicalReadForm = forms?.Elements?.Any(form => form.Value.Topic != null && (form.Value.Op?.Elements?.Any(op => op.Value.Value == TDValues.OpReadProp) ?? false)) ?? false; + if (topicalWriteForm != null && !hasTopicalReadForm) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"'{TDProperty.FormsName}' array contains entry with '{TDForm.TopicName}' property and '{TDForm.OpName}' property with value '{TDValues.OpWriteProp}' but no entry with '{TDForm.TopicName}' property and '{TDForm.OpName}' property with value '{TDValues.OpReadProp}'.", topicalWriteForm.TokenIndex, forms?.TokenIndex ?? -1); + hasError = true; + } + } + } + else if (readOp != null) + { + if (readOnly == null) + { + errorReporter.ReportWarning($"Property '{name}' is effectively read-only because the only '{TDForm.OpName}' value in '{TDProperty.FormsName}' is '{TDValues.OpReadProp}'; however, the property has no '{TDProperty.ReadOnlyName}' property with value true.", readOp.TokenIndex); + } + else if (readOnly.Value.Value == false) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Property '{name}' is effectively read-only because the only '{TDForm.OpName}' value in '{TDProperty.FormsName}' is '{TDValues.OpReadProp}'; however, the property has a '{TDProperty.ReadOnlyName}' property with value false.", readOp.TokenIndex, readOnly.TokenIndex); + hasError = true; + } + } + + return !hasError; + } + + private bool TryValidateEvents(MapTracker? evts, MapTracker? schemaDefinitions, HashSet serializationFormats, bool platContextPresent, long contextTokenIndex) + { + if (evts?.Entries == null) + { + return true; + } + + bool hasError = false; + + foreach (KeyValuePair> evt in evts.Entries) + { + if (!TryValidateEvent(evt.Key, evt.Value, schemaDefinitions, out ValueTracker? contentType, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + else if (contentType != null) + { + serializationFormats.Add(ThingSupport.ContentTypeToFormat(contentType.Value.Value)); + } + } + + if (!hasError) + { + Dictionary> containsMap = evts.Entries.Where(e => e.Value.Value.Contains != null).ToDictionary(e => e.Key, e => e.Value.Value.Contains!); + Dictionary> containedInMap = evts.Entries.Where(e => e.Value.Value.ContainedIn != null).ToDictionary(e => e.Key, e => e.Value.Value.ContainedIn!); + + if (!TryValidateContainmentConsistency("Property", containsMap, containedInMap, evts.Entries.Keys, evts.TokenIndex)) + { + hasError = true; + } + } + + return !hasError; + } + + private bool TryValidateEvent(string name, ValueTracker evt, MapTracker? schemaDefinitions, out ValueTracker? contentType, bool platContextPresent, long contextTokenIndex) + { + if (!TryValidateForms(evt.Value.Forms, FormsKind.Event, schemaDefinitions, out contentType)) + { + return false; + } + + bool hasError = false; + + if (evt.Value.Namespace != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Event element '{TDEvent.NamespaceName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", evt.Value.Namespace.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (string.IsNullOrWhiteSpace(evt.Value.Namespace.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Event element '{TDProperty.NamespaceName}' property has empty value.", evt.Value.Namespace.TokenIndex); + hasError = true; + } + } + + if (evt.Value.Contains?.Elements != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Event element '{TDEvent.ContainsName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", evt.Value.Contains.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (evt.Value.Contains.Elements.Count == 0) + { + errorReporter.ReportError(ErrorCondition.ElementMissing, $"Event element '{TDEvent.ContainsName}' array value contains no elements.", evt.Value.Contains.TokenIndex); + hasError = true; + } + } + + if (evt.Value.ContainedIn != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Event element '{TDEvent.ContainedInName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", evt.Value.ContainedIn.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (string.IsNullOrWhiteSpace(evt.Value.ContainedIn.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Event element '{TDEvent.ContainedInName}' property has empty value.", evt.Value.ContainedIn.TokenIndex); + hasError = true; + } + } + + if (evt.Value.Data != null && !TryValidateDataSchema(evt.Value.Data, null, platContextPresent, contextTokenIndex, DataSchemaKind.Event, contentType)) + { + hasError = true; + } + + foreach (KeyValuePair propertyName in evt.Value.PropertyNames) + { + if (propertyName.Key != TDEvent.DescriptionName && propertyName.Key != TDEvent.DataName && propertyName.Key != TDEvent.PlaceholderName && propertyName.Key != TDEvent.FormsName && propertyName.Key != TDEvent.ContainsName && propertyName.Key != TDEvent.ContainedInName && propertyName.Key != TDEvent.NamespaceName) + { + if (propertyName.Key.Contains(':') && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioProtocol}:") && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioPlatform}:")) + { + errorReporter.ReportWarning($"Event '{name}' has unrecognized '{propertyName.Key}' property, which will be ignored.", propertyName.Value); + } + else + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Event '{name}' has '{propertyName.Key}' property, which is not supported.", propertyName.Value); + hasError = true; + } + } + } + + return !hasError; + } + + private bool TryValidateForms(ArrayTracker? forms, FormsKind formsKind, MapTracker? schemaDefinitions, out ValueTracker? contentType, bool isReadOnly = false) + { + contentType = null; + + if (forms?.Elements == null) + { + return true; + } + + if (forms.Elements.Count == 0) + { + errorReporter.ReportError(ErrorCondition.ElementMissing, $"Property '{TDEvent.FormsName}' array value contains no elements; at least one form is required.", forms.TokenIndex); + return false; + } + + bool hasError = false; + + foreach (ValueTracker form in forms.Elements) + { + if (!TryValidateForm(form, formsKind, schemaDefinitions, out ValueTracker? formContentType, isReadOnly)) + { + hasError = true; + } + else if (formContentType != null) + { + if (contentType != null) + { + if (formContentType.Value.Value != contentType.Value.Value) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"'{TDCommon.FormsName}' array contains forms with different '{TDForm.ContentTypeName}' property values '{contentType.Value.Value}' and '{formContentType.Value.Value}'.", contentType.TokenIndex, formContentType.TokenIndex); + hasError = true; + } + } + else + { + contentType = formContentType; + } + } + } + + if (hasError) + { + return false; + } + + ValueTracker? oplessForm = forms.Elements.FirstOrDefault(f => f.Value.Op == null); + if (oplessForm != null && forms.Elements.Count > 1) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"'{TDCommon.FormsName}' array contains a form with no '{TDForm.OpName}' property, so it must be the only form in the array.", oplessForm.TokenIndex, forms.TokenIndex); + return false; + } + + List> aggregateOps = forms.Elements.SelectMany(form => form.Value.Op?.Elements ?? new()).ToList(); + + foreach (IGrouping> dupOpGroup in aggregateOps.GroupBy(op => op.Value.Value).Where(g => g.Count() > 1)) + { + errorReporter.ReportError(ErrorCondition.Duplication, $"'{TDCommon.FormsName}' array contains '{TDForm.OpName}' properties that duplicate value '{dupOpGroup.Key}'.", dupOpGroup.First().TokenIndex, dupOpGroup.Skip(1).First().TokenIndex); + hasError = true; + } + + return !hasError; + } + + private bool TryValidateForm(ValueTracker form, FormsKind formsKind, MapTracker? schemaDefinitions, out ValueTracker? contentType, bool isReadOnly) + { + bool hasError = false; + + if (form.Value.Op?.Elements == null) + { + if (formsKind == FormsKind.Root) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Root-level form is missing required '{TDForm.OpName}' property.", form.TokenIndex); + hasError = true; + } + } + else if (form.Value.Op.Elements.Count == 0) + { + errorReporter.ReportError(ErrorCondition.ElementMissing, $"Form '{TDForm.OpName}' property has no values.", form.Value.Op.TokenIndex); + hasError = true; + } + + if (form.Value.ContentType != null) + { + if (formsKind == FormsKind.Action || formsKind == FormsKind.Event) + { + if (form.Value.ContentType.Value.Value != TDValues.ContentTypeJson && form.Value.ContentType.Value.Value != TDValues.ContentTypeRaw && form.Value.ContentType.Value.Value != TDValues.ContentTypeCustom) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Form '{TDForm.ContentTypeName}' property has unsupported value '{form.Value.ContentType.Value.Value}'; for '{TDThing.ActionsName}' and '{TDThing.EventsName}' forms, supported values are '{TDValues.ContentTypeJson}', '{TDValues.ContentTypeRaw}', and '{TDValues.ContentTypeCustom}' (empty string).", form.Value.ContentType.TokenIndex); + hasError = true; + } + } + else + { + if (form.Value.ContentType.Value.Value != TDValues.ContentTypeJson) + { + string adjective = form.Value.ContentType.Value.Value == TDValues.ContentTypeRaw || form.Value.ContentType.Value.Value == TDValues.ContentTypeCustom ? "disallowed" : "unsupported"; + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Form '{TDForm.ContentTypeName}' property has {adjective} value '{form.Value.ContentType.Value.Value}'; for '{TDThing.PropertiesName}' and root-level forms, only '{TDValues.ContentTypeJson}' is supported.", form.Value.ContentType.TokenIndex); + hasError = true; + } + } + } + + if (form.Value.ServiceGroupId != null) + { + if (formsKind == FormsKind.Root && !(form.Value.Op?.Elements?.Any(op => op.Value.Value == TDValues.OpSubAllEvents) ?? false)) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"'{TDForm.ServiceGroupIdName}' property is not allowed in root-level '{TDCommon.FormsName}' property without an '{TDForm.OpName}' property value of '{TDValues.OpSubAllEvents}'.", form.Value.ServiceGroupId.TokenIndex); + hasError = true; + } + else if (formsKind == FormsKind.Property) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"'{TDForm.ServiceGroupIdName}' property is not allowed in '{TDProperty.FormsName}' property of a '{TDThing.PropertiesName}' element.", form.Value.ServiceGroupId.TokenIndex); + hasError = true; + } + else if (string.IsNullOrWhiteSpace(form.Value.ServiceGroupId.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Form '{TDForm.ServiceGroupIdName}' property has empty value.", form.Value.ServiceGroupId.TokenIndex); + hasError = true; + } + } + + if (hasError) + { + contentType = null; + return false; + } + + foreach (ValueTracker op in form.Value.Op?.Elements ?? new()) + { + if (string.IsNullOrWhiteSpace(op.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Form '{TDForm.OpName}' property has empty value.", op.TokenIndex); + hasError = true; + continue; + } + + switch (formsKind) + { + case FormsKind.Root: + if (op.Value.Value != TDValues.OpReadAllProps && op.Value.Value != TDValues.OpWriteMultProps && op.Value.Value != TDValues.OpSubAllEvents) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Form '{TDForm.OpName}' property has unsupported value '{op.Value.Value}' for a root-level form; supported values are '{TDValues.OpReadAllProps}', '{TDValues.OpWriteMultProps}', and '{TDValues.OpSubAllEvents}'.", op.TokenIndex); + hasError = true; + } + break; + case FormsKind.Property: + if (op.Value.Value != TDValues.OpReadProp && op.Value.Value != TDValues.OpWriteProp) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Form '{TDForm.OpName}' property has unsupported value '{op.Value.Value}' for a property form; supported values are '{TDValues.OpReadProp}' and '{TDValues.OpWriteProp}'.", op.TokenIndex); + hasError = true; + } + break; + case FormsKind.Action: + if (op.Value.Value != TDValues.OpInvokeAction) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Form '{TDForm.OpName}' property has unsupported value '{op.Value.Value}' for an action form; only supported value is '{TDValues.OpInvokeAction}'.", op.TokenIndex); + hasError = true; + } + break; + case FormsKind.Event: + if (op.Value.Value != TDValues.OpSubEvent) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Form '{TDForm.OpName}' property has unsupported value '{op.Value.Value}' for an event form; only supported value is '{TDValues.OpSubEvent}'.", op.TokenIndex); + hasError = true; + } + break; + } + } + + if (hasError) + { + contentType = null; + return false; + } + + bool hasOpRead = form.Value.Op?.Elements?.Any(op => op.Value.Value == TDValues.OpReadProp || op.Value.Value == TDValues.OpReadAllProps) ?? false; + bool hasOpWrite = form.Value.Op?.Elements?.Any(op => op.Value.Value == TDValues.OpWriteProp || op.Value.Value == TDValues.OpWriteMultProps) ?? false; + bool hasOpSub = form.Value.Op?.Elements?.Any(op => op.Value.Value == TDValues.OpSubEvent || op.Value.Value == TDValues.OpSubAllEvents) ?? false; + + if (form.Value.Op?.Elements != null) + { + foreach (IGrouping> dupOpGroup in form.Value.Op.Elements.GroupBy(op => op.Value.Value).Where(g => g.Count() > 1)) + { + errorReporter.ReportError(ErrorCondition.Duplication, $"Form '{TDForm.OpName}' property has duplicate value '{dupOpGroup.Key}'.", form.Value.Op.TokenIndex); + hasError = true; + } + + if (formsKind == FormsKind.Root) + { + if (hasOpRead && hasOpSub) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"A single form '{TDForm.OpName}' property cannot contain both '{TDValues.OpReadAllProps}' and '{TDValues.OpSubAllEvents}' values.", form.Value.Op.TokenIndex); + hasError = true; + } + if (hasOpWrite && hasOpSub) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Form '{TDForm.OpName}' property cannot contain both '{TDValues.OpWriteMultProps}' and '{TDValues.OpSubAllEvents}' values.", form.Value.Op.TokenIndex); + hasError = true; + } + } + } + + if (form.Value.Topic == null) + { + if (formsKind == FormsKind.Root) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Root-level form is missing required '{TDForm.TopicName}' property.", form.TokenIndex); + hasError = true; + } + } + else + { + if (!TryValidateTopic(form.Value.Topic, formsKind, hasOpRead, hasOpWrite, hasOpSub, isReadOnly)) + { + hasError = true; + } + + if (form.Value.ContentType == null) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Form missing '{TDForm.ContentTypeName}' property, which is required when '{TDForm.TopicName}' property present.", form.TokenIndex, form.Value.Topic.TokenIndex); + hasError = true; + } + } + + if (form.Value.HeaderCode != null) + { + if (formsKind != FormsKind.Action) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Form '{TDForm.HeaderCodeName}' property is permitted only in action forms.", form.Value.HeaderCode.TokenIndex); + hasError = true; + } + else if (string.IsNullOrWhiteSpace(form.Value.HeaderCode.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Form '{TDForm.HeaderCodeName}' property has empty value.", form.Value.HeaderCode.TokenIndex); + hasError = true; + } + else if (schemaDefinitions?.Entries == null) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"Form '{TDForm.HeaderCodeName}' property must refer to key in '{TDThing.SchemaDefinitionsName}' property, but TD has no '{TDThing.SchemaDefinitionsName}' property.", form.Value.HeaderCode.TokenIndex); + hasError = true; + } + else if (!schemaDefinitions.Entries.TryGetValue(form.Value.HeaderCode.Value.Value, out ValueTracker? dataSchema)) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"Form '{TDForm.HeaderCodeName}' property refers to non-existent key '{form.Value.HeaderCode.Value.Value}' in '{TDThing.SchemaDefinitionsName}' property.", form.Value.HeaderCode.TokenIndex, schemaDefinitions.TokenIndex); + hasError = true; + } + else if (dataSchema.Value.Type?.Value.Value != TDValues.TypeString || dataSchema.Value.Enum == null) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"Form '{TDForm.HeaderCodeName}' property refers to '{TDThing.SchemaDefinitionsName}' key '{form.Value.HeaderCode.Value.Value}', but this is not a string enum.", form.Value.HeaderCode.TokenIndex, dataSchema.TokenIndex); + hasError = true; + } + } + + if (form.Value.AdditionalResponses?.Elements != null) + { + if (formsKind == FormsKind.Event) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Form '{TDForm.AdditionalResponsesName}' property is not permitted in an event form.", form.Value.AdditionalResponses.TokenIndex); + hasError = true; + } + else if (formsKind == FormsKind.Root && !form.Value.Op!.Elements!.Any(op => op.Value.Value == TDValues.OpReadAllProps || op.Value.Value == TDValues.OpWriteMultProps)) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Form '{TDForm.AdditionalResponsesName}' property is not permitted in a root form without an '{TDForm.OpName}' property value of '{TDValues.OpReadAllProps}' or '{TDValues.OpWriteMultProps}'.", form.Value.AdditionalResponses.TokenIndex, form.Value.Op.TokenIndex); + hasError = true; + } + else if (!TryValidateSchemaReferences(form.Value.AdditionalResponses, formsKind, TDForm.AdditionalResponsesName, schemaDefinitions)) + { + hasError = true; + } + } + + if (form.Value.HeaderInfo?.Elements != null) + { + if (formsKind != FormsKind.Action) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Form '{TDForm.HeaderInfoName}' property is permitted only in an action form.", form.Value.HeaderInfo.TokenIndex); + hasError = true; + } + else if (!TryValidateSchemaReferences(form.Value.HeaderInfo, formsKind, TDForm.HeaderInfoName, schemaDefinitions)) + { + hasError = true; + } + } + + foreach (KeyValuePair propertyName in form.Value.PropertyNames) + { + if (propertyName.Key != TDForm.ContentTypeName && propertyName.Key != TDForm.AdditionalResponsesName && propertyName.Key != TDForm.HeaderInfoName && propertyName.Key != TDForm.HeaderCodeName && propertyName.Key != TDForm.ServiceGroupIdName && propertyName.Key != TDForm.TopicName && propertyName.Key != TDForm.OpName) + { + if (propertyName.Key.Contains(':') && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioProtocol}:") && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioPlatform}:")) + { + errorReporter.ReportWarning($"Form has unrecognized '{propertyName.Key}' property, which will be ignored.", propertyName.Value); + } + else + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Form has '{propertyName.Key}' property, which is not supported.", propertyName.Value); + hasError = true; + } + } + } + + contentType = form.Value.ContentType; + return !hasError; + } + + private bool TryValidateContainmentConsistency(string affordanceType, Dictionary> containsMap, Dictionary> containedInMap, ICollection affordanceKeys, long affordanceTokenIndex) + { + bool hasError = false; + + foreach (KeyValuePair> containsEntry in containsMap) + { + foreach (ValueTracker containedKey in containsEntry.Value.Elements ?? []) + { + if (!affordanceKeys.Contains(containedKey.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"{affordanceType} '{containsEntry.Key}' declares it contains {affordanceType} '{containedKey.Value.Value}', but no such {affordanceType} in model.", containedKey.TokenIndex, affordanceTokenIndex); + hasError = true; + } + else if (containedInMap.TryGetValue(containedKey.Value.Value, out ValueTracker? container)) + { + if (container.Value.Value != containsEntry.Key) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"{affordanceType} '{containsEntry.Key}' declares it contains {affordanceType} '{containedKey.Value.Value}', but that {affordanceType} declares it is contained in '{container.Value.Value}'.", containedKey.TokenIndex, container.TokenIndex); + hasError = true; + } + } + } + } + + foreach (KeyValuePair> containedInEntry in containedInMap) + { + if (!affordanceKeys.Contains(containedInEntry.Value.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"{affordanceType} '{containedInEntry.Key}' declares it is contained in {affordanceType} '{containedInEntry.Value.Value.Value}', but no such {affordanceType} in model.", containedInEntry.Value.TokenIndex, affordanceTokenIndex); + hasError = true; + } + else if (containsMap.TryGetValue(containedInEntry.Value.Value.Value, out ArrayTracker? contains)) + { + if (!(contains.Elements?.Any(e => e.Value.Value == containedInEntry.Key) ?? false)) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"{affordanceType} '{containedInEntry.Key}' declares it is contained in {affordanceType} '{containedInEntry.Value.Value.Value}', but that {affordanceType} declares a contained set that does not include '{containedInEntry.Key}'.", containedInEntry.Value.TokenIndex, contains.TokenIndex); + hasError = true; + } + } + } + + return !hasError; + } + + private bool TryValidateTopic(ValueTracker topic, FormsKind formsKind, bool hasOpRead, bool hasOpWrite, bool hasOpSub, bool isReadOnly) + { + FormsKind effectiveFormsKind = formsKind switch + { + FormsKind.Action => FormsKind.Action, + FormsKind.Property => FormsKind.Property, + FormsKind.Event => FormsKind.Event, + FormsKind.Root when hasOpRead || hasOpWrite => FormsKind.Property, + FormsKind.Root when hasOpSub => FormsKind.Event, + _ => FormsKind.Root, + }; + + bool hasError = false; + + if (topic.Value.Value.Length == 0) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Form '{TDForm.TopicName}' property has empty value.", topic.TokenIndex); + return false; + } + + if (topic.Value.Value.StartsWith('$')) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Form '{TDForm.TopicName}' property value starts with reserved character '$'.", topic.TokenIndex); + hasError = true; + } + + bool actionTokenPresent = false; + foreach (string level in topic.Value.Value.Split('/')) + { + if (level.Length == 0) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Form '{TDForm.TopicName}' property has value containing empty topic level.", topic.TokenIndex); + hasError = true; + } + else if (level.StartsWith('{') && level.EndsWith('}')) + { + string token = level[1..^1]; + if (token.Length == 0) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Form '{TDForm.TopicName}' property has value containing empty token '{{}}'.", topic.TokenIndex); + hasError = true; + } + else if (token.StartsWith($"{MqttTopicTokens.PrefixCustom}")) + { + string exToken = token[3..]; + if (exToken.Length == 0) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Form '{TDForm.TopicName}' property has value containing empty custom token '{{{MqttTopicTokens.PrefixCustom}}}'.", topic.TokenIndex); + hasError = true; + } + else if (!exToken.All(c => char.IsAsciiLetter(c))) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Form '{TDForm.TopicName}' property has value containing custom token '{{{token}}}' that contains invalid character(s); only ASCII letters are allowed after the '{MqttTopicTokens.PrefixCustom}' prefix.", topic.TokenIndex); + hasError = true; + } + } + else + { + switch (effectiveFormsKind) + { + case FormsKind.Action: + if (token != MqttTopicTokens.ActionInvokerId && token != MqttTopicTokens.ActionExecutorId) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Form '{TDForm.TopicName}' property has value containing token '{{{token}}}' that is not valid in an action topic; only '{{{MqttTopicTokens.ActionInvokerId}}}' and '{{{MqttTopicTokens.ActionExecutorId}}}' are allowed unless token starts with '{MqttTopicTokens.PrefixCustom}'.", topic.TokenIndex); + hasError = true; + } + break; + case FormsKind.Property: + if (token == MqttTopicTokens.PropertyAction) + { + actionTokenPresent = true; + } + else if (token != MqttTopicTokens.PropertyConsumerId && token != MqttTopicTokens.PropertyMaintainerId) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Form '{TDForm.TopicName}' property has value containing token '{{{token}}}' that is not valid in a property topic; only '{{{MqttTopicTokens.PropertyAction}}}', '{{{MqttTopicTokens.PropertyConsumerId}}}', and '{{{MqttTopicTokens.PropertyMaintainerId}}}' are allowed unless token starts with '{MqttTopicTokens.PrefixCustom}'.", topic.TokenIndex); + hasError = true; + } + break; + case FormsKind.Event: + if (token != MqttTopicTokens.EventSenderId) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Form '{TDForm.TopicName}' property has value containing token '{{{token}}}' that is not valid in an event topic; only '{{{MqttTopicTokens.EventSenderId}}}' is allowed unless token starts with '{MqttTopicTokens.PrefixCustom}'.", topic.TokenIndex); + hasError = true; + } + break; + } + } + } + else + { + if (level.Any(c => c is < '!' or > '~' or '+' or '#' or '{' or '}')) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Form '{TDForm.TopicName}' property has value containing non-token topic level '{level}' that contains invalid character(s); only printable ASCII characters not including space, '\"', '+', '#', '{{', or '}}' are allowed.", topic.TokenIndex); + hasError = true; + } + } + } + + if (effectiveFormsKind == FormsKind.Property) + { + if (actionTokenPresent) + { + string readResolvedTopic = topic.Value.Value.Replace($"{{{MqttTopicTokens.PropertyAction}}}", MqttTopicTokens.PropertyActionValues.Read); + string writeResolvedTopic = topic.Value.Value.Replace($"{{{MqttTopicTokens.PropertyAction}}}", MqttTopicTokens.PropertyActionValues.Write); + + if (hasOpRead) + { + errorReporter.RegisterTopicInThing(readResolvedTopic, topic.TokenIndex, topic.Value.Value); + } + + if (hasOpWrite) + { + errorReporter.RegisterTopicInThing(writeResolvedTopic, topic.TokenIndex, topic.Value.Value); + } + + if (!hasOpRead && !hasOpWrite) + { + errorReporter.RegisterTopicInThing(readResolvedTopic, topic.TokenIndex, topic.Value.Value); + if (!isReadOnly) + { + errorReporter.RegisterTopicInThing(writeResolvedTopic, topic.TokenIndex, topic.Value.Value); + } + } + } + else + { + if ((hasOpRead && hasOpWrite) || (!hasOpRead && !hasOpWrite && !isReadOnly)) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Form '{TDForm.TopicName}' property value is missing required '{{{MqttTopicTokens.PropertyAction}}}' token for a property form that supports both read and write operations.", topic.TokenIndex); + hasError = true; + } + else + { + errorReporter.RegisterTopicInThing(topic.Value.Value, topic.TokenIndex, topic.Value.Value); + } + } + } + else + { + errorReporter.RegisterTopicInThing(topic.Value.Value, topic.TokenIndex, topic.Value.Value); + } + + return !hasError; + } + + private bool TryValidateSchemaReferences(ArrayTracker schemaReferences, FormsKind formsKind, string propertyName, MapTracker? schemaDefinitions) + { + bool hasError = false; + + foreach (ValueTracker schemaReference in schemaReferences.Elements!) + { + if (!TryValidateSchemaReference(schemaReference, formsKind, propertyName, schemaDefinitions)) + { + hasError = true; + } + } + + if (schemaReferences.Elements.Count > 1) + { + errorReporter.ReportError(ErrorCondition.ElementsPlural, $"No more than one element is permitted in '{propertyName}' array.", schemaReferences.TokenIndex); + hasError = true; + } + + return !hasError; + } + + private bool TryValidateSchemaReference(ValueTracker schemaReference, FormsKind formsKind, string parentPropName, MapTracker? schemaDefinitions) + { + bool hasError = false; + + if (schemaReference.Value.ContentType == null) + { + if (parentPropName == TDForm.HeaderInfoName) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"'{parentPropName}' element is missing required '{TDSchemaReference.ContentTypeName}' property.", schemaReference.TokenIndex); + hasError = true; + } + } + else if (string.IsNullOrWhiteSpace(schemaReference.Value.ContentType.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"'{parentPropName}' element has empty '{TDSchemaReference.ContentTypeName}' property value.", schemaReference.Value.ContentType.TokenIndex); + hasError = true; + } + else if (schemaReference.Value.ContentType.Value.Value != TDValues.ContentTypeJson) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"'{parentPropName}' element has '{TDSchemaReference.ContentTypeName}' property with unsupported value '{schemaReference.Value.ContentType.Value.Value}'.", schemaReference.Value.ContentType.TokenIndex); + hasError = true; + } + + if (schemaReference.Value.Success == null) + { + if (parentPropName == TDForm.AdditionalResponsesName) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"'{parentPropName}' element is missing required '{TDSchemaReference.SuccessName}' property.", schemaReference.TokenIndex); + hasError = true; + } + } + else if (schemaReference.Value.Success.Value.Value == true) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"'{parentPropName}' element has '{TDSchemaReference.SuccessName}' property value of true, which is not supported.", schemaReference.Value.Success.TokenIndex); + hasError = true; + } + + if (schemaReference.Value.Schema == null) + { + if (formsKind != FormsKind.Root) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"'{parentPropName}' element is missing '{TDSchemaReference.SchemaName}' property, which is required except within root-level '{TDThing.FormsName}' elements.", schemaReference.TokenIndex); + hasError = true; + } + } + else if (formsKind == FormsKind.Root) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"'{parentPropName}' element has '{TDSchemaReference.SchemaName}' property, which is not allowed within root-level '{TDThing.FormsName}' element because schema is automatically composed from schema definitions in affordance '{TDThing.FormsName}' elements.", schemaReference.Value.Schema.TokenIndex); + hasError = true; + } + else if (string.IsNullOrWhiteSpace(schemaReference.Value.Schema.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"'{parentPropName}' element '{TDSchemaReference.SchemaName}' property has empty value.", schemaReference.Value.Schema.TokenIndex); + hasError = true; + } + else if (schemaDefinitions?.Entries == null) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"'{parentPropName}' element '{TDSchemaReference.SchemaName}' property must refer to key in '{TDThing.SchemaDefinitionsName}' property, but TD has no '{TDThing.SchemaDefinitionsName}' property.", schemaReference.Value.Schema.TokenIndex); + hasError = true; + } + else if (!schemaDefinitions.Entries.TryGetValue(schemaReference.Value.Schema.Value.Value, out ValueTracker? dataSchema)) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"'{parentPropName}' element '{TDSchemaReference.SchemaName}' property refers to non-existent key '{schemaReference.Value.Schema.Value.Value}' in '{TDThing.SchemaDefinitionsName}' property.", schemaReference.Value.Schema.TokenIndex, schemaDefinitions.TokenIndex); + hasError = true; + } + else if (dataSchema.Value.Type?.Value.Value != TDValues.TypeObject) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"'{parentPropName}' element '{TDSchemaReference.SchemaName}' property refers to '{TDThing.SchemaDefinitionsName}' key '{schemaReference.Value.Schema.Value.Value}', but this is not a structured object definition.", schemaReference.Value.Schema.TokenIndex, dataSchema.TokenIndex); + hasError = true; + } + else if (dataSchema.Value.Properties == null) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"'{parentPropName}' element '{TDSchemaReference.SchemaName}' property refers to '{TDThing.SchemaDefinitionsName}' key '{schemaReference.Value.Schema.Value.Value}', but this defines a map, whereas a structured object is required.", schemaReference.Value.Schema.TokenIndex, dataSchema.TokenIndex); + hasError = true; + } + + foreach (KeyValuePair propertyName in schemaReference.Value.PropertyNames) + { + if (propertyName.Key != TDSchemaReference.SuccessName && propertyName.Key != TDSchemaReference.ContentTypeName && propertyName.Key != TDSchemaReference.SchemaName) + { + if (propertyName.Key.Contains(':') && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioProtocol}:") && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioPlatform}:")) + { + errorReporter.ReportWarning($"Schema reference has unrecognized '{propertyName.Key}' property, which will be ignored.", propertyName.Value); + } + else + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Schema reference has '{propertyName.Key}' property, which is not supported.", propertyName.Value); + hasError = true; + } + } + } + + return !hasError; + } + + private bool TryValidateDataSchema(ValueTracker dataSchema, Func? propertyApprover, bool platContextPresent, long contextTokenIndex, DataSchemaKind dataSchemaKind = DataSchemaKind.Undistinguished, ValueTracker? contentType = null) + where T : TDDataSchema, IDeserializable + { + if (dataSchema.Value.Ref != null && dataSchemaKind != DataSchemaKind.Action && dataSchemaKind != DataSchemaKind.Property && dataSchemaKind != DataSchemaKind.Event) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"The '{TDDataSchema.RefName}' property is permitted only in the first level of an affordance schema definition ('{TDThing.PropertiesName}', '{TDThing.EventsName}'/'{TDEvent.DataName}', '{TDThing.ActionsName}'/'{TDAction.InputName}', and '{TDThing.ActionsName}'/'{TDAction.OutputName}').", dataSchema.Value.Ref.TokenIndex); + return false; + } + + if (dataSchema.Value.Ref == null && dataSchema.Value.Type == null) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Data schema must have either '{TDDataSchema.RefName}' or '{TDDataSchema.TypeName}' property.", dataSchema.TokenIndex); + return false; + } + + if (dataSchema.Value.Ref != null && dataSchema.Value.Type != null) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Data schema cannot have both '{TDDataSchema.RefName}' and '{TDDataSchema.TypeName}' properties.", dataSchema.Value.Ref.TokenIndex, dataSchema.Value.Type.TokenIndex); + return false; + } + + if (dataSchema.Value.TypeRef != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Data schema '{TDDataSchema.TypeRefName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", dataSchema.Value.TypeRef.TokenIndex, contextTokenIndex); + return false; + } + + if (string.IsNullOrWhiteSpace(dataSchema.Value.TypeRef.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Data schema '{TDThing.TypeRefName}' property has empty value.", dataSchema.Value.TypeRef.TokenIndex); + return false; + } + } + + bool contentTypeIsRawOrCustom = contentType?.Value.Value == TDValues.ContentTypeRaw || contentType?.Value.Value == TDValues.ContentTypeCustom; + + if (dataSchema.Value.Ref != null) + { + if (contentTypeIsRawOrCustom) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Data schema with '{TDDataSchema.RefName}' property is not permitted in an affordance with a form that specifies '{TDForm.ContentTypeName}' of '{contentType!.Value.Value}'.", dataSchema.Value.Ref.TokenIndex, contentType!.TokenIndex); + return false; + } + else + { + return TryValidateReferenceDataSchema(dataSchema, propertyApprover); + } + } + + if (contentTypeIsRawOrCustom && dataSchema.Value.Type!.Value.Value != TDValues.TypeNull) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Data schema with '{TDDataSchema.TypeName}' of '{dataSchema.Value.Type.Value.Value}' is not permitted in an affordance with a form that specifies '{TDForm.ContentTypeName}' of '{contentType!.Value.Value}'; only '{TDDataSchema.TypeName}' of '{TDValues.TypeNull}' is permitted.", dataSchema.Value.Type.TokenIndex, contentType!.TokenIndex); + return false; + } + + switch (dataSchema.Value.Type!.Value.Value) + { + case TDValues.TypeObject: + return TryValidateObjectDataSchema(dataSchema, dataSchemaKind, propertyApprover, platContextPresent, contextTokenIndex); + case TDValues.TypeArray: + return TryValidateArrayDataSchema(dataSchema, propertyApprover, platContextPresent, contextTokenIndex); + case TDValues.TypeString: + return TryValidateStringDataSchema(dataSchema, dataSchemaKind, propertyApprover, platContextPresent, contextTokenIndex); + case TDValues.TypeNumber: + return TryValidateNumberDataSchema(dataSchema, dataSchemaKind, propertyApprover, platContextPresent, contextTokenIndex); + case TDValues.TypeInteger: + return TryValidateIntegerDataSchema(dataSchema, dataSchemaKind, propertyApprover, platContextPresent, contextTokenIndex); + case TDValues.TypeBoolean: + return TryValidateBooleanDataSchema(dataSchema, dataSchemaKind, propertyApprover); + case TDValues.TypeNull: + return TryValidateNullDataSchema(dataSchema, dataSchemaKind, propertyApprover, contentTypeIsRawOrCustom, contentType?.TokenIndex ?? -1); + default: + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Data schema '{TDDataSchema.TypeName}' property has unsupported value '{dataSchema.Value.Type.Value.Value}'.", dataSchema.Value.Type.TokenIndex); + return false; + } + } + + private bool TryValidateReferenceDataSchema(ValueTracker dataSchema, Func? propertyApprover) + where T : TDDataSchema, IDeserializable + { + bool hasError = false; + + string refValue = dataSchema.Value.Ref!.Value.Value; + long tokenIndex = dataSchema.Value.Ref.TokenIndex; + + if (string.IsNullOrWhiteSpace(refValue)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Data schema '{TDDataSchema.RefName}' property has empty value.", tokenIndex); + hasError = true; + } + else if (!RefCharRegex.IsMatch(refValue)) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Data schema '{TDDataSchema.RefName}' property value \"{refValue}\" contains one or more illegal characters.", tokenIndex); + hasError = true; + } + else if (refValue.StartsWith('#')) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Data schema '{TDDataSchema.RefName}' property value \"{refValue}\" may not begin with a '#' character.", tokenIndex); + hasError = true; + } + else + { + bool isRelative = refValue.StartsWith("./") || refValue.StartsWith("../"); + bool hasPathSegs = refValue.Contains('/') && refValue.IndexOf('/') < (refValue.Contains('#') ? refValue.IndexOf('#') : refValue.Length); + if (!isRelative && hasPathSegs) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Data schema '{TDDataSchema.RefName}' property value \"{refValue}\" must be relative (start with \"./\" or \"../\") if it has any path segments.", tokenIndex); + hasError = true; + } + } + + HashSet supportedProperties = new() + { + TDDataSchema.RefName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "a schema via a reference", tokenIndex)) + { + hasError = true; + } + + return !hasError; + } + + private bool TryValidateStringConst(ValueTracker dataSchema, ValueTracker constProperty, ValueTracker constValue) + where T : TDDataSchema, IDeserializable + { + if (dataSchema.Value.Title != null && !TitleRegex.IsMatch(dataSchema.Value.Title.Value.Value)) + { + errorReporter.ReportWarning($"Data schema '{TDDataSchema.TitleName}' property value \"{dataSchema.Value.Title.Value.Value}\" does not conform to codegen type naming rules (only alphanumerics, starting with uppercase), which will be problematic unless titles are suppressed via a '{TDValues.RelationSchemaNaming}' linked schema naming file", dataSchema.Value.Title.TokenIndex); + } + + bool hasError = false; + + if (constValue.Value.Value is not string) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The specified constant value must be a string.", constValue.TokenIndex, dataSchema.Value.Type!.TokenIndex); + hasError = true; + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.ConstName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, null, "a constant string schema", constProperty.TokenIndex)) + { + hasError = true; + } + + return !hasError; + } + + private bool TryValidateNumberConst(ValueTracker dataSchema, ValueTracker constProperty, ValueTracker constValue) + where T : TDDataSchema, IDeserializable + { + bool hasError = false; + + if (dataSchema.Value.Title != null && !TitleRegex.IsMatch(dataSchema.Value.Title.Value.Value)) + { + errorReporter.ReportWarning($"Data schema '{TDDataSchema.TitleName}' property value \"{dataSchema.Value.Title.Value.Value}\" does not conform to codegen type naming rules (only alphanumerics, starting with uppercase), which will be problematic unless titles are suppressed via a '{TDValues.RelationSchemaNaming}' linked schema naming file", dataSchema.Value.Title.TokenIndex); + } + + if (constValue.Value.Value is double numValue) + { + if (dataSchema.Value.Minimum?.Value.Value != null && numValue < dataSchema.Value.Minimum.Value.Value) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"The specified constant value ({numValue}) cannot be less than the '{TDDataSchema.MinimumName}' property value ({dataSchema.Value.Minimum.Value.Value}).", constValue.TokenIndex, dataSchema.Value.Minimum.TokenIndex); + hasError = true; + } + if (dataSchema.Value.Maximum?.Value.Value != null && numValue > dataSchema.Value.Maximum.Value.Value) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"The specified constant value ({numValue}) cannot be greater than the '{TDDataSchema.MaximumName}' property value ({dataSchema.Value.Maximum.Value.Value}).", constValue.TokenIndex, dataSchema.Value.Maximum.TokenIndex); + hasError = true; + } + } + else + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The specified constant value must be a number.", constValue.TokenIndex, dataSchema.Value.Type!.TokenIndex); + hasError = true; + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.MinimumName, + TDDataSchema.MaximumName, + TDDataSchema.ConstName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, null, "a constant number schema", constProperty.TokenIndex)) + { + hasError = true; + } + + return !hasError; + } + + private bool TryValidateIntegerConst(ValueTracker dataSchema, ValueTracker constProperty, ValueTracker constValue) + where T : TDDataSchema, IDeserializable + { + bool hasError = false; + + if (dataSchema.Value.Title != null && !TitleRegex.IsMatch(dataSchema.Value.Title.Value.Value)) + { + errorReporter.ReportWarning($"Data schema '{TDDataSchema.TitleName}' property value \"{dataSchema.Value.Title.Value.Value}\" does not conform to codegen type naming rules (only alphanumerics, starting with uppercase), which will be problematic unless titles are suppressed via a '{TDValues.RelationSchemaNaming}' linked schema naming file", dataSchema.Value.Title.TokenIndex); + } + + if (constValue.Value.Value is double numValue && double.IsInteger(numValue)) + { + if (dataSchema.Value.Minimum?.Value.Value != null && numValue < dataSchema.Value.Minimum.Value.Value) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"The specified constant value ({numValue}) cannot be less than the '{TDDataSchema.MinimumName}' property value ({dataSchema.Value.Minimum.Value.Value}).", constValue.TokenIndex, dataSchema.Value.Minimum.TokenIndex); + hasError = true; + } + if (dataSchema.Value.Maximum?.Value.Value != null && numValue > dataSchema.Value.Maximum.Value.Value) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"The specified constant value ({numValue}) cannot be greater than the '{TDDataSchema.MaximumName}' property value ({dataSchema.Value.Maximum.Value.Value}).", constValue.TokenIndex, dataSchema.Value.Maximum.TokenIndex); + hasError = true; + } + } + else + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The specified constant value must be an integer.", constValue.TokenIndex, dataSchema.Value.Type!.TokenIndex); + hasError = true; + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.MinimumName, + TDDataSchema.MaximumName, + TDDataSchema.ConstName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, null, "a constant integer schema", constProperty.TokenIndex)) + { + hasError = true; + } + + return !hasError; + } + + private bool TryValidateBooleanConst(ValueTracker dataSchema, ValueTracker constProperty, ValueTracker constValue) + where T : TDDataSchema, IDeserializable + { + bool hasError = false; + + if (dataSchema.Value.Title != null && !TitleRegex.IsMatch(dataSchema.Value.Title.Value.Value)) + { + errorReporter.ReportWarning($"Data schema '{TDDataSchema.TitleName}' property value \"{dataSchema.Value.Title.Value.Value}\" does not conform to codegen type naming rules (only alphanumerics, starting with uppercase), which will be problematic unless titles are suppressed via a '{TDValues.RelationSchemaNaming}' linked schema naming file", dataSchema.Value.Title.TokenIndex); + } + + if (constValue.Value.Value is not bool) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The specified constant value must be Boolean.", constValue.TokenIndex, dataSchema.Value.Type!.TokenIndex); + hasError = true; + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.ConstName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, null, "a constant boolean schema", constProperty.TokenIndex)) + { + hasError = true; + } + + return !hasError; + } + + private bool TryValidateResidualProperties(Dictionary propertyNames, HashSet supportedProperties, Func? propertyApprover, string schemaDescription, long cfTokenIndex = -1) + { + bool hasError = false; + + foreach (KeyValuePair propertyName in propertyNames) + { + if (propertyApprover?.Invoke(propertyName.Key) != true && !supportedProperties.Contains(propertyName.Key)) + { + if (propertyName.Key.Contains(':') && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioProtocol}:") && !propertyName.Key.StartsWith($"{TDValues.ContextPrefixAioPlatform}:")) + { + errorReporter.ReportWarning($"Data schema has unrecognized '{propertyName.Key}' property, which will be ignored.", propertyName.Value); + } + else + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"Data schema defines {schemaDescription}; property '{propertyName.Key}' is not supported.", propertyName.Value, cfTokenIndex); + hasError = true; + } + } + } + + return !hasError; + } + + private bool TryValidateObjectDataSchema(ValueTracker dataSchema, DataSchemaKind dataSchemaKind, Func? propertyApprover, bool platContextPresent, long contextTokenIndex) + where T : TDDataSchema, IDeserializable + { + if (dataSchema.Value.Properties?.Entries == null && dataSchema.Value.AdditionalProperties == null) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Data schema with '{TDDataSchema.TypeName}' of '{TDValues.TypeObject}' must have either '{TDDataSchema.PropertiesName}' or '{TDDataSchema.AdditionalPropertiesName}' property.", dataSchema.TokenIndex); + return false; + } + + if (dataSchema.Value.Properties?.Entries != null && dataSchema.Value.AdditionalProperties != null) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Data schema with '{TDDataSchema.TypeName}' of '{TDValues.TypeObject}' cannot have both '{TDDataSchema.PropertiesName}' and '{TDDataSchema.AdditionalPropertiesName}' properties.", dataSchema.Value.Properties.TokenIndex, dataSchema.Value.AdditionalProperties.TokenIndex); + return false; + } + + bool hasError = false; + if (dataSchema.Value.Properties?.Entries != null) + { + if (dataSchema.Value.Const != null) + { + if (dataSchemaKind != DataSchemaKind.SchemaDefinition) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"The '{TDDataSchema.ConstName}' property is permitted only in the first level of a '{TDThing.SchemaDefinitionsName}' element.", dataSchema.Value.Const.TokenIndex); + hasError = true; + } + else if (dataSchema.Value.Const.Value.ValueMap?.Entries == null) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The '{TDDataSchema.ConstName}' property value must be an object.", dataSchema.Value.Const.TokenIndex, dataSchema.Value.Type!.TokenIndex); + hasError = true; + } + else + { + foreach (KeyValuePair> property in dataSchema.Value.Properties.Entries) + { + if (property.Value.Value.Type == null) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Data schema property '{property.Key}' is missing '{TDDataSchema.TypeName}' property, which is required in an object definition that specifies a constant value.", property.Value.TokenIndex, dataSchema.Value.Const.TokenIndex); + hasError = true; + } + else if (property.Value.Value.Const != null) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"The '{TDDataSchema.ConstName}' property is not permitted in a schema definition that defines a '{TDDataSchema.ConstName}' value.", property.Value.Value.Const.TokenIndex, dataSchema.Value.Const.TokenIndex); + hasError = true; + } + else if (dataSchema.Value.Const.Value.ValueMap.Entries!.TryGetValue(property.Key, out ValueTracker? constValue)) + { + switch (property.Value.Value.Type!.Value.Value) + { + case TDValues.TypeString: + if (!TryValidateStringConst(property.Value, dataSchema.Value.Const, constValue)) + { + hasError = true; + } + break; + case TDValues.TypeNumber: + if (!TryValidateNumberConst(property.Value, dataSchema.Value.Const, constValue)) + { + hasError = true; + } + break; + case TDValues.TypeInteger: + if (!TryValidateIntegerConst(property.Value, dataSchema.Value.Const, constValue)) + { + hasError = true; + } + break; + case TDValues.TypeBoolean: + if (!TryValidateBooleanConst(property.Value, dataSchema.Value.Const, constValue)) + { + hasError = true; + } + break; + default: + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Data schema property '{property.Key}' value must specify a '{TDDataSchema.TypeName}' value of '{TDValues.TypeString}', '{TDValues.TypeNumber}', '{TDValues.TypeInteger}', or '{TDValues.TypeBoolean}' because the object definition specifies a constant value.", property.Value.Value.Type.TokenIndex, dataSchema.Value.Const.TokenIndex); + hasError = true; + break; + } + } + else + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"Data schema '{TDDataSchema.PropertiesName}' value has property named '{property.Key}' that has no value in '{TDDataSchema.ConstName}' elements.", property.Value.TokenIndex, dataSchema.Value.Const.Value.ValueMap.TokenIndex); + hasError = true; + } + } + + foreach (KeyValuePair> constValue in dataSchema.Value.Const.Value.ValueMap.Entries!) + { + if (!dataSchema.Value.Properties.Entries.ContainsKey(constValue.Key)) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"Data schema '{TDDataSchema.ConstName}' value has property named '{constValue.Key}' that has no type definition in '{TDDataSchema.PropertiesName}' elements.", constValue.Value.TokenIndex, dataSchema.Value.Properties.TokenIndex); + hasError = true; + } + } + } + + if (dataSchema.Value.Title != null && !TitleRegex.IsMatch(dataSchema.Value.Title.Value.Value)) + { + errorReporter.ReportWarning($"Data schema '{TDDataSchema.TitleName}' property value \"{dataSchema.Value.Title.Value.Value}\" does not conform to codegen type naming rules (only alphanumerics, starting with uppercase), which will be problematic unless titles are suppressed via a '{TDValues.RelationSchemaNaming}' linked schema naming file", dataSchema.Value.Title.TokenIndex); + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.PropertiesName, + TDDataSchema.ConstName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "a constant object", dataSchema.Value.Const.TokenIndex)) + { + hasError = true; + } + } + else + { + foreach (KeyValuePair> property in dataSchema.Value.Properties.Entries) + { + if (property.Value.Value.Namespace != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Data schema '{TDDataSchema.NamespaceName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", property.Value.Value.Namespace.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (string.IsNullOrWhiteSpace(property.Value.Value.Namespace.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyEmpty, $"Data schema '{TDDataSchema.NamespaceName}' property has empty value.", property.Value.Value.Namespace.TokenIndex); + hasError = true; + } + } + + if (!TryValidateDataSchema(property.Value, (propName) => propName == TDDataSchema.NamespaceName, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + } + + if (dataSchema.Value.Required?.Elements != null) + { + foreach (ValueTracker requiredProperty in dataSchema.Value.Required.Elements) + { + if (!dataSchema.Value.Properties.Entries.ContainsKey(requiredProperty.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"Data schema '{TDDataSchema.RequiredName}' property names non-existent property '{requiredProperty.Value.Value}'.", requiredProperty.TokenIndex, dataSchema.Value.Properties.TokenIndex); + hasError = true; + } + } + } + + if (dataSchema.Value.ErrorMessage != null) + { + if (dataSchemaKind != DataSchemaKind.SchemaDefinition) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"The '{TDDataSchema.ErrorMessageName}' property is permitted only in the first level of a '{TDThing.SchemaDefinitionsName}' element.", dataSchema.Value.ErrorMessage.TokenIndex); + hasError = true; + } + else if (!dataSchema.Value.Properties.Entries.TryGetValue(dataSchema.Value.ErrorMessage.Value.Value, out ValueTracker? errorMessage)) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"Data schema '{TDDataSchema.ErrorMessageName}' property names non-existent property '{dataSchema.Value.ErrorMessage.Value.Value}'.", dataSchema.Value.ErrorMessage.TokenIndex, dataSchema.Value.Properties.TokenIndex); + hasError = true; + } + else if (errorMessage.Value.Type == null || errorMessage.Value.Type.Value.Value != TDValues.TypeString) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"Data schema '{TDDataSchema.ErrorMessageName}' property must refer to a property with '{TDDataSchema.TypeName}' of '{TDValues.TypeString}'.", dataSchema.Value.ErrorMessage.TokenIndex, errorMessage.TokenIndex); + hasError = true; + } + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.PropertiesName, + TDDataSchema.RequiredName, + TDDataSchema.ErrorMessageName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "a structured object", dataSchema.Value.Properties.TokenIndex)) + { + hasError = true; + } + } + + if (dataSchema.Value.Title != null && !TitleRegex.IsMatch(dataSchema.Value.Title.Value.Value)) + { + errorReporter.ReportWarning($"Data schema '{TDDataSchema.TitleName}' property value \"{dataSchema.Value.Title.Value.Value}\" does not conform to codegen type naming rules (only alphanumerics, starting with uppercase), which will be problematic unless titles are suppressed via a '{TDValues.RelationSchemaNaming}' linked schema naming file", dataSchema.Value.Title.TokenIndex); + } + } + else + { + if (!TryValidateDataSchema(dataSchema.Value.AdditionalProperties!, null, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.AdditionalPropertiesName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "a map", dataSchema.Value.AdditionalProperties!.TokenIndex)) + { + hasError = true; + } + } + + return !hasError; + } + + private bool TryValidateArrayDataSchema(ValueTracker dataSchema, Func? propertyApprover, bool platContextPresent, long contextTokenIndex) + where T : TDDataSchema, IDeserializable + { + if (dataSchema.Value.Items == null) + { + errorReporter.ReportError(ErrorCondition.PropertyMissing, $"Data schema with '{TDDataSchema.TypeName}' of '{TDValues.TypeArray}' must have '{TDDataSchema.ItemsName}' property.", dataSchema.TokenIndex); + return false; + } + + bool hasError = false; + + if (!TryValidateDataSchema(dataSchema.Value.Items!, null, platContextPresent, contextTokenIndex)) + { + hasError = true; + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.ItemsName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "an array", dataSchema.Value.Type!.TokenIndex)) + { + hasError = true; + } + + return !hasError; + } + + private bool TryValidateStringDataSchema(ValueTracker dataSchema, DataSchemaKind dataSchemaKind, Func? propertyApprover, bool platContextPresent, long contextTokenIndex) + where T : TDDataSchema, IDeserializable + { + bool hasError = false; + + if (dataSchema.Value.Enum?.Elements != null) + { + if (dataSchema.Value.Title != null && !TitleRegex.IsMatch(dataSchema.Value.Title.Value.Value)) + { + errorReporter.ReportWarning($"Data schema '{TDDataSchema.TitleName}' property value \"{dataSchema.Value.Title.Value.Value}\" does not conform to codegen type naming rules (only alphanumerics, starting with uppercase), which will be problematic unless titles are suppressed via a '{TDValues.RelationSchemaNaming}' linked schema naming file", dataSchema.Value.Title.TokenIndex); + } + + foreach (ValueTracker enumValue in dataSchema.Value.Enum.Elements) + { + if (!EnumValueRegex.IsMatch(enumValue.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Data schema '{TDDataSchema.EnumName}' property has value \"{enumValue.Value.Value}\" that does not conform to codegen enum member naming rules -- it must start with a letter and contain only alphanumeric characters and underscores", enumValue.TokenIndex); + hasError = true; + } + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.EnumName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "an enumerated string", dataSchema.Value.Enum.TokenIndex)) + { + hasError = true; + } + } + else + { + if (dataSchema.Value.Const != null) + { + if (dataSchemaKind != DataSchemaKind.SchemaDefinition) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"The '{TDDataSchema.ConstName}' property is permitted only in the first level of a '{TDThing.SchemaDefinitionsName}' element.", dataSchema.Value.Const.TokenIndex); + hasError = true; + } + else if (!TryValidateStringConst(dataSchema, dataSchema.Value.Const, dataSchema.Value.Const)) + { + hasError = true; + } + } + else + { + List exclusiveProperties = new(); + if (dataSchema.Value.Format != null) + { + exclusiveProperties.Add(TDDataSchema.FormatName); + } + if (dataSchema.Value.Pattern != null) + { + exclusiveProperties.Add(TDDataSchema.PatternName); + } + if (dataSchema.Value.ContentEncoding != null) + { + exclusiveProperties.Add(TDDataSchema.ContentEncodingName); + } + + if (exclusiveProperties.Count > 1) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Data schema string type cannot have more than one of the following properties: {string.Join(", ", exclusiveProperties)}.", dataSchema.TokenIndex); + hasError = true; + } + + if (dataSchema.Value.Format != null) + { + string formatValue = dataSchema.Value.Format.Value.Value; + if (formatValue != TDValues.FormatDateTime && formatValue != TDValues.FormatDate && formatValue != TDValues.FormatTime && formatValue != TDValues.FormatUuid) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Data schema '{TDDataSchema.FormatName}' property has unsupported value '{formatValue}'; supported values are '{TDValues.FormatDateTime}', '{TDValues.FormatDate}', '{TDValues.FormatTime}', and '{TDValues.FormatUuid}'.", dataSchema.Value.Format.TokenIndex); + hasError = true; + } + } + + if (dataSchema.Value.ContentEncoding != null) + { + string contentEncodingValue = dataSchema.Value.ContentEncoding.Value.Value; + if (contentEncodingValue != TDValues.ContentEncodingBase64) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Data schema '{TDDataSchema.ContentEncodingName}' property has unsupported value '{contentEncodingValue}'; only supported value is '{TDValues.ContentEncodingBase64}'.", dataSchema.Value.ContentEncoding.TokenIndex); + hasError = true; + } + } + + if (dataSchema.Value.Pattern != null) + { + string patternValue = dataSchema.Value.Pattern.Value.Value; + try + { + Regex patternRegex = new Regex(patternValue); + + if (patternRegex.IsMatch(AnArbitraryString)) + { + errorReporter.ReportWarning($"Data schema '{TDDataSchema.PatternName}' property value \"{patternValue}\" matches an arbitrary test string value, so no type restriction will be applied.", dataSchema.Value.Pattern.TokenIndex); + } + else if (!patternRegex.IsMatch(Iso8601DurationExample) && !patternRegex.IsMatch(DecimalExample)) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"Data schema '{TDDataSchema.PatternName}' property value \"{patternValue}\" matches neither an ISO 8601 duration value (e.g., \"{Iso8601DurationExample}\") nor a decimal value (e.g., \"{DecimalExample}\"), so indended type is indeterminate.", dataSchema.Value.Pattern.TokenIndex); + hasError = true; + } + } + catch (RegexParseException) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"Data schema '{TDDataSchema.PatternName}' property has invalid regular expression pattern '{patternValue}'", dataSchema.Value.Pattern.TokenIndex); + hasError = true; + } + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.FormatName, + TDDataSchema.ContentEncodingName, + TDDataSchema.PatternName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "a string schema")) + { + hasError = true; + } + } + } + + return !hasError; + } + + private bool TryValidateNumberDataSchema(ValueTracker dataSchema, DataSchemaKind dataSchemaKind, Func? propertyApprover, bool platContextPresent, long contextTokenIndex) + where T : TDDataSchema, IDeserializable + { + bool hasError = false; + + if (dataSchema.Value.Minimum?.Value.Value != null && dataSchema.Value.Maximum?.Value.Value != null && dataSchema.Value.Maximum.Value.Value < dataSchema.Value.Minimum.Value.Value) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"The '{TDDataSchema.MaximumName}' property value ({dataSchema.Value.Maximum.Value.Value}) cannot be less than the '{TDDataSchema.MinimumName}' property value ({dataSchema.Value.Minimum.Value.Value}).", dataSchema.Value.Maximum.TokenIndex, dataSchema.Value.Minimum.TokenIndex); + hasError = true; + } + + if (dataSchema.Value.Const != null) + { + if (dataSchemaKind != DataSchemaKind.SchemaDefinition) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"The '{TDDataSchema.ConstName}' property is permitted only in the first level of a '{TDThing.SchemaDefinitionsName}' element.", dataSchema.Value.Const.TokenIndex); + hasError = true; + } + else if (!TryValidateNumberConst(dataSchema, dataSchema.Value.Const, dataSchema.Value.Const)) + { + hasError = true; + } + } + else + { + if (dataSchema.Value.ScaleFactor != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"The '{TDDataSchema.ScaleFactorName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", dataSchema.Value.ScaleFactor.TokenIndex, contextTokenIndex); + hasError = true; + } + } + + if (dataSchema.Value.DecimalPlaces != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"The '{TDDataSchema.DecimalPlacesName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", dataSchema.Value.DecimalPlaces.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (!double.IsInteger(dataSchema.Value.DecimalPlaces.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The '{TDDataSchema.DecimalPlacesName}' property value must be an integer.", dataSchema.Value.DecimalPlaces.TokenIndex); + hasError = true; + } + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.MinimumName, + TDDataSchema.MaximumName, + TDDataSchema.ScaleFactorName, + TDDataSchema.DecimalPlacesName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "a number schema")) + { + hasError = true; + } + } + + return !hasError; + } + + private bool TryValidateIntegerDataSchema(ValueTracker dataSchema, DataSchemaKind dataSchemaKind, Func? propertyApprover, bool platContextPresent, long contextTokenIndex) + where T : TDDataSchema, IDeserializable + { + bool hasError = false; + + if (dataSchema.Value.Minimum?.Value.Value != null && !double.IsInteger(dataSchema.Value.Minimum.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The '{TDDataSchema.MinimumName}' property value must be an integer.", dataSchema.Value.Minimum.TokenIndex, dataSchema.Value.Type!.TokenIndex); + hasError = true; + } + if (dataSchema.Value.Maximum?.Value.Value != null && !double.IsInteger(dataSchema.Value.Maximum.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The '{TDDataSchema.MaximumName}' property value must be an integer.", dataSchema.Value.Maximum.TokenIndex, dataSchema.Value.Type!.TokenIndex); + hasError = true; + } + + if (hasError) + { + return false; + } + + if (dataSchema.Value.Minimum?.Value.Value != null && dataSchema.Value.Maximum?.Value.Value != null && dataSchema.Value.Maximum.Value.Value < dataSchema.Value.Minimum.Value.Value) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"The '{TDDataSchema.MaximumName}' property value ({dataSchema.Value.Maximum.Value.Value}) cannot be less than the '{TDDataSchema.MinimumName}' property value ({dataSchema.Value.Minimum.Value.Value}).", dataSchema.Value.Maximum.TokenIndex, dataSchema.Value.Minimum.TokenIndex); + hasError = true; + } + + if (dataSchema.Value.Const != null) + { + if (dataSchemaKind != DataSchemaKind.SchemaDefinition) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"The '{TDDataSchema.ConstName}' property is permitted only in the first level of a '{TDThing.SchemaDefinitionsName}' element.", dataSchema.Value.Const.TokenIndex); + hasError = true; + } + else if (dataSchema.Value.Const != null && !TryValidateIntegerConst(dataSchema, dataSchema.Value.Const, dataSchema.Value.Const)) + { + hasError = true; + } + } + else + { + if (dataSchema.Value.ScaleFactor != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"The '{TDDataSchema.ScaleFactorName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", dataSchema.Value.ScaleFactor.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (!double.IsInteger(dataSchema.Value.ScaleFactor.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The '{TDDataSchema.ScaleFactorName}' property value must be an integer.", dataSchema.Value.ScaleFactor.TokenIndex, dataSchema.Value.Type!.TokenIndex); + hasError = true; + } + } + + if (dataSchema.Value.DecimalPlaces != null) + { + if (!platContextPresent) + { + errorReporter.ReportError(ErrorCondition.PropertyInvalid, $"The '{TDDataSchema.DecimalPlacesName}' property requires the Azure Operations Platform context in the '{TDThing.ContextName}' property.", dataSchema.Value.DecimalPlaces.TokenIndex, contextTokenIndex); + hasError = true; + } + + if (!double.IsInteger(dataSchema.Value.DecimalPlaces.Value.Value)) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"The '{TDDataSchema.DecimalPlacesName}' property value must be an integer.", dataSchema.Value.DecimalPlaces.TokenIndex); + hasError = true; + } + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.MinimumName, + TDDataSchema.MaximumName, + TDDataSchema.ScaleFactorName, + TDDataSchema.DecimalPlacesName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "an integer schema")) + { + hasError = true; + } + } + + return !hasError; + } + + private bool TryValidateBooleanDataSchema(ValueTracker dataSchema, DataSchemaKind dataSchemaKind, Func? propertyApprover) + where T : TDDataSchema, IDeserializable + { + bool hasError = false; + + if (dataSchema.Value.Const != null) + { + if (dataSchemaKind != DataSchemaKind.SchemaDefinition) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupported, $"The '{TDDataSchema.ConstName}' property is permitted only in the first level of a '{TDThing.SchemaDefinitionsName}' element.", dataSchema.Value.Const.TokenIndex); + hasError = true; + } + else if (!TryValidateBooleanConst(dataSchema, dataSchema.Value.Const, dataSchema.Value.Const)) + { + hasError = true; + } + } + else + { + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "a Boolean schema")) + { + hasError = true; + } + } + + return !hasError; + } + + private bool TryValidateNullDataSchema(ValueTracker dataSchema, DataSchemaKind dataSchemaKind, Func? propertyApprover, bool contentTypeIsRawOrCustom, long contentTypeTokenIndex) + where T : TDDataSchema, IDeserializable + { + if (dataSchemaKind != DataSchemaKind.Action && dataSchemaKind != DataSchemaKind.Event) + { + errorReporter.ReportError(ErrorCondition.PropertyUnsupportedValue, $"A '{TDDataSchema.TypeName}' property value of '{TDValues.TypeNull}' is permitted only in the '{TDAction.InputName}' or '{TDAction.OutputName}' property value of an '{TDThing.ActionsName}' element or in the '{TDEvent.DataName}' property value of an '{TDThing.EventsName}' element.", dataSchema.Value.Type!.TokenIndex); + return false; + } + else if (!contentTypeIsRawOrCustom) + { + errorReporter.ReportError(ErrorCondition.ValuesInconsistent, $"Data schema with '{TDDataSchema.TypeName}' of '{TDValues.TypeNull}' is permitted only in an affordance with a form that specifies '{TDForm.ContentTypeName}' of '{TDValues.ContentTypeRaw}' or '{TDValues.ContentTypeCustom}'.", dataSchema.Value.Type!.TokenIndex, contentTypeTokenIndex); + return false; + } + + HashSet supportedProperties = new() + { + TDDataSchema.TypeName, + TDDataSchema.TitleName, + TDDataSchema.DescriptionName, + TDDataSchema.TypeRefName, + }; + if (!TryValidateResidualProperties(dataSchema.Value.PropertyNames, supportedProperties, propertyApprover, "a null schema")) + { + return false; + } + + return true; + } + + private enum DataSchemaKind + { + Undistinguished, + Action, + Property, + Event, + SchemaDefinition, + } + + private enum FormsKind + { + Root, + Action, + Property, + Event, + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/TypeNameInfo.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/TypeNameInfo.cs new file mode 100644 index 0000000000..42c137d227 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/TypeNameInfo.cs @@ -0,0 +1,17 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Collections.Generic; + using System.Text.Json.Serialization; + + public class TypeNameInfo + { + [JsonPropertyName("suppressTitles")] + public bool SuppressTitles { get; set; } + + [JsonPropertyName("nameRules")] + public Dictionary? NameRules { get; set; } + + [JsonPropertyName("capitalizeCaptures")] + public bool CapitalizeCaptures { get; set; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/TypeNamer.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/TypeNamer.cs new file mode 100644 index 0000000000..bb5d2dbece --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/TypeNamer.cs @@ -0,0 +1,62 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + using System.Collections.Generic; + using System.IO; + using System.Text.Json; + using System.Text.RegularExpressions; + + public class TypeNamer + { + private TypeNameInfo? typeNameInfo; + private Dictionary nameRules; + private bool capitalizeCaptures; + + public bool SuppressTitles { get; } + + public TypeNamer(string? typeNameInfoText) + { + this.typeNameInfo = typeNameInfoText != null ? JsonSerializer.Deserialize(typeNameInfoText) : null; + this.SuppressTitles = this.typeNameInfo?.SuppressTitles ?? false; + this.nameRules = this.typeNameInfo?.NameRules ?? new(); + this.capitalizeCaptures = this.typeNameInfo?.CapitalizeCaptures ?? false; + } + + public string GenerateTypeName(string schemaName, string? keyName, string? title) + { + if (title != null && !this.SuppressTitles) + { + return title; + } + + if (keyName == null) + { + string pathlessName = Path.GetFileName(schemaName); + int dotIx = pathlessName.IndexOf('.'); + string unsuffixedName = dotIx < 0 ? pathlessName : pathlessName.Substring(0, dotIx); + return Capitalize(unsuffixedName); + } + + foreach (KeyValuePair rule in this.nameRules) + { + Regex rx = new(rule.Key, RegexOptions.IgnoreCase); + Match? match = rx.Match(keyName); + if (match.Success) + { + string replacement = rule.Value; + for (int i = 1; i < match.Groups.Count; i++) + { + string captureValue = match.Groups[i].Captures[0].Value; + replacement = replacement.Replace($"{{{i}}}", this.capitalizeCaptures ? Capitalize(captureValue) : captureValue); + } + + return replacement; + } + } + + uint keyHash = (uint)((long)keyName.GetHashCode() - (long)int.MinValue); + return $"Type{keyHash:D10}"; + } + + private static string Capitalize(string input) => input.Length == 0 ? input : char.ToUpper(input[0]) + input.Substring(1); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.CodeGeneration/ValueReference.cs b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ValueReference.cs new file mode 100644 index 0000000000..79882fac06 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.CodeGeneration/ValueReference.cs @@ -0,0 +1,4 @@ +namespace Azure.Iot.Operations.CodeGeneration +{ + public record ValueReference(string Filename, int LineNumber, string Value); +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ActionEnvoyGenerator.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ActionEnvoyGenerator.cs new file mode 100644 index 0000000000..6776726f2c --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ActionEnvoyGenerator.cs @@ -0,0 +1,127 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal static class ActionEnvoyGenerator + { + internal static List GenerateActionEnvoys(ErrorReporter errorReporter, TDThing tdThing, SchemaNamer schemaNamer, CodeName serviceName, EnvoyTransformFactory envoyFactory, Dictionary transforms, Dictionary errorSpecs, Dictionary> formattedTypesToSerialize) + { + List actionSpecs = new(); + + foreach (KeyValuePair> actionKvp in tdThing.Actions?.Entries ?? new()) + { + TDAction? action = actionKvp.Value.Value; + if (action == null) + { + continue; + } + + FormInfo? actionForm = FormInfo.CreateFromForm(errorReporter, action.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpInvokeAction) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + actionForm ??= FormInfo.CreateFromForm(errorReporter, action.Forms?.Elements?.FirstOrDefault(f => f.Value.Op == null)?.Value, tdThing.SchemaDefinitions?.Entries); + + if (actionForm?.TopicPattern != null && actionForm.Format != SerializationFormat.None) + { + HashSet typesToSerialize = formattedTypesToSerialize[actionForm.Format]; + + string? inputSchemaType = action.Input != null ? schemaNamer.GetActionInSchema(action.Input?.Value, actionKvp.Key) : null; + string? outArgsType = action.Output != null ? schemaNamer.GetActionOutSchema(action.Output?.Value, actionKvp.Key) : null; + string? outputSchemaType = actionForm.ErrorRespSchema != null ? schemaNamer.GetActionRespSchema(actionKvp.Key) : outArgsType; + string? errRespName = actionForm.ErrorRespName != null ? schemaNamer.GetActionRespErrorField(actionKvp.Key, actionForm.ErrorRespName) : null; + string? errSchemaName = schemaNamer.ChooseTitleOrName(actionForm.ErrorRespSchema?.Value.Title?.Value?.Value, actionForm.ErrorRespName); + + List normalResultNames = action.Output?.Value?.Properties?.Entries?.Keys?.ToList() ?? new(); + List> normalRequiredNames = action.Output?.Value?.Required?.Elements?.ToList() ?? new(); + string? headerCodeSchema = schemaNamer.ChooseTitleOrName(actionForm.HeaderCodeSchema?.Value.Title?.Value?.Value, actionForm.HeaderCodeName); + string? headerInfoSchema = schemaNamer.ChooseTitleOrName(actionForm.HeaderInfoSchema?.Value.Title?.Value?.Value, actionForm.HeaderInfoName); + + bool doesTargetExecutor = DoesTopicReferToExecutor(actionForm.TopicPattern); + + actionSpecs.Add(new ActionSpec( + schemaNamer, + actionKvp.Key, + inputSchemaType, + outputSchemaType, + actionForm.Format, + normalResultNames, + normalRequiredNames, + outArgsType, + errRespName, + errSchemaName, + actionForm.HeaderCodeName, + headerCodeSchema, + actionForm.HeaderInfoName, + headerInfoSchema, + doesTargetExecutor)); + + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetActionTransforms( + schemaNamer, + serviceName, + actionKvp.Key, + inputSchemaType, + outputSchemaType, + actionForm.Format, + actionForm.ServiceGroupId, + actionForm.TopicPattern, + action.Idempotent?.Value.Value ?? false, + normalResultNames, + normalRequiredNames, + outArgsType, + errRespName, + errSchemaName, + actionForm.HeaderCodeName, + headerCodeSchema, + actionForm.HeaderInfoName, + headerInfoSchema, + actionForm.HeaderCodeSchema?.Value.Enum?.Elements?.Select(e => e.Value.Value).ToList(), + doesTargetExecutor)) + { + transforms[transform.FileName] = transform; + } + + if (inputSchemaType != null) + { + typesToSerialize.Add(inputSchemaType); + } + + if (outputSchemaType != null) + { + typesToSerialize.Add(outputSchemaType); + } + + if (actionForm.ErrorRespSchema != null) + { + if (outArgsType != null) + { + typesToSerialize.Add(outArgsType); + } + + typesToSerialize.Add(errSchemaName!); + + ErrorSpec errorSpec = new ErrorSpec( + errSchemaName!, + actionForm.ErrorRespSchema.Value.Description?.Value.Value ?? "The action could not be completed", + actionForm.ErrorRespSchema.Value.ErrorMessage?.Value.Value, + actionForm.ErrorRespSchema.Value.Required?.Elements?.Any(e => e.Value.Value == (actionForm.ErrorRespSchema.Value.ErrorMessage?.Value?.Value ?? string.Empty)) ?? false, + actionForm.HeaderCodeName, + schemaNamer.ChooseTitleOrName(actionForm.HeaderCodeSchema?.Value.Title?.Value.Value, actionForm.HeaderCodeName), + actionForm.HeaderInfoName, + schemaNamer.ChooseTitleOrName(actionForm.HeaderInfoSchema?.Value.Title?.Value.Value, actionForm.HeaderInfoName)); + + errorSpecs[errSchemaName!] = errorSpec; + } + } + } + + return actionSpecs; + } + + private static bool DoesTopicReferToExecutor(string? topic) + { + return topic != null && topic.Contains($"{{{MqttTopicTokens.ActionExecutorId}}}"); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ActionSpec.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ActionSpec.cs new file mode 100644 index 0000000000..3d64136a47 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ActionSpec.cs @@ -0,0 +1,63 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + + public record ActionSpec( + CodeName Name, + CodeName Invoker, + CodeName Executor, + ITypeName? RequestSchema, + ITypeName? ResponseSchema, + EmptyTypeName SerializerEmptyType, + List NormalResultNames, + List NormalRequiredNames, + ITypeName? NormalResultSchema, + CodeName? ErrorResultName, + CodeName? ErrorResultSchema, + CodeName? ErrorCodeName, + CodeName? ErrorCodeSchema, + CodeName? ErrorInfoName, + CodeName? ErrorInfoSchema, + bool DoesTargetExecutor, + bool ResponseNullable) + { + public ActionSpec( + SchemaNamer schemaNamer, + string actionName, + string? inputSchemaType, + string? outputSchemaType, + SerializationFormat format, + List normalResultNames, + List> normalRequiredNames, + string? normalResultSchema, + string? errorResultName, + string? errorResultSchema, + string? headerCodeName, + string? headerCodeSchema, + string? headerInfoName, + string? headerInfoSchema, + bool doesTargetExecutor) + : this( + new CodeName(actionName), + new CodeName(schemaNamer.GetActionInvokerBinder(actionName)), + new CodeName(schemaNamer.GetActionExecutorBinder(actionName)), + EnvoyGeneratorSupport.GetTypeName(inputSchemaType, format), + EnvoyGeneratorSupport.GetTypeName(outputSchemaType, format), + format.GetEmptyTypeName(), + normalResultNames.ConvertAll(name => new CodeName(name)), + normalRequiredNames.ConvertAll(name => new CodeName(name.Value.Value)), + EnvoyGeneratorSupport.GetTypeName(normalResultSchema, format), + errorResultName != null ? new CodeName(errorResultName) : null, + errorResultSchema != null ? new CodeName(errorResultSchema) : null, + headerCodeName != null ? new CodeName(headerCodeName) : null, + headerCodeSchema != null ? new CodeName(headerCodeSchema) : null, + headerInfoName != null ? new CodeName(headerInfoName) : null, + headerInfoSchema != null ? new CodeName(headerInfoSchema) : null, + doesTargetExecutor, + ResponseNullable: false) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/AggregateErrorSpec.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/AggregateErrorSpec.cs new file mode 100644 index 0000000000..5f7fd91b45 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/AggregateErrorSpec.cs @@ -0,0 +1,6 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + + public record AggregateErrorSpec(string SchemaName, Dictionary InnerErrors); +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/Azure.Iot.Operations.EnvoyGenerator.csproj b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/Azure.Iot.Operations.EnvoyGenerator.csproj new file mode 100644 index 0000000000..28ecc28204 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/Azure.Iot.Operations.EnvoyGenerator.csproj @@ -0,0 +1,310 @@ + + + + 0.10.0 + net9.0 + enable + + + + + + + + + + + + + + TextTemplatingFilePreprocessor + DotNetCommandExecutor.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetCommandInvoker.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetResponseExtension.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetConstants.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetAggregateError.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetError.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetTelemetryReceiver.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetTelemetrySender.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetProject.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetPropertyConsumer.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetPropertyMaintainer.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + DotNetService.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustCommandExecutor.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustCommandExecutorHeaders.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustCommandInvoker.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustCommandInvokerHeaders.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustConstants.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustAggregateError.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustError.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustTelemetryReceiver.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustTelemetrySender.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustCargoToml.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustLib.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustPropertyConsumer.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustPropertyMaintainer.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustSerialization.cs + Azure.Iot.Operations.EnvoyGenerator + + + TextTemplatingFilePreprocessor + RustIndex.cs + Azure.Iot.Operations.EnvoyGenerator + + + + + + + + + + True + True + DotNetCommandExecutor.tt + + + True + True + DotNetCommandInvoker.tt + + + True + True + DotNetResponseExtension.tt + + + True + True + DotNetConstants.tt + + + True + True + DotNetAggregateError.tt + + + True + True + DotNetError.tt + + + True + True + DotNetTelemetryReceiver.tt + + + True + True + DotNetTelemetrySender.tt + + + True + True + DotNetProject.tt + + + True + True + DotNetPropertyConsumer.tt + + + True + True + DotNetPropertyMaintainer.tt + + + True + True + DotNetService.tt + + + True + True + RustCommandExecutor.tt + + + True + True + RustCommandExecutorHeaders.tt + + + True + True + RustCommandInvoker.tt + + + True + True + RustCommandInvokerHeaders.tt + + + True + True + RustConstants.tt + + + True + True + RustAggregateError.tt + + + True + True + RustError.tt + + + True + True + RustTelemetryReceiver.tt + + + True + True + RustTelemetrySender.tt + + + True + True + RustCargoToml.tt + + + True + True + RustLib.tt + + + True + True + RustPropertyConsumer.tt + + + True + True + RustPropertyMaintainer.tt + + + True + True + RustSerialization.tt + + + True + True + RustIndex.tt + + + + + + LanguageResources\csharp\%(RecursiveDir)%(Filename)%(Extension) + + + LanguageResources\csharp\common\%(RecursiveDir)%(Filename)%(Extension) + + + LanguageResources\rust\%(RecursiveDir)%(Filename)%(Extension) + + + + diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ConstantsSpec.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ConstantsSpec.cs new file mode 100644 index 0000000000..1f1dcf6ccf --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ConstantsSpec.cs @@ -0,0 +1,7 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + + public record ConstantsSpec(string? Description, Dictionary Constants); +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EnvoyGenerator.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EnvoyGenerator.cs new file mode 100644 index 0000000000..b813d4b575 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EnvoyGenerator.cs @@ -0,0 +1,174 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + public static class EnvoyGenerator + { + public static List GenerateEnvoys( + List parsedThings, + List serializationFormats, + TargetLanguage targetLanguage, + string genNamespace, + string projectName, + string? sdkPath, + List typeFileNames, + string srcSubdir, + bool generateClient, + bool generateServer, + bool generateProject, + bool defaultImpl) + { + EnvoyTransformFactory envoyFactory = new(targetLanguage, new CodeName(genNamespace), projectName, srcSubdir, generateClient, generateServer, defaultImpl); + + Dictionary transforms = new(); + Dictionary errorSpecs = new(); + Dictionary aggErrorSpecs = new(); + Dictionary schemaConstants = new(); + Dictionary> formattedTypesToSerialize = serializationFormats.ToDictionary(f => f, f => new HashSet()); + + foreach (ParsedThing parsedThing in parsedThings) + { + if (parsedThing.Thing.Title == null) + { + throw new System.InvalidOperationException($"Thing defined in file '{parsedThing.FileName}' is missing a root-level 'title' property."); + } + + CodeName serviceName = new CodeName(parsedThing.Thing.Title.Value.Value); + + List actionSpecs = ActionEnvoyGenerator.GenerateActionEnvoys(parsedThing.ErrorReporter, parsedThing.Thing, parsedThing.SchemaNamer, serviceName, envoyFactory, transforms, errorSpecs, formattedTypesToSerialize); + List eventSpecs = EventEnvoyGenerator.GenerateEventEnvoys(parsedThing.ErrorReporter, parsedThing.Thing, parsedThing.SchemaNamer, serviceName, envoyFactory, transforms, formattedTypesToSerialize); + List propSpecs = PropertyEnvoyGenerator.GeneratePropertyEnvoys(parsedThing.ErrorReporter, parsedThing.Thing, parsedThing.SchemaNamer, serviceName, envoyFactory, transforms, errorSpecs, aggErrorSpecs, formattedTypesToSerialize); + GenerateServiceEnvoys(parsedThing.SchemaNamer, serviceName, actionSpecs, propSpecs, eventSpecs, envoyFactory, transforms); + CollectNamedConstants(parsedThing.Thing, parsedThing.SchemaNamer, schemaConstants); + } + + GenerateConstantEnvoys(schemaConstants, envoyFactory, transforms); + GenerateErrorEnvoys(errorSpecs, envoyFactory, transforms); + GenerateAggregateErrorEnvoys(aggErrorSpecs, envoyFactory, transforms); + GenerateSerializationEnvoys(formattedTypesToSerialize, envoyFactory, transforms); + + List generatedEnvoys = new(); + + foreach (KeyValuePair transform in transforms) + { + generatedEnvoys.Add(new GeneratedItem(transform.Value.TransformText(), transform.Key, transform.Value.FolderPath)); + } + + foreach (IEnvoyTemplateTransform project in envoyFactory.GetProjectTransforms(serializationFormats, sdkPath, transforms.Keys.Concat(typeFileNames).ToList(), generateProject)) + { + generatedEnvoys.Add(new GeneratedItem(project.TransformText(), project.FileName, project.FolderPath)); + } + + foreach (IEnvoyTemplateTransform resource in envoyFactory.GetResourceTransforms(serializationFormats)) + { + generatedEnvoys.Add(new GeneratedItem(resource.TransformText(), resource.FileName, resource.FolderPath)); + } + + return generatedEnvoys; + } + + private static void CollectNamedConstants(TDThing tdThing, SchemaNamer schemaNamer, Dictionary schemaConstants) + { + IEnumerable>> constDefs; + Dictionary> constValues; + + if (tdThing.SchemaDefinitions?.Entries != null) + { + constDefs = tdThing.SchemaDefinitions.Entries.Where(d => d.Value.Value.Const?.Value != null && d.Value.Value.Type?.Value.Value != TDValues.TypeObject); + constValues = constDefs.ToDictionary(d => d.Key, d => d.Value.Value.Const!); + AddNamedConstants(schemaNamer.ConstantsSchema, "Global constants.", constDefs, constValues, schemaNamer, schemaConstants); + } + + foreach (KeyValuePair> topLevelDef in tdThing.SchemaDefinitions?.Entries ?? new()) + { + if (topLevelDef.Value.Value.Properties?.Entries != null && topLevelDef.Value.Value.Const?.Value.ValueMap != null) + { + constDefs = topLevelDef.Value.Value.Properties.Entries; + constValues = topLevelDef.Value.Value.Const.Value.ValueMap!.Entries!; + string schemaName = schemaNamer.ApplyBackupSchemaName(topLevelDef.Value.Value.Title?.Value.Value, topLevelDef.Key); + AddNamedConstants(schemaName, topLevelDef.Value.Value.Description?.Value.Value, constDefs!, constValues, schemaNamer, schemaConstants); + } + } + } + + private static void AddNamedConstants(string schemaName, string? description, IEnumerable>> constDefs, Dictionary> constValues, SchemaNamer schemaNamer, Dictionary schemaConstants) + { + if (constDefs.Any()) + { + if (!schemaConstants.TryGetValue(schemaName, out ConstantsSpec? namedConstants)) + { + namedConstants = new ConstantsSpec(description, new()); + schemaConstants[schemaName] = namedConstants; + } + + foreach (var constDef in constDefs) + { + if (constDef.Value.Value.Type?.Value.Value == TDValues.TypeString || constDef.Value.Value.Type?.Value.Value == TDValues.TypeNumber || constDef.Value.Value.Type?.Value.Value == TDValues.TypeInteger || constDef.Value.Value.Type?.Value.Value == TDValues.TypeBoolean) + { + CodeName constName = new CodeName(schemaNamer.ChooseTitleOrName(constDef.Value.Value.Title?.Value.Value, constDef.Key)); + namedConstants.Constants[constName] = new TypedConstant(constDef.Value.Value.Type.Value.Value, constValues[constDef.Key].Value.Value!, constDef.Value.Value.Description?.Value.Value); + } + } + } + } + + private static void GenerateServiceEnvoys(SchemaNamer schemaNamer, CodeName serviceName, List actionSpecs, List propSpecs, List eventSpecs, EnvoyTransformFactory envoyFactory, Dictionary transforms) + { + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetServiceTransforms(schemaNamer, serviceName, actionSpecs, propSpecs, eventSpecs)) + { + transforms[transform.FileName] = transform; + } + } + + private static void GenerateConstantEnvoys(Dictionary schemaConstants, EnvoyTransformFactory envoyFactory, Dictionary transforms) + { + foreach (KeyValuePair schemaConstant in schemaConstants) + { + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetConstantTransforms(new CodeName(schemaConstant.Key), schemaConstant.Value)) + { + transforms[transform.FileName] = transform; + } + } + } + + private static void GenerateErrorEnvoys(Dictionary errorSpecs, EnvoyTransformFactory envoyFactory, Dictionary transforms) + { + foreach (KeyValuePair errorSpec in errorSpecs) + { + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetErrorTransforms(errorSpec.Value)) + { + transforms[transform.FileName] = transform; + } + } + } + + private static void GenerateAggregateErrorEnvoys(Dictionary aggErrorSpecs, EnvoyTransformFactory envoyFactory, Dictionary transforms) + { + foreach (KeyValuePair aggErrorSpec in aggErrorSpecs) + { + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetAggregateErrorTransforms(aggErrorSpec.Value)) + { + transforms[transform.FileName] = transform; + } + } + } + + private static void GenerateSerializationEnvoys(Dictionary> formattedTypesToSerialize, EnvoyTransformFactory envoyFactory, Dictionary transforms) + { + foreach (KeyValuePair> formatTypes in formattedTypesToSerialize) + { + foreach (string typeToSerialize in formatTypes.Value) + { + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetSerializationTransforms(typeToSerialize, formatTypes.Key)) + { + transforms[transform.FileName] = transform; + } + } + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EnvoyGeneratorSupport.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EnvoyGeneratorSupport.cs new file mode 100644 index 0000000000..949ec6b6ef --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EnvoyGeneratorSupport.cs @@ -0,0 +1,24 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Diagnostics.CodeAnalysis; + using Azure.Iot.Operations.CodeGeneration; + + internal static class EnvoyGeneratorSupport + { + [return: NotNullIfNotNull(nameof(schemaType))] + internal static ITypeName? GetTypeName(string? schemaType, SerializationFormat format) + { + if (schemaType == null) + { + return null; + } + + return format switch + { + SerializationFormat.Raw => RawTypeName.Instance, + SerializationFormat.Custom => CustomTypeName.Instance, + _ => new CodeName(schemaType), + }; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EnvoyTransformFactory.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EnvoyTransformFactory.cs new file mode 100644 index 0000000000..37416c4963 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EnvoyTransformFactory.cs @@ -0,0 +1,537 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text.RegularExpressions; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + + internal class EnvoyTransformFactory + { + private readonly TargetLanguage targetLanguage; + private readonly CodeName genNamespace; + private readonly string projectName; + private readonly string srcSubdir; + private readonly bool generateClient; + private readonly bool generateServer; + private readonly bool defaultImpl; + + internal EnvoyTransformFactory( + TargetLanguage targetLanguage, + CodeName genNamespace, + string projectName, + string srcSubdir, + bool generateClient, + bool generateServer, + bool defaultImpl) + { + this.targetLanguage = targetLanguage; + this.genNamespace = genNamespace; + this.projectName = projectName; + this.srcSubdir = srcSubdir; + this.generateClient = generateClient; + this.generateServer = generateServer; + this.defaultImpl = defaultImpl; + } + + internal IEnumerable GetConstantTransforms(CodeName schemaName, ConstantsSpec constantSpec) + { + switch (targetLanguage) + { + case TargetLanguage.CSharp: + yield return new DotNetConstants(projectName, schemaName, genNamespace, constantSpec); + break; + case TargetLanguage.Rust: + yield return new RustConstants(schemaName, genNamespace, constantSpec, srcSubdir); + break; + default: + throw new NotSupportedException($"Target language {targetLanguage} is not supported."); + } + } + + internal IEnumerable GetActionTransforms( + SchemaNamer schemaNamer, + CodeName serviceName, + string actionName, + string? inputSchemaType, + string? outputSchemaType, + SerializationFormat format, + string? serviceGroupId, + string topicPattern, + bool idempotent, + List normalResultFields, + List> normalRequiredFields, + string? normalResultSchema, + string? errorResultName, + string? errorResultSchema, + string? headerCodeName, + string? headerCodeSchema, + string? headerInfoName, + string? headerInfoSchema, + List? codeValues, + bool doesCommandTargetExecutor) + { + string serializerClassName = format.GetSerializerClassName(); + EmptyTypeName serializerEmptyType = format.GetEmptyTypeName(); + + ITypeName? inputSchema = EnvoyGeneratorSupport.GetTypeName(inputSchemaType, format); + ITypeName? outputSchema = EnvoyGeneratorSupport.GetTypeName(outputSchemaType, format); + + List normalFields = normalResultFields.Select(f => new CodeName(f)).ToList(); + List requiredFields = normalRequiredFields.Select(f => new CodeName(f.Value.Value)).ToList(); + ITypeName? normalSchema = EnvoyGeneratorSupport.GetTypeName(normalResultSchema, format); + CodeName? errorName = errorResultName != null ? new CodeName(errorResultName) : null; + CodeName? errorSchema = errorResultSchema != null ? new CodeName(errorResultSchema) : null; + + CodeName? codeName = headerCodeName != null ? new CodeName(headerCodeName) : null; + CodeName? codeSchema = headerCodeSchema != null ? new CodeName(headerCodeSchema) : null; + CodeName? infoName = headerInfoName != null ? new CodeName(headerInfoName) : null; + CodeName? infoSchema = headerInfoSchema != null ? new CodeName(headerInfoSchema) : null; + + switch (targetLanguage) + { + case TargetLanguage.CSharp: + if (generateServer) + { + yield return new DotNetCommandExecutor( + actionName, + schemaNamer.GetActionExecutorBinder(actionName), + projectName, + genNamespace, + serviceName, + serializerClassName, + serializerEmptyType, + inputSchema, + outputSchema, + serviceGroupId, + topicPattern, + idempotent); + } + + if (generateClient) + { + yield return new DotNetCommandInvoker( + actionName, + schemaNamer.GetActionInvokerBinder(actionName), + projectName, + genNamespace, + serviceName, + serializerClassName, + serializerEmptyType, + inputSchema, + outputSchema, + topicPattern); + } + + if (outputSchema != null && codeName != null && codeValues != null) + { + yield return new DotNetResponseExtension( + projectName, + genNamespace, + outputSchema, + codeName, + codeSchema!, + infoName, + infoSchema, + codeValues, + generateClient, + generateServer); + } + + break; + case TargetLanguage.Rust: + if (generateServer) + { + yield return new RustCommandExecutor( + actionName, + schemaNamer.GetActionExecutorBinder(actionName), + genNamespace, + serializerEmptyType, + inputSchema, + outputSchema, + normalFields, + requiredFields, + normalSchema, + errorName, + errorSchema, + idempotent, + serviceGroupId, + topicPattern, + srcSubdir); + if (codeName != null && codeValues != null) + { + yield return new RustCommandExecutorHeaders(actionName, schemaNamer.GetActionExecutorBinder(actionName), genNamespace, codeName, codeSchema!, infoName, infoSchema, codeValues, srcSubdir); + } + } + + if (generateClient) + { + yield return new RustCommandInvoker( + actionName, + schemaNamer.GetActionInvokerBinder(actionName), + genNamespace, + serializerEmptyType, + inputSchema, + outputSchema, + normalFields, + requiredFields, + normalSchema, + errorName, + errorSchema, + topicPattern, + doesCommandTargetExecutor, + srcSubdir); + if (codeName != null && codeValues != null) + { + yield return new RustCommandInvokerHeaders(actionName, schemaNamer.GetActionInvokerBinder(actionName), genNamespace, codeName, codeSchema!, infoName, infoSchema, codeValues, srcSubdir); + } + } + + break; + default: + throw new NotSupportedException($"Target language {targetLanguage} is not supported."); + } + } + + internal IEnumerable GetPropertyTransforms( + SchemaNamer schemaNamer, + CodeName serviceName, + string propertyName, + string propSchema, + string? readRespSchema, + string? writeReqSchema, + string? writeRespSchema, + string? propValueName, + string? readErrorName, + string? readErrorSchema, + string? writeErrorName, + string? writeErrorSchema, + SerializationFormat readFormat, + SerializationFormat writeFormat, + string readTopicPattern, + string writeTopicPattern, + bool separateProperties, + bool doesPropertyTargetReadMaintainer, + bool doesPropertyTargetWriteMaintainer) + { + string readSerializerClassName = readFormat.GetSerializerClassName(); + EmptyTypeName readSerializerEmptyType = readFormat.GetEmptyTypeName(); + string writeSerializerClassName = writeFormat.GetSerializerClassName(); + EmptyTypeName writeSerializerEmptyType = writeFormat.GetEmptyTypeName(); + + switch (targetLanguage) + { + case TargetLanguage.CSharp: + if (generateServer) + { + yield return new DotNetPropertyMaintainer( + propertyName, + schemaNamer.GetPropMaintainerBinder(propSchema), + schemaNamer.ReadResponderBinder, + schemaNamer.WriteResponderBinder, + schemaNamer.GetPropReadActName(propertyName), + schemaNamer.GetPropWriteActName(propertyName), + projectName, + genNamespace, + serviceName, + readSerializerClassName, + readSerializerEmptyType, + writeSerializerClassName, + writeSerializerEmptyType, + readRespSchema, + writeReqSchema, + writeRespSchema, + readTopicPattern, + writeTopicPattern); + } + + if (generateClient) + { + yield return new DotNetPropertyConsumer( + propertyName, + schemaNamer.GetPropConsumerBinder(propSchema), + schemaNamer.ReadRequesterBinder, + schemaNamer.WriteRequesterBinder, + schemaNamer.GetPropReadActName(propertyName), + schemaNamer.GetPropWriteActName(propertyName), + projectName, + genNamespace, + serviceName, + readSerializerClassName, + readSerializerEmptyType, + writeSerializerClassName, + writeSerializerEmptyType, + readRespSchema, + writeReqSchema, + writeRespSchema, + readTopicPattern, + writeTopicPattern); + } + + break; + case TargetLanguage.Rust: + if (generateServer) + { + yield return new RustPropertyMaintainer( + propertyName, + new CodeName(propSchema), + schemaNamer.GetPropMaintainerBinder(propSchema), + schemaNamer.GetPropReadActName(propertyName), + schemaNamer.GetPropWriteActName(propertyName), + genNamespace, + readSerializerEmptyType, + writeSerializerEmptyType, + readRespSchema, + writeReqSchema, + writeRespSchema, + propValueName, + readErrorName, + readErrorSchema, + writeErrorName, + writeErrorSchema, + readTopicPattern, + writeTopicPattern, + srcSubdir, + separateProperties); + } + + if (generateClient) + { + yield return new RustPropertyConsumer( + propertyName, + new CodeName(propSchema), + schemaNamer.GetPropConsumerBinder(propSchema), + schemaNamer.GetPropReadActName(propertyName), + schemaNamer.GetPropWriteActName(propertyName), + genNamespace, + readSerializerEmptyType, + writeSerializerEmptyType, + readRespSchema, + writeReqSchema, + writeRespSchema, + propValueName, + readErrorName, + readErrorSchema, + writeErrorName, + writeErrorSchema, + readTopicPattern, + writeTopicPattern, + srcSubdir, + doesPropertyTargetReadMaintainer, + doesPropertyTargetWriteMaintainer, + separateProperties); + } + + break; + default: + throw new NotSupportedException($"Target language {targetLanguage} is not supported."); + } + } + + internal IEnumerable GetEventTransforms( + SchemaNamer schemaNamer, + CodeName serviceName, + string eventName, + string schemaType, + SerializationFormat format, + string? serviceGroupId, + string topicPattern) + { + string serializerClassName = format.GetSerializerClassName(); + EmptyTypeName serializerEmptyType = format.GetEmptyTypeName(); + + switch (targetLanguage) + { + case TargetLanguage.CSharp: + if (generateServer) + { + yield return new DotNetTelemetrySender(eventName, schemaNamer.GetEventSenderBinder(schemaType), projectName, genNamespace, serviceName, serializerClassName, serializerEmptyType, EnvoyGeneratorSupport.GetTypeName(schemaType, format), topicPattern); + } + + if (generateClient) + { + yield return new DotNetTelemetryReceiver(eventName, schemaNamer.GetEventReceiverBinder(schemaType), projectName, genNamespace, serviceName, serializerClassName, serializerEmptyType, EnvoyGeneratorSupport.GetTypeName(schemaType, format), serviceGroupId, topicPattern); + } + + break; + case TargetLanguage.Rust: + if (generateServer) + { + yield return new RustTelemetrySender(eventName, schemaNamer.GetEventSenderBinder(schemaType), genNamespace, EnvoyGeneratorSupport.GetTypeName(schemaType, format), topicPattern, schemaType, srcSubdir); + } + + if (generateClient) + { + yield return new RustTelemetryReceiver(eventName, schemaNamer.GetEventReceiverBinder(schemaType), genNamespace, EnvoyGeneratorSupport.GetTypeName(schemaType, format), serviceGroupId, topicPattern, schemaType, srcSubdir); + } + + break; + default: + throw new NotSupportedException($"Target language {targetLanguage} is not supported."); + } + } + + internal IEnumerable GetErrorTransforms(ErrorSpec errorSpec) + { + switch (targetLanguage) + { + case TargetLanguage.CSharp: + yield return new DotNetError( + projectName, + new CodeName(errorSpec.SchemaName), + genNamespace, + errorSpec.ErrorCodeName != null ? new CodeName(errorSpec.ErrorCodeName) : null, + errorSpec.ErrorCodeSchema != null ? new CodeName(errorSpec.ErrorCodeSchema): null, + errorSpec.ErrorInfoName != null ? new CodeName(errorSpec.ErrorInfoName) : null, + errorSpec.ErrorInfoSchema != null ? new CodeName(errorSpec.ErrorInfoSchema) : null, + errorSpec.Description, + errorSpec.MessageField != null ? new CodeName(errorSpec.MessageField) : null, + errorSpec.MessageIsRequired); + break; + case TargetLanguage.Rust: + yield return new RustError( + new CodeName(errorSpec.SchemaName), + genNamespace, + errorSpec.Description, + errorSpec.MessageField != null ? new CodeName(errorSpec.MessageField) : null, + errorSpec.MessageIsRequired, + srcSubdir); + break; + default: + throw new NotSupportedException($"Target language {targetLanguage} is not supported."); + } + } + + internal IEnumerable GetAggregateErrorTransforms(AggregateErrorSpec errorSpec) + { + switch (targetLanguage) + { + case TargetLanguage.CSharp: + yield return new DotNetAggregateError( + projectName, + new CodeName(errorSpec.SchemaName), + genNamespace, + errorSpec.InnerErrors.Select(kv => (new CodeName(kv.Key), new CodeName(kv.Value))).ToList()); + break; + case TargetLanguage.Rust: + yield return new RustAggregateError( + new CodeName(errorSpec.SchemaName), + genNamespace, + errorSpec.InnerErrors.Select(kv => (new CodeName(kv.Key), new CodeName(kv.Value))).ToList(), + srcSubdir); + break; + default: + throw new NotSupportedException($"Target language {targetLanguage} is not supported."); + } + } + + internal IEnumerable GetSerializationTransforms(string serializableType, SerializationFormat format) + { + switch (targetLanguage) + { + case TargetLanguage.CSharp: + break; + case TargetLanguage.Rust: + if (format != SerializationFormat.Raw && format != SerializationFormat.Custom) + { + yield return new RustSerialization( + genNamespace, + format, + new CodeName(serializableType), + srcSubdir); + } + break; + default: + throw new NotSupportedException($"Target language {targetLanguage} is not supported."); + } + } + + internal IEnumerable GetServiceTransforms(SchemaNamer schemaNamer, CodeName serviceName, List actionSpecs, List propSpecs, List eventSpecs) + { + switch (targetLanguage) + { + case TargetLanguage.CSharp: + + yield return new DotNetService( + schemaNamer.ReadRequesterBinder, + schemaNamer.WriteRequesterBinder, + schemaNamer.ReadResponderBinder, + schemaNamer.WriteResponderBinder, + projectName, + genNamespace, + serviceName, + actionSpecs, + propSpecs, + eventSpecs, + generateClient, + generateServer, + defaultImpl); + + break; + case TargetLanguage.Rust: + yield break; + default: + throw new NotSupportedException($"Target language {targetLanguage} is not supported."); + } + } + + internal IEnumerable GetProjectTransforms(List genFormats, string? sdkPath, List envoyFilenames, bool generateProject) + { + switch (targetLanguage) + { + case TargetLanguage.CSharp: + yield return new DotNetProject(projectName, sdkPath); + break; + case TargetLanguage.Rust: + yield return new RustIndex(genNamespace, envoyFilenames, generateClient, generateServer, srcSubdir); + yield return new RustLib(genNamespace, generateProject, srcSubdir); + yield return new RustCargoToml(projectName, genFormats, sdkPath, generateProject, srcSubdir); + break; + default: + throw new NotSupportedException($"Target language {targetLanguage} is not supported."); + } + } + + internal IEnumerable GetResourceTransforms(List genFormats) + { + foreach (SerializationFormat genFormat in genFormats) + { + string serializerSubNamespace = genFormat.GetSerializerSubNamespace(); + + var (folder, ext) = targetLanguage switch + { + TargetLanguage.CSharp => ("csharp", "cs"), + TargetLanguage.Rust => ("rust", "rs"), + _ => throw GetLanguageNotRecognizedException(targetLanguage) + }; + + foreach (string subNamespace in new List { ResourceTransform.LanguageCommonFolder, serializerSubNamespace }) + { + foreach (string resourceName in Assembly.GetExecutingAssembly().GetManifestResourceNames()) + { + Regex rx = new($"^{Assembly.GetExecutingAssembly().GetName().Name}\\.{ResourceTransform.LanguageResourcesFolder}\\.{folder}\\.({subNamespace})(?:\\.(\\w+(?:\\.\\w+)*))?\\.(\\w+)\\.{ext}$", RegexOptions.IgnoreCase); + Match? match = rx.Match(resourceName); + if (match.Success) + { + string subFolder = match.Groups[1].Captures[0].Value; + string resourcePath = match.Groups[2].Captures.Count > 0 ? match.Groups[2].Captures[0].Value : string.Empty; + string resourceFile = match.Groups[3].Captures[0].Value; + + StreamReader resourceReader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)!); + + yield return new ResourceTransform(targetLanguage, projectName, subFolder, resourcePath, resourceFile, ext, resourceReader.ReadToEnd(), srcSubdir); + } + } + } + } + } + + private static Exception GetLanguageNotRecognizedException(TargetLanguage language) + { + return new Exception($"language '{language}' not recognized"); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ErrorSpec.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ErrorSpec.cs new file mode 100644 index 0000000000..b4a559db23 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ErrorSpec.cs @@ -0,0 +1,4 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + public record ErrorSpec(string SchemaName, string Description, string? MessageField, bool MessageIsRequired, string? ErrorCodeName = null, string? ErrorCodeSchema = null, string? ErrorInfoName = null, string? ErrorInfoSchema = null); +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EventEnvoyGenerator.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EventEnvoyGenerator.cs new file mode 100644 index 0000000000..6740159eef --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EventEnvoyGenerator.cs @@ -0,0 +1,47 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal static class EventEnvoyGenerator + { + internal static List GenerateEventEnvoys(ErrorReporter errorReporter, TDThing tdThing, SchemaNamer schemaNamer, CodeName serviceName, EnvoyTransformFactory envoyFactory, Dictionary transforms, Dictionary> formattedTypesToSerialize) + { + List eventSpecs = new(); + + foreach (KeyValuePair> eventKvp in tdThing.Events?.Entries ?? new()) + { + TDEvent eachEvent = eventKvp.Value.Value; + FormInfo? subEventForm = FormInfo.CreateFromForm(errorReporter, eachEvent.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpSubEvent) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + subEventForm ??= FormInfo.CreateFromForm(errorReporter, eachEvent.Forms?.Elements?.FirstOrDefault(f => f.Value.Op == null)?.Value, tdThing.SchemaDefinitions?.Entries); + + if (subEventForm != null && subEventForm.Format != SerializationFormat.None && subEventForm.TopicPattern != null) + { + string schemaType = schemaNamer.GetEventSchema(eventKvp.Key); + formattedTypesToSerialize[subEventForm.Format].Add(schemaType); + eventSpecs.Add(new EventSpec(schemaNamer, eventKvp.Key, schemaType, subEventForm.Format)); + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetEventTransforms(schemaNamer, serviceName, eventKvp.Key, schemaType, subEventForm.Format, subEventForm.ServiceGroupId, subEventForm.TopicPattern)) + { + transforms[transform.FileName] = transform; + } + } + } + + FormInfo? subAllEventsForm = FormInfo.CreateFromForm(errorReporter, tdThing.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpSubAllEvents) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + if (subAllEventsForm != null && subAllEventsForm.Format != SerializationFormat.None && subAllEventsForm.TopicPattern != null) + { + formattedTypesToSerialize[subAllEventsForm.Format].Add(schemaNamer.AggregateEventSchema); + eventSpecs.Add(new EventSpec(schemaNamer, schemaNamer.AggregateEventName, schemaNamer.AggregateEventSchema, subAllEventsForm.Format)); + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetEventTransforms(schemaNamer, serviceName, schemaNamer.AggregateEventName, schemaNamer.AggregateEventSchema, subAllEventsForm.Format, subAllEventsForm.ServiceGroupId, subAllEventsForm.TopicPattern)) + { + transforms[transform.FileName] = transform; + } + } + + return eventSpecs; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EventSpec.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EventSpec.cs new file mode 100644 index 0000000000..3c0babd651 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/EventSpec.cs @@ -0,0 +1,16 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public record EventSpec(CodeName Name, CodeName Sender, CodeName Receiver, ITypeName Schema) + { + public EventSpec(SchemaNamer schemaNamer, string eventName, string schemaType, SerializationFormat format) + : this( + new CodeName(eventName), + new CodeName(schemaNamer.GetEventSenderBinder(schemaType)), + new CodeName(schemaNamer.GetEventReceiverBinder(schemaType)), + EnvoyGeneratorSupport.GetTypeName(schemaType, format)) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/IEnvoyTemplateTransform.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/IEnvoyTemplateTransform.cs new file mode 100644 index 0000000000..2668cc34b1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/IEnvoyTemplateTransform.cs @@ -0,0 +1,11 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + internal interface IEnvoyTemplateTransform + { + string FileName { get; } + + string FolderPath { get; } + + string TransformText(); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/PropertyEnvoyGenerator.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/PropertyEnvoyGenerator.cs new file mode 100644 index 0000000000..3784a7fecf --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/PropertyEnvoyGenerator.cs @@ -0,0 +1,241 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal static class PropertyEnvoyGenerator + { + internal static List GeneratePropertyEnvoys(ErrorReporter errorReporter, TDThing tdThing, SchemaNamer schemaNamer, CodeName serviceName, EnvoyTransformFactory envoyFactory, Dictionary transforms, Dictionary errorSpecs, Dictionary aggErrorSpecs, Dictionary> formattedTypesToSerialize) + { + List propertySpecs = new(); + Dictionary readInnerErrors = new(); + Dictionary writeInnerErrors = new(); + + foreach (KeyValuePair> propKvp in tdThing.Properties?.Entries ?? new()) + { + TDProperty? property = propKvp.Value.Value; + if (property == null) + { + continue; + } + + FormInfo? readPropForm = FormInfo.CreateFromForm(errorReporter, property.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpReadProp) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + FormInfo? writePropForm = FormInfo.CreateFromForm(errorReporter, property.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpWriteProp) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + FormInfo? noOpForm = FormInfo.CreateFromForm(errorReporter, property.Forms?.Elements?.FirstOrDefault(f => f.Value.Op == null)?.Value, tdThing.SchemaDefinitions?.Entries); + readPropForm ??= noOpForm; + writePropForm ??= noOpForm; + + string propSchema = schemaNamer.GetPropSchema(propKvp.Key); + string? readRespSchema = null; + string? readErrorSchema = GetAndRecordError(propKvp.Key, readPropForm, schemaNamer, errorSpecs, formattedTypesToSerialize, readInnerErrors); + if (readPropForm?.TopicPattern != null && readPropForm.Format != SerializationFormat.None) + { + readRespSchema = readPropForm.ErrorRespSchema != null ? schemaNamer.GetPropReadRespSchema(propKvp.Key) : propSchema; + formattedTypesToSerialize[readPropForm.Format].Add(readRespSchema); + formattedTypesToSerialize[readPropForm.Format].Add(propSchema); + } + + string? writeReqSchema = null; + string? writeRespSchema = null; + string? writeErrorSchema = null; + if (!property.ReadOnly?.Value.Value ?? false) + { + if (writePropForm?.TopicPattern != null && writePropForm.Format != SerializationFormat.None) + { + writeReqSchema = (property.Placeholder?.Value.Value ?? false) ? schemaNamer.GetWritablePropSchema(propKvp.Key) : propSchema; + formattedTypesToSerialize[writePropForm.Format].Add(writeReqSchema); + + if (writePropForm.HasErrorResponse) + { + writeRespSchema = schemaNamer.GetPropWriteRespSchema(propKvp.Key); + formattedTypesToSerialize[writePropForm.Format].Add(writeRespSchema); + } + } + + writeErrorSchema = GetAndRecordError(propKvp.Key, writePropForm, schemaNamer, errorSpecs, formattedTypesToSerialize, writeInnerErrors); + } + + if (readRespSchema != null || writeReqSchema != null) + { + SerializationFormat readFormat = readPropForm?.Format ?? SerializationFormat.None; + SerializationFormat writeFormat = writePropForm?.Format ?? SerializationFormat.None; + + string? readErrorName = readErrorSchema != null ? schemaNamer.GetPropReadRespErrorField(propKvp.Key, readErrorSchema) : null; + string? writeErrorName = writeErrorSchema != null ? schemaNamer.GetPropWriteRespErrorField(propKvp.Key, writeErrorSchema) : null; + + string readTopicPattern = readPropForm?.TopicPattern ?? string.Empty; + string writeTopicPattern = writePropForm?.TopicPattern ?? string.Empty; + + bool doesReadTargetMaintainer = DoesTopicReferToMaintainer(readTopicPattern); + bool doesWriteTargetMaintainer = DoesTopicReferToMaintainer(writeTopicPattern); + + propertySpecs.Add(new PropertySpec( + schemaNamer, + propKvp.Key, + propSchema, + readFormat, + writeFormat, + readRespSchema, + writeReqSchema, + writeRespSchema, + propKvp.Key, + readErrorName, + readErrorSchema, + writeErrorName, + writeErrorSchema, + doesReadTargetMaintainer, + doesWriteTargetMaintainer, + isAggregate: false)); + + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetPropertyTransforms( + schemaNamer, + serviceName, + propKvp.Key, + propSchema, + readRespSchema, + writeReqSchema, + writeRespSchema, + propKvp.Key, + readErrorName, + readErrorSchema, + writeErrorName, + writeErrorSchema, + readFormat, + writeFormat, + readPropForm?.TopicPattern ?? string.Empty, + writePropForm?.TopicPattern ?? string.Empty, + separateProperties: true, + doesReadTargetMaintainer, + doesWriteTargetMaintainer)) + { + transforms[transform.FileName] = transform; + } + } + } + + string? readAllRespSchema = null; + FormInfo? readAllPropsForm = FormInfo.CreateFromForm(errorReporter, tdThing.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpReadAllProps) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + if (readAllPropsForm?.TopicPattern != null && readAllPropsForm.Format != SerializationFormat.None) + { + readAllRespSchema = readAllPropsForm.HasErrorResponse ? schemaNamer.AggregatePropReadRespSchema : schemaNamer.AggregatePropSchema; + + formattedTypesToSerialize[readAllPropsForm.Format].Add(readAllRespSchema); + + if (readAllPropsForm.HasErrorResponse) + { + formattedTypesToSerialize[readAllPropsForm.Format].Add(schemaNamer.AggregatePropSchema); + formattedTypesToSerialize[readAllPropsForm.Format].Add(schemaNamer.AggregatePropReadErrSchema); + aggErrorSpecs[schemaNamer.AggregatePropReadErrSchema] = new AggregateErrorSpec(schemaNamer.AggregatePropReadErrSchema, readInnerErrors); + } + } + + string? writeMultiReqSchema = null; + string? writeMultiRespSchema = null; + FormInfo? writeMultPropsForm = FormInfo.CreateFromForm(errorReporter, tdThing.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpWriteMultProps) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + if (writeMultPropsForm?.TopicPattern != null && writeMultPropsForm.Format != SerializationFormat.None) + { + writeMultiReqSchema = schemaNamer.AggregatePropWriteSchema; + formattedTypesToSerialize[writeMultPropsForm.Format].Add(writeMultiReqSchema); + + if (writeMultPropsForm.HasErrorResponse) + { + writeMultiRespSchema = schemaNamer.AggregatePropWriteRespSchema; + formattedTypesToSerialize[writeMultPropsForm.Format].Add(writeMultiRespSchema); + formattedTypesToSerialize[writeMultPropsForm.Format].Add(schemaNamer.AggregatePropWriteErrSchema); + aggErrorSpecs[schemaNamer.AggregatePropWriteErrSchema] = new AggregateErrorSpec(schemaNamer.AggregatePropWriteErrSchema, writeInnerErrors); + } + } + + if (readAllRespSchema != null || writeMultiReqSchema != null) + { + SerializationFormat readFormat = readAllPropsForm?.Format ?? SerializationFormat.None; + SerializationFormat writeFormat = writeMultPropsForm?.Format ?? SerializationFormat.None; + + string? readErrorName = readAllPropsForm?.HasErrorResponse ?? false ? schemaNamer.AggregateRespErrorField : null; + string? readErrorSchema = readAllPropsForm?.HasErrorResponse ?? false ? schemaNamer.AggregatePropReadErrSchema : null; + string? writeErrorName = writeMultPropsForm?.HasErrorResponse ?? false ? schemaNamer.AggregateRespErrorField : null; + string? writeErrorSchema = writeMultPropsForm?.HasErrorResponse ?? false ? schemaNamer.AggregatePropWriteErrSchema : null; + + string readTopicPattern = readAllPropsForm?.TopicPattern ?? string.Empty; + string writeTopicPattern = writeMultPropsForm?.TopicPattern ?? string.Empty; + + bool doesReadTargetMaintainer = DoesTopicReferToMaintainer(readTopicPattern); + bool doesWriteTargetMaintainer = DoesTopicReferToMaintainer(writeTopicPattern); + + propertySpecs.Add(new PropertySpec( + schemaNamer, + schemaNamer.AggregatePropName, + schemaNamer.AggregatePropSchema, + readFormat, + writeFormat, + readAllRespSchema, + writeMultiReqSchema, + writeMultiRespSchema, + schemaNamer.AggregateReadRespValueField, + readErrorName, + readErrorSchema, + writeErrorName, + writeErrorSchema, + doesReadTargetMaintainer, + doesWriteTargetMaintainer, + isAggregate: true)); + + foreach (IEnvoyTemplateTransform transform in envoyFactory.GetPropertyTransforms( + schemaNamer, + serviceName, + schemaNamer.AggregatePropName, + schemaNamer.AggregatePropSchema, + readAllRespSchema, + writeMultiReqSchema, + writeMultiRespSchema, + schemaNamer.AggregateReadRespValueField, + readErrorName, + readErrorSchema, + writeErrorName, + writeErrorSchema, + readAllPropsForm?.Format ?? SerializationFormat.None, + writeMultPropsForm?.Format ?? SerializationFormat.None, + readAllPropsForm?.TopicPattern ?? string.Empty, + writeMultPropsForm?.TopicPattern ?? string.Empty, + separateProperties: false, + doesReadTargetMaintainer, + doesWriteTargetMaintainer)) + { + transforms[transform.FileName] = transform; + } + } + + return propertySpecs; + } + + private static string? GetAndRecordError(string propName, FormInfo? form, SchemaNamer schemaNamer, Dictionary errorSpecs, Dictionary> formattedTypesToSerialize, Dictionary innerErrors) + { + if (form?.ErrorRespSchema == null) + { + return null; + } + + string errSchemaName = schemaNamer.ChooseTitleOrName(form.ErrorRespSchema.Value.Title?.Value.Value, form.ErrorRespName)!; + if (form.ErrorRespFormat != SerializationFormat.None) + { + formattedTypesToSerialize[form.ErrorRespFormat].Add(errSchemaName); + } + + errorSpecs[errSchemaName] = new ErrorSpec( + errSchemaName, + form.ErrorRespSchema.Value.Description?.Value.Value ?? "The action could not be completed", + form.ErrorRespSchema.Value.ErrorMessage?.Value.Value, + form.ErrorRespSchema.Value.Required?.Elements?.Any(e => e.Value.Value == (form.ErrorRespSchema.Value.ErrorMessage?.Value.Value ?? string.Empty)) ?? false); + innerErrors[propName] = errSchemaName; + return errSchemaName; + } + + private static bool DoesTopicReferToMaintainer(string? topic) + { + return topic != null && topic.Contains($"{{{MqttTopicTokens.PropertyMaintainerId}}}"); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/PropertySpec.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/PropertySpec.cs new file mode 100644 index 0000000000..268f1f837c --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/PropertySpec.cs @@ -0,0 +1,62 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public record PropertySpec( + CodeName Name, + CodeName Consumer, + CodeName Maintainer, + CodeName PropSchema, + EmptyTypeName ReadSerializerEmptyType, + EmptyTypeName WriteSerializerEmptyType, + ITypeName? ReadRespSchema, + ITypeName? WriteReqSchema, + ITypeName? WriteRespSchema, + CodeName? PropValueName, + CodeName? ReadErrorName, + CodeName? ReadErrorSchema, + CodeName? WriteErrorName, + CodeName? WriteErrorSchema, + bool DoesReadTargetMaintainer, + bool DoesWriteTargetMaintainer, + bool IsAggregate) + { + public PropertySpec( + SchemaNamer schemaNamer, + string propertyName, + string propSchema, + SerializationFormat readFormat, + SerializationFormat writeFormat, + string? readRespSchema, + string? writeReqSchema, + string? writeRespSchema, + string? propValueName, + string? readErrorName, + string? readErrorSchema, + string? writeErrorName, + string? writeErrorSchema, + bool doesReadTargetMaintainer, + bool doesWriteTargetMaintainer, + bool isAggregate) + : this( + new CodeName(propertyName), + new CodeName(schemaNamer.GetPropConsumerBinder(propSchema)), + new CodeName(schemaNamer.GetPropMaintainerBinder(propSchema)), + new CodeName(propSchema), + readFormat.GetEmptyTypeName(), + writeFormat.GetEmptyTypeName(), + readRespSchema != null ? new CodeName(readRespSchema) : null, + writeReqSchema != null ? new CodeName(writeReqSchema) : null, + writeRespSchema != null ? new CodeName(writeRespSchema) : null, + propValueName != null ? new CodeName(propValueName) : null, + readErrorName != null ? new CodeName(readErrorName) : null, + readErrorSchema != null ? new CodeName(readErrorSchema) : null, + writeErrorName != null ? new CodeName(writeErrorName) : null, + writeErrorSchema != null ? new CodeName(writeErrorSchema) : null, + doesReadTargetMaintainer, + doesWriteTargetMaintainer, + isAggregate) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ResourceTransform.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ResourceTransform.cs new file mode 100644 index 0000000000..a19307c66c --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/ResourceTransform.cs @@ -0,0 +1,42 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using System.Text.RegularExpressions; + using Azure.Iot.Operations.CodeGeneration; + + internal class ResourceTransform : IEnvoyTemplateTransform + { + public const string LanguageResourcesFolder = "LanguageResources"; + public const string LanguageCommonFolder = "common"; + + private const string SourceComment = "This file will be copied into the folder for generated code."; + + private readonly string destComment = $"Code generated by Azure.Iot.Operations.ProtocolCompilerLib v{Assembly.GetExecutingAssembly().GetName().Version}; DO NOT EDIT."; + + private readonly string resourceText; + + private static readonly Dictionary namespaceReplacementRegexes = new() + { + { TargetLanguage.CSharp, new Regex(@"Azure\.Iot\.Operations\.Protocol\.UnitTests\.(?:Serializers\.\w+|Support)") }, + { TargetLanguage.Rust, new Regex(@"resources") }, + }; + + public ResourceTransform(TargetLanguage language, string projectName, string subFolder, string serializationPath, string serializationFile, string extension, string serializerCode, string srcSubdir) + { + this.FolderPath = Path.Combine(srcSubdir, serializationPath); + this.FileName = $"{serializationFile}.{extension}"; + this.resourceText = namespaceReplacementRegexes[language].Replace(serializerCode, projectName).Replace(SourceComment, destComment); + } + + public string FileName { get; } + + public string FolderPath { get; } + + public string TransformText() + { + return this.resourceText; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/TypedConstant.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/TypedConstant.cs new file mode 100644 index 0000000000..8005ffece2 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/TypedConstant.cs @@ -0,0 +1,6 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public record TypedConstant(string Type, object Value, string? Description); +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/code/DotNetCommandExecutor.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/code/DotNetCommandExecutor.cs new file mode 100644 index 0000000000..ad28a906e8 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/code/DotNetCommandExecutor.cs @@ -0,0 +1,52 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetCommandExecutor : IEnvoyTemplateTransform + { + private readonly CodeName commandName; + private readonly CodeName componentName; + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly CodeName serviceName; + private readonly string serializerClassName; + private readonly EmptyTypeName serializerEmptyType; + private readonly ITypeName? reqSchema; + private readonly ITypeName? respSchema; + private readonly string? serviceGroupId; + private readonly string topicPattern; + private readonly bool isIdempotent; + + public DotNetCommandExecutor( + string commandName, + string componentName, + string projectName, + CodeName genNamespace, + CodeName serviceName, + string serializerClassName, + EmptyTypeName serializerEmptyType, + ITypeName? reqSchema, + ITypeName? respSchema, + string? serviceGroupId, + string topicPattern, + bool isIdempotent) + { + this.commandName = new CodeName(commandName); + this.componentName = new CodeName(componentName); + this.projectName = projectName; + this.genNamespace = genNamespace; + this.serviceName = serviceName; + this.serializerClassName = serializerClassName; + this.serializerEmptyType = serializerEmptyType; + this.reqSchema = reqSchema; + this.respSchema = respSchema; + this.serviceGroupId = serviceGroupId; + this.topicPattern = topicPattern; + this.isIdempotent = isIdempotent; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/code/DotNetCommandInvoker.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/code/DotNetCommandInvoker.cs new file mode 100644 index 0000000000..8cf8051ba5 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/code/DotNetCommandInvoker.cs @@ -0,0 +1,46 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetCommandInvoker : IEnvoyTemplateTransform + { + private readonly CodeName commandName; + private readonly CodeName componentName; + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly CodeName serviceName; + private readonly string serializerClassName; + private readonly EmptyTypeName serializerEmptyType; + private readonly ITypeName? reqSchema; + private readonly ITypeName? respSchema; + private readonly string topicPattern; + + public DotNetCommandInvoker( + string commandName, + string componentName, + string projectName, + CodeName genNamespace, + CodeName serviceName, + string serializerClassName, + EmptyTypeName serializerEmptyType, + ITypeName? reqSchema, + ITypeName? respSchema, + string topicPattern) + { + this.commandName = new CodeName(commandName); + this.componentName = new CodeName(componentName); + this.projectName = projectName; + this.genNamespace = genNamespace; + this.serviceName = serviceName; + this.serializerClassName = serializerClassName; + this.serializerEmptyType = serializerEmptyType; + this.reqSchema = reqSchema; + this.respSchema = respSchema; + this.topicPattern = topicPattern; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/code/DotNetResponseExtension.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/code/DotNetResponseExtension.cs new file mode 100644 index 0000000000..66b2af6867 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/code/DotNetResponseExtension.cs @@ -0,0 +1,47 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetResponseExtension : IEnvoyTemplateTransform + { + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly ITypeName respSchema; + private readonly CodeName headerCodeName; + private readonly CodeName headerCodeSchema; + private readonly CodeName? headerInfoName; + private readonly CodeName? headerInfoSchema; + private readonly List headerCodeValues; + private readonly bool generateClient; + private readonly bool generateServer; + + public DotNetResponseExtension( + string projectName, + CodeName genNamespace, + ITypeName respSchema, + CodeName headerCodeName, + CodeName headerCodeSchema, + CodeName? headerInfoName, + CodeName? headerInfoSchema, + List headerCodeValues, + bool generateClient, + bool generateServer) + { + this.projectName = projectName; + this.genNamespace = genNamespace; + this.respSchema = respSchema; + this.headerCodeName = headerCodeName; + this.headerCodeSchema = headerCodeSchema; + this.headerInfoName = headerInfoName; + this.headerInfoSchema = headerInfoSchema; + this.headerCodeValues = headerCodeValues; + this.generateClient = generateClient; + this.generateServer = generateServer; + } + + public string FileName { get => $"{this.respSchema.GetFileName(TargetLanguage.CSharp, "extensions")}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandExecutor.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandExecutor.cs new file mode 100644 index 0000000000..d2311b15ad --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandExecutor.cs @@ -0,0 +1,356 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetCommandExecutor : DotNetCommandExecutorBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write(@" +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Xml; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.RPC; + using Azure.Iot.Operations.Protocol.Models; + using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n public static partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n /// \r\n /// Specializes a CommandExecutor " + + "class for Command \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.AsGiven)); + this.Write("\'.\r\n /// \r\n"); + if (this.isIdempotent) { + this.Write(" [CommandBehavior(idempotent: true)]\r\n"); + } + if (this.serviceGroupId != null) { + this.Write(" [ServiceGroupId(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceGroupId)); + this.Write("\")]\r\n"); + } + this.Write(" [CommandTopic(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.topicPattern)); + this.Write("\")]\r\n public class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" : CommandExecutor"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.TypeParams())); + this.Write("\r\n {\r\n /// \r\n /// Initializes a new instanc" + + "e of the class.\r\n /// \r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(ApplicationContext applicationContext, IMqttPubSubClient mqttClient)\r\n " + + " : base(applicationContext, mqttClient, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.AsGiven)); + this.Write("\", new "); + this.Write(this.ToStringHelper.ToStringWithCulture(string.Format(this.serializerClassName, this.TypeParams()))); + this.Write("())\r\n {\r\n if (mqttClient.ClientId != null)\r\n " + + " {\r\n TopicTokenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.ActionExecutorId)); + this.Write("\"] = mqttClient.ClientId;\r\n }\r\n }\r\n }\r\n }\r\n}\r" + + "\n"); + return this.GenerationEnvironment.ToString(); + } + + private string TypeParams() => $"<{this.reqSchema?.GetTypeName(TargetLanguage.CSharp) ?? serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}, {this.respSchema?.GetTypeName(TargetLanguage.CSharp) ?? serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"; + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetCommandExecutorBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandExecutor.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandExecutor.tt new file mode 100644 index 0000000000..033dc7c3f0 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandExecutor.tt @@ -0,0 +1,48 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Xml; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.RPC; + using Azure.Iot.Operations.Protocol.Models; + using <#=this.projectName#>; + + public static partial class <#=this.serviceName.GetTypeName(TargetLanguage.CSharp)#> + { + /// + /// Specializes a CommandExecutor class for Command '<#=this.commandName.AsGiven#>'. + /// +<# if (this.isIdempotent) { #> + [CommandBehavior(idempotent: true)] +<# } #> +<# if (this.serviceGroupId != null) { #> + [ServiceGroupId("<#=this.serviceGroupId#>")] +<# } #> + [CommandTopic("<#=this.topicPattern#>")] + public class <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#> : CommandExecutor<#=this.TypeParams()#> + { + /// + /// Initializes a new instance of the class. + /// + public <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#>(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) + : base(applicationContext, mqttClient, "<#=this.commandName.AsGiven#>", new <#=string.Format(this.serializerClassName, this.TypeParams())#>()) + { + if (mqttClient.ClientId != null) + { + TopicTokenMap["<#=MqttTopicTokens.ActionExecutorId#>"] = mqttClient.ClientId; + } + } + } + } +} +<#+ + private string TypeParams() => $"<{this.reqSchema?.GetTypeName(TargetLanguage.CSharp) ?? serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}, {this.respSchema?.GetTypeName(TargetLanguage.CSharp) ?? serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"; +#> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandInvoker.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandInvoker.cs new file mode 100644 index 0000000000..2b32f4c2cf --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandInvoker.cs @@ -0,0 +1,342 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetCommandInvoker : DotNetCommandInvokerBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n using System;\r\n using System.Collections.Generic;\r\n using Azure.Io" + + "t.Operations.Protocol;\r\n using Azure.Iot.Operations.Protocol.RPC;\r\n using " + + "Azure.Iot.Operations.Protocol.Models;\r\n using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n public static partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n /// \r\n /// Specializes the CommandInvoker" + + " class for Command \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.AsGiven)); + this.Write("\'.\r\n /// \r\n [CommandTopic(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.topicPattern)); + this.Write("\")]\r\n public class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" : CommandInvoker"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.TypeParams())); + this.Write("\r\n {\r\n /// \r\n /// Initializes a new instanc" + + "e of the class.\r\n /// \r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(ApplicationContext applicationContext, IMqttPubSubClient mqttClient)\r\n " + + " : base(applicationContext, mqttClient, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.AsGiven)); + this.Write("\", new "); + this.Write(this.ToStringHelper.ToStringWithCulture(string.Format(this.serializerClassName, this.TypeParams()))); + this.Write("())\r\n {\r\n this.ResponseTopicPrefix = \"clients/{invokerC" + + "lientId}\"; // default value, can be overwritten by user code\r\n\r\n " + + "if (mqttClient.ClientId != null)\r\n {\r\n TopicTo" + + "kenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.ActionInvokerId)); + this.Write("\"] = mqttClient.ClientId;\r\n }\r\n }\r\n }\r\n }\r\n}\r" + + "\n"); + return this.GenerationEnvironment.ToString(); + } + + private string TypeParams() => $"<{this.reqSchema?.GetTypeName(TargetLanguage.CSharp) ?? serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}, {this.respSchema?.GetTypeName(TargetLanguage.CSharp) ?? serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"; + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetCommandInvokerBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandInvoker.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandInvoker.tt new file mode 100644 index 0000000000..738f45be4b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetCommandInvoker.tt @@ -0,0 +1,42 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; + using System.Collections.Generic; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.RPC; + using Azure.Iot.Operations.Protocol.Models; + using <#=this.projectName#>; + + public static partial class <#=this.serviceName.GetTypeName(TargetLanguage.CSharp)#> + { + /// + /// Specializes the CommandInvoker class for Command '<#=this.commandName.AsGiven#>'. + /// + [CommandTopic("<#=this.topicPattern#>")] + public class <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#> : CommandInvoker<#=this.TypeParams()#> + { + /// + /// Initializes a new instance of the class. + /// + public <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#>(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) + : base(applicationContext, mqttClient, "<#=this.commandName.AsGiven#>", new <#=string.Format(this.serializerClassName, this.TypeParams())#>()) + { + this.ResponseTopicPrefix = "clients/{invokerClientId}"; // default value, can be overwritten by user code + + if (mqttClient.ClientId != null) + { + TopicTokenMap["<#=MqttTopicTokens.ActionInvokerId#>"] = mqttClient.ClientId; + } + } + } + } +} +<#+ + private string TypeParams() => $"<{this.reqSchema?.GetTypeName(TargetLanguage.CSharp) ?? serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}, {this.respSchema?.GetTypeName(TargetLanguage.CSharp) ?? serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"; +#> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetResponseExtension.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetResponseExtension.cs new file mode 100644 index 0000000000..463cd77638 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetResponseExtension.cs @@ -0,0 +1,401 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetResponseExtension : DotNetResponseExtensionBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n"); + +string codeName = this.headerCodeName.GetVariableName(TargetLanguage.CSharp); +string codeSchema = this.headerCodeSchema.GetTypeName(TargetLanguage.CSharp); +string infoName = this.headerInfoName?.GetVariableName(TargetLanguage.CSharp) ?? "errorPayload"; +string infoSchema = this.headerInfoSchema?.GetTypeName(TargetLanguage.CSharp) ?? "string"; + + this.Write("\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n using System;\r\n using System.Diagnostics.CodeAnalysis;\r\n using Sys" + + "tem.Text;\r\n using System.Text.Json;\r\n using Azure.Iot.Operations.Protocol." + + "RPC;\r\n\r\n public static class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetTypeName(TargetLanguage.CSharp, "extensions"))); + this.Write("\r\n {\r\n"); + if (this.generateServer) { + this.Write(" public static ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write("> WithApplicationError(this ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write("> extResp, "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeSchema)); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoSchema)); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(" = null)\r\n {\r\n string errorCode = "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(" switch\r\n {\r\n"); + foreach (string codeValue in this.headerCodeValues) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeSchema)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(codeValue)); + this.Write(" => \""); + this.Write(this.ToStringHelper.ToStringWithCulture(codeValue)); + this.Write("\",\r\n"); + } + this.Write(" _ => throw new InvalidOperationException($\"Unable to map "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeSchema)); + this.Write(".{"); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write("} to a valid string value\"),\r\n };\r\n\r\n return extResp.WithAp" + + "plicationError(errorCode, "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.headerInfoSchema != null ? $"{infoName} != null ? Encoding.UTF8.GetString(JsonSerializer.SerializeToUtf8Bytes({infoName})) : null" : infoName)); + this.Write(");\r\n }\r\n"); + } + if (this.generateClient && this.generateServer) { + this.Write("\r\n"); + } + if (this.generateClient) { + this.Write(" public static bool TryGetApplicationError(this ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write("> extResp, [NotNullWhen(true)] out "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeSchema)); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(", out "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoSchema)); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(")\r\n {\r\n if (!extResp.TryGetApplicationError(out string? errorCo" + + "de, out "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.headerInfoSchema != null ? "string? " : "")); + this.Write("errorPayload))\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(" = null;\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(" = null;\r\n return false;\r\n }\r\n\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(" = errorCode switch\r\n {\r\n"); + foreach (string codeValue in this.headerCodeValues) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(codeValue)); + this.Write("\" => "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeSchema)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(codeValue)); + this.Write(",\r\n"); + } + this.Write(" _ => throw new InvalidOperationException($\"Unable to map string \\" + + "\"{errorCode}\\\" to "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeSchema)); + this.Write(" enumeration value\")\r\n };\r\n\r\n"); + if (this.headerInfoSchema != null) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(" = errorPayload != null ? JsonSerializer.Deserialize<"); + this.Write(this.ToStringHelper.ToStringWithCulture(infoSchema)); + this.Write(">(Encoding.UTF8.GetBytes(errorPayload)) : null;\r\n\r\n"); + } + this.Write(" return true;\r\n }\r\n"); + } + this.Write(" }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetResponseExtensionBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetResponseExtension.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetResponseExtension.tt new file mode 100644 index 0000000000..cf734d4308 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Actions/t4/DotNetResponseExtension.tt @@ -0,0 +1,67 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ +<# +string codeName = this.headerCodeName.GetVariableName(TargetLanguage.CSharp); +string codeSchema = this.headerCodeSchema.GetTypeName(TargetLanguage.CSharp); +string infoName = this.headerInfoName?.GetVariableName(TargetLanguage.CSharp) ?? "errorPayload"; +string infoSchema = this.headerInfoSchema?.GetTypeName(TargetLanguage.CSharp) ?? "string"; +#> + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Text; + using System.Text.Json; + using Azure.Iot.Operations.Protocol.RPC; + + public static class <#=this.respSchema.GetTypeName(TargetLanguage.CSharp, "extensions")#> + { +<# if (this.generateServer) { #> + public static ExtendedResponse<<#=this.respSchema.GetTypeName(TargetLanguage.CSharp)#>> WithApplicationError(this ExtendedResponse<<#=this.respSchema.GetTypeName(TargetLanguage.CSharp)#>> extResp, <#=codeSchema#> <#=codeName#>, <#=infoSchema#>? <#=infoName#> = null) + { + string errorCode = <#=codeName#> switch + { +<# foreach (string codeValue in this.headerCodeValues) { #> + <#=codeSchema#>.<#=codeValue#> => "<#=codeValue#>", +<# } #> + _ => throw new InvalidOperationException($"Unable to map <#=codeSchema#>.{<#=codeName#>} to a valid string value"), + }; + + return extResp.WithApplicationError(errorCode, <#=this.headerInfoSchema != null ? $"{infoName} != null ? Encoding.UTF8.GetString(JsonSerializer.SerializeToUtf8Bytes({infoName})) : null" : infoName#>); + } +<# } #> +<# if (this.generateClient && this.generateServer) { #> + +<# } #> +<# if (this.generateClient) { #> + public static bool TryGetApplicationError(this ExtendedResponse<<#=this.respSchema.GetTypeName(TargetLanguage.CSharp)#>> extResp, [NotNullWhen(true)] out <#=codeSchema#>? <#=codeName#>, out <#=infoSchema#>? <#=infoName#>) + { + if (!extResp.TryGetApplicationError(out string? errorCode, out <#=this.headerInfoSchema != null ? "string? " : ""#>errorPayload)) + { + <#=codeName#> = null; + <#=infoName#> = null; + return false; + } + + <#=codeName#> = errorCode switch + { +<# foreach (string codeValue in this.headerCodeValues) { #> + "<#=codeValue#>" => <#=codeSchema#>.<#=codeValue#>, +<# } #> + _ => throw new InvalidOperationException($"Unable to map string \"{errorCode}\" to <#=codeSchema#> enumeration value") + }; + +<# if (this.headerInfoSchema != null) { #> + <#=infoName#> = errorPayload != null ? JsonSerializer.Deserialize<<#=infoSchema#>>(Encoding.UTF8.GetBytes(errorPayload)) : null; + +<# } #> + return true; + } +<# } #> + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Constants/code/DotNetConstants.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Constants/code/DotNetConstants.cs new file mode 100644 index 0000000000..0ae4aa47d1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Constants/code/DotNetConstants.cs @@ -0,0 +1,53 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetConstants : IEnvoyTemplateTransform + { + private readonly string projectName; + private readonly CodeName schemaName; + private readonly CodeName genNamespace; + private readonly ConstantsSpec constantSpec; + private readonly bool anyDescriptions; + + public DotNetConstants(string projectName, CodeName schemaName, CodeName genNamespace, ConstantsSpec constantSpec) + { + this.projectName = projectName; + this.schemaName = schemaName; + this.genNamespace = genNamespace; + this.constantSpec = constantSpec; + this.anyDescriptions = constantSpec.Constants.Any(c => c.Value.Description != null); + } + + public string FileName { get => $"{this.schemaName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + + private static string GetDotNetType(string type) + { + return type switch + { + TDValues.TypeString => "string", + TDValues.TypeNumber => "double", + TDValues.TypeInteger => "int", + TDValues.TypeBoolean => "bool", + _ => throw new System.ArgumentException($"Unsupported constant type: {type}"), + }; + } + + private static string GetDotNetValue(object value) + { + return value switch + { + string s => $"\"{s}\"", + double d => d.ToString(CultureInfo.InvariantCulture), + int i => i.ToString(CultureInfo.InvariantCulture), + bool b => b ? "true" : "false", + _ => throw new System.ArgumentException($"Unsupported constant value type: {value.GetType()}"), + }; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Constants/t4/DotNetConstants.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Constants/t4/DotNetConstants.cs new file mode 100644 index 0000000000..ae2c4a4576 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Constants/t4/DotNetConstants.cs @@ -0,0 +1,338 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetConstants : DotNetConstantsBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n"); + if (this.constantSpec.Description != null) { + this.Write(" /// \r\n /// "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.constantSpec.Description)); + this.Write("\r\n /// \r\n"); + } + this.Write(" [System.CodeDom.Compiler.GeneratedCode(\"Azure.Iot.Operations.ProtocolCompiler" + + "Lib\", \""); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("\")]\r\n public static class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n"); + int ix = 1; foreach (KeyValuePair constant in this.constantSpec.Constants) { + if (constant.Value.Description != null) { + this.Write(" /// "); + this.Write(this.ToStringHelper.ToStringWithCulture(constant.Value.Description)); + this.Write("\r\n"); + } + this.Write(" public const "); + this.Write(this.ToStringHelper.ToStringWithCulture(GetDotNetType(constant.Value.Type))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(constant.Key.GetConstantName(TargetLanguage.CSharp))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(GetDotNetValue(constant.Value.Value))); + this.Write(";\r\n"); + if (this.anyDescriptions && ix < this.constantSpec.Constants.Count) { + this.Write("\r\n"); + } + ix++; } + this.Write(" }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetConstantsBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Constants/t4/DotNetConstants.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Constants/t4/DotNetConstants.tt new file mode 100644 index 0000000000..c947642fce --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Constants/t4/DotNetConstants.tt @@ -0,0 +1,28 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ +<# if (this.constantSpec.Description != null) { #> + /// + /// <#=this.constantSpec.Description#> + /// +<# } #> + [System.CodeDom.Compiler.GeneratedCode("Azure.Iot.Operations.ProtocolCompilerLib", "<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>")] + public static class <#=this.schemaName.GetTypeName(TargetLanguage.CSharp)#> + { +<# int ix = 1; foreach (KeyValuePair constant in this.constantSpec.Constants) { #> +<# if (constant.Value.Description != null) { #> + /// <#=constant.Value.Description#> +<# } #> + public const <#=GetDotNetType(constant.Value.Type)#> <#=constant.Key.GetConstantName(TargetLanguage.CSharp)#> = <#=GetDotNetValue(constant.Value.Value)#>; +<# if (this.anyDescriptions && ix < this.constantSpec.Constants.Count) { #> + +<# } #> +<# ix++; } #> + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/code/DotNetAggregateError.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/code/DotNetAggregateError.cs new file mode 100644 index 0000000000..ce1fa829d1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/code/DotNetAggregateError.cs @@ -0,0 +1,25 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetAggregateError : IEnvoyTemplateTransform + { + private readonly string projectName; + private readonly CodeName schemaName; + private readonly CodeName schemaNamespace; + private readonly List<(CodeName, CodeName)> innerNameSchemas; + + public DotNetAggregateError(string projectName, CodeName schemaName, CodeName schemaNamespace, List<(CodeName, CodeName)> innerNameSchemas) + { + this.projectName = projectName; + this.schemaName = schemaName; + this.schemaNamespace = schemaNamespace; + this.innerNameSchemas = innerNameSchemas; + } + + public string FileName { get => $"{this.schemaName.GetFileName(TargetLanguage.CSharp, "exception")}.g.cs"; } + + public string FolderPath { get => this.schemaNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/code/DotNetError.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/code/DotNetError.cs new file mode 100644 index 0000000000..fd9e93d63f --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/code/DotNetError.cs @@ -0,0 +1,36 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetError : IEnvoyTemplateTransform + { + private readonly string projectName; + private readonly CodeName schemaName; + private readonly CodeName genNamespace; + private readonly CodeName? errorCodeName; + private readonly CodeName? errorCodeSchema; + private readonly CodeName? errorInfoName; + private readonly CodeName? errorInfoSchema; + private readonly string description; + private readonly CodeName? messageField; + private readonly bool messageIsRequired; + + public DotNetError(string projectName, CodeName schemaName, CodeName genNamespace, CodeName? errorCodeName, CodeName? errorCodeSchema, CodeName? errorInfoName, CodeName? errorInfoSchema, string description, CodeName? messageField, bool messageIsRequired) + { + this.projectName = projectName; + this.schemaName = schemaName; + this.genNamespace = genNamespace; + this.errorCodeName = errorCodeName; + this.errorCodeSchema = errorCodeSchema; + this.errorInfoName = errorInfoName; + this.errorInfoSchema = errorInfoSchema; + this.description = description; + this.messageField = messageField; + this.messageIsRequired = messageIsRequired; + } + + public string FileName { get => $"{this.schemaName.GetFileName(TargetLanguage.CSharp, "exception")}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetAggregateError.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetAggregateError.cs new file mode 100644 index 0000000000..2cbfe0c550 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetAggregateError.cs @@ -0,0 +1,349 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetAggregateError : DotNetAggregateErrorBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n using System;\r\n using System.Collections.Generic;\r\n using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n [System.CodeDom.Compiler.GeneratedCode(\"Azure.Iot.Operations.ProtocolCom" + + "pilerLib\", \""); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("\")]\r\n public partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write(" : AggregateException\r\n {\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetVariableName(TargetLanguage.CSharp))); + this.Write(")\r\n : base(GetInnerExceptions("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetVariableName(TargetLanguage.CSharp))); + this.Write("))\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetVariableName(TargetLanguage.CSharp))); + this.Write(";\r\n }\r\n\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" { get; }\r\n\r\n private static IEnumerable GetInnerExceptions("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetVariableName(TargetLanguage.CSharp))); + this.Write(")\r\n {\r\n List innerExceptions = new();\r\n"); + foreach (var innerNameSchema in this.innerNameSchemas) { + this.Write("\r\n if ("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetVariableName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(innerNameSchema.Item1.GetFieldName(TargetLanguage.CSharp))); + this.Write(" != null)\r\n {\r\n innerExceptions.Add(new "); + this.Write(this.ToStringHelper.ToStringWithCulture(innerNameSchema.Item2.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetVariableName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(innerNameSchema.Item1.GetFieldName(TargetLanguage.CSharp))); + this.Write("));\r\n }\r\n"); + } + this.Write("\r\n return innerExceptions;\r\n }\r\n }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetAggregateErrorBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetAggregateError.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetAggregateError.tt new file mode 100644 index 0000000000..2b3ba255f7 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetAggregateError.tt @@ -0,0 +1,38 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.schemaNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; + using System.Collections.Generic; + using <#=this.projectName#>; + + [System.CodeDom.Compiler.GeneratedCode("Azure.Iot.Operations.ProtocolCompilerLib", "<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>")] + public partial class <#=this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception")#> : AggregateException + { + public <#=this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception")#>(<#=this.schemaName.GetTypeName(TargetLanguage.CSharp)#> <#=this.schemaName.GetVariableName(TargetLanguage.CSharp)#>) + : base(GetInnerExceptions(<#=this.schemaName.GetVariableName(TargetLanguage.CSharp)#>)) + { + <#=this.schemaName.GetFieldName(TargetLanguage.CSharp)#> = <#=this.schemaName.GetVariableName(TargetLanguage.CSharp)#>; + } + + public <#=this.schemaName.GetTypeName(TargetLanguage.CSharp)#> <#=this.schemaName.GetFieldName(TargetLanguage.CSharp)#> { get; } + + private static IEnumerable GetInnerExceptions(<#=this.schemaName.GetTypeName(TargetLanguage.CSharp)#> <#=this.schemaName.GetVariableName(TargetLanguage.CSharp)#>) + { + List innerExceptions = new(); +<# foreach (var innerNameSchema in this.innerNameSchemas) { #> + + if (<#=this.schemaName.GetVariableName(TargetLanguage.CSharp)#>.<#=innerNameSchema.Item1.GetFieldName(TargetLanguage.CSharp)#> != null) + { + innerExceptions.Add(new <#=innerNameSchema.Item2.GetTypeName(TargetLanguage.CSharp, "exception")#>(<#=this.schemaName.GetVariableName(TargetLanguage.CSharp)#>.<#=innerNameSchema.Item1.GetFieldName(TargetLanguage.CSharp)#>)); + } +<# } #> + + return innerExceptions; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetError.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetError.cs new file mode 100644 index 0000000000..43fdf8d8b3 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetError.cs @@ -0,0 +1,406 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetError : DotNetErrorBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n"); + +string codeName = this.errorCodeName?.GetVariableName(TargetLanguage.CSharp); +string codeSchema = this.errorCodeSchema?.GetTypeName(TargetLanguage.CSharp); +string infoName = this.errorInfoName?.GetVariableName(TargetLanguage.CSharp) ?? "errorPayload"; +string infoSchema = this.errorInfoSchema?.GetTypeName(TargetLanguage.CSharp) ?? "string"; + + this.Write("\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n using System;\r\n"); + if (codeName != null) { + this.Write(" using System.Diagnostics.CodeAnalysis;\r\n"); + } + this.Write(" using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n [System.CodeDom.Compiler.GeneratedCode(\"Azure.Iot.Operations.ProtocolCom" + + "pilerLib\", \""); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("\")]\r\n public partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write(" : Exception\r\n {\r\n"); + if (codeName != null) { + this.Write(" private "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeSchema)); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(" = default;\r\n private "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoSchema)); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(" = default;\r\n\r\n"); + } + this.Write(" public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetVariableName(TargetLanguage.CSharp))); + this.Write(")\r\n"); + if (this.messageField != null) { + this.Write(" : base("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetVariableName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageField.GetFieldName(TargetLanguage.CSharp))); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageIsRequired ? "" : $" ?? \"{this.description}\"")); + this.Write(")\r\n"); + } else { + this.Write(" : base(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.description)); + this.Write("\")\r\n"); + } + this.Write(" {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetVariableName(TargetLanguage.CSharp))); + this.Write(";\r\n }\r\n\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" { get; }\r\n"); + if (codeName != null) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write(" WithApplicationError("); + this.Write(this.ToStringHelper.ToStringWithCulture(codeSchema)); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoSchema)); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(" = null)\r\n {\r\n this."); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(";\r\n this."); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(";\r\n\r\n return this;\r\n }\r\n\r\n public bool TryGetApplication" + + "Error([NotNullWhen(true)] out "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeSchema)); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(", out "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoSchema)); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(")\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(" = this."); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(";\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(" = this."); + this.Write(this.ToStringHelper.ToStringWithCulture(infoName)); + this.Write(";\r\n\r\n return "); + this.Write(this.ToStringHelper.ToStringWithCulture(codeName)); + this.Write(" != null;\r\n }\r\n"); + } + this.Write(" }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetErrorBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetError.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetError.tt new file mode 100644 index 0000000000..518ee08980 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Error/t4/DotNetError.tt @@ -0,0 +1,59 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ +<# +string codeName = this.errorCodeName?.GetVariableName(TargetLanguage.CSharp); +string codeSchema = this.errorCodeSchema?.GetTypeName(TargetLanguage.CSharp); +string infoName = this.errorInfoName?.GetVariableName(TargetLanguage.CSharp) ?? "errorPayload"; +string infoSchema = this.errorInfoSchema?.GetTypeName(TargetLanguage.CSharp) ?? "string"; +#> + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; +<# if (codeName != null) { #> + using System.Diagnostics.CodeAnalysis; +<# } #> + using <#=this.projectName#>; + + [System.CodeDom.Compiler.GeneratedCode("Azure.Iot.Operations.ProtocolCompilerLib", "<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>")] + public partial class <#=this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception")#> : Exception + { +<# if (codeName != null) { #> + private <#=codeSchema#>? <#=codeName#> = default; + private <#=infoSchema#>? <#=infoName#> = default; + +<# } #> + public <#=this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception")#>(<#=this.schemaName.GetTypeName(TargetLanguage.CSharp)#> <#=this.schemaName.GetVariableName(TargetLanguage.CSharp)#>) +<# if (this.messageField != null) { #> + : base(<#=this.schemaName.GetVariableName(TargetLanguage.CSharp)#>.<#=this.messageField.GetFieldName(TargetLanguage.CSharp)#><#=this.messageIsRequired ? "" : $" ?? \"{this.description}\""#>) +<# } else { #> + : base("<#=this.description#>") +<# } #> + { + <#=this.schemaName.GetFieldName(TargetLanguage.CSharp)#> = <#=this.schemaName.GetVariableName(TargetLanguage.CSharp)#>; + } + + public <#=this.schemaName.GetTypeName(TargetLanguage.CSharp)#> <#=this.schemaName.GetFieldName(TargetLanguage.CSharp)#> { get; } +<# if (codeName != null) { #> + + public <#=this.schemaName.GetTypeName(TargetLanguage.CSharp, "exception")#> WithApplicationError(<#=codeSchema#> <#=codeName#>, <#=infoSchema#>? <#=infoName#> = null) + { + this.<#=codeName#> = <#=codeName#>; + this.<#=infoName#> = <#=infoName#>; + + return this; + } + + public bool TryGetApplicationError([NotNullWhen(true)] out <#=codeSchema#>? <#=codeName#>, out <#=infoSchema#>? <#=infoName#>) + { + <#=codeName#> = this.<#=codeName#>; + <#=infoName#> = this.<#=infoName#>; + + return <#=codeName#> != null; + } +<# } #> + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/code/DotNetTelemetryReceiver.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/code/DotNetTelemetryReceiver.cs new file mode 100644 index 0000000000..cae0f8c2cd --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/code/DotNetTelemetryReceiver.cs @@ -0,0 +1,44 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetTelemetryReceiver : IEnvoyTemplateTransform + { + private readonly CodeName telemetryName; + private readonly CodeName componentName; + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly CodeName serviceName; + private readonly string serializerClassName; + private readonly ITypeName schemaType; + private readonly string? serviceGroupId; + private readonly string topicPattern; + + public DotNetTelemetryReceiver( + string telemetryName, + string componentName, + string projectName, + CodeName genNamespace, + CodeName serviceName, + string serializerClassName, + EmptyTypeName serializerEmptyType, + ITypeName schemaType, + string? serviceGroupId, + string topicPattern) + { + this.telemetryName = new CodeName(telemetryName); + this.componentName = new CodeName(componentName); + this.projectName = projectName; + this.genNamespace = genNamespace; + this.serviceName = serviceName; + this.serializerClassName = string.Format(serializerClassName, $"<{schemaType.GetTypeName(TargetLanguage.CSharp)}, {serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"); + this.schemaType = schemaType; + this.serviceGroupId = serviceGroupId; + this.topicPattern = topicPattern; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/code/DotNetTelemetrySender.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/code/DotNetTelemetrySender.cs new file mode 100644 index 0000000000..cede99bb2d --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/code/DotNetTelemetrySender.cs @@ -0,0 +1,41 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetTelemetrySender : IEnvoyTemplateTransform + { + private readonly CodeName telemetryName; + private readonly CodeName componentName; + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly CodeName serviceName; + private readonly string serializerClassName; + private readonly ITypeName schemaType; + private readonly string topicPattern; + + public DotNetTelemetrySender( + string telemetryName, + string componentName, + string projectName, + CodeName genNamespace, + CodeName serviceName, + string serializerClassName, + EmptyTypeName serializerEmptyType, + ITypeName schemaType, + string topicPattern) + { + this.telemetryName = new CodeName(telemetryName); + this.componentName = new CodeName(componentName); + this.projectName = projectName; + this.genNamespace = genNamespace; + this.serviceName = serviceName; + this.serializerClassName = string.Format(serializerClassName, $"<{schemaType.GetTypeName(TargetLanguage.CSharp)}, {serializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"); + this.schemaType = schemaType; + this.topicPattern = topicPattern; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetryReceiver.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetryReceiver.cs new file mode 100644 index 0000000000..48bc901f4e --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetryReceiver.cs @@ -0,0 +1,337 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetTelemetryReceiver : DotNetTelemetryReceiverBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n using System;\r\n using System.Collections.Generic;\r\n using Azure.Io" + + "t.Operations.Protocol;\r\n using Azure.Iot.Operations.Protocol.Telemetry;\r\n " + + "using Azure.Iot.Operations.Protocol.Models;\r\n using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n public static partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n /// \r\n /// Specializes the TelemetryReceiver<" + + "/c> class for type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.CSharp))); + this.Write(".\r\n /// \r\n"); + if (this.serviceGroupId != null) { + this.Write(" [ServiceGroupId(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceGroupId)); + this.Write("\")]\r\n"); + } + this.Write(" [TelemetryTopic(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.topicPattern)); + this.Write("\")]\r\n public class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" : TelemetryReceiver<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.CSharp))); + this.Write(">\r\n {\r\n /// \r\n /// Initializes a new instan" + + "ce of the class.\r\n /// \r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(ApplicationContext applicationContext, IMqttPubSubClient mqttClient)\r\n " + + " : base(applicationContext, mqttClient, new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializerClassName)); + this.Write("())\r\n {\r\n }\r\n }\r\n }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetTelemetryReceiverBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetryReceiver.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetryReceiver.tt new file mode 100644 index 0000000000..863936278a --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetryReceiver.tt @@ -0,0 +1,36 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; + using System.Collections.Generic; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.Telemetry; + using Azure.Iot.Operations.Protocol.Models; + using <#=this.projectName#>; + + public static partial class <#=this.serviceName.GetTypeName(TargetLanguage.CSharp)#> + { + /// + /// Specializes the TelemetryReceiver class for type <#=this.schemaType.GetTypeName(TargetLanguage.CSharp)#>. + /// +<# if (this.serviceGroupId != null) { #> + [ServiceGroupId("<#=this.serviceGroupId#>")] +<# } #> + [TelemetryTopic("<#=this.topicPattern#>")] + public class <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#> : TelemetryReceiver<<#=this.schemaType.GetTypeName(TargetLanguage.CSharp)#>> + { + /// + /// Initializes a new instance of the class. + /// + public <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#>(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) + : base(applicationContext, mqttClient, new <#=this.serializerClassName#>()) + { + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetrySender.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetrySender.cs new file mode 100644 index 0000000000..ad415f7ea4 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetrySender.cs @@ -0,0 +1,335 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetTelemetrySender : DotNetTelemetrySenderBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n using System.Collections.Generic;\r\n using Azure.Iot.Operations.Protoc" + + "ol;\r\n using Azure.Iot.Operations.Protocol.Telemetry;\r\n using Azure.Iot.Ope" + + "rations.Protocol.Models;\r\n using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n public static partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n /// \r\n /// Specializes the TelemetrySender class for type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.CSharp))); + this.Write(".\r\n /// \r\n [TelemetryTopic(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.topicPattern)); + this.Write("\")]\r\n public class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" : TelemetrySender<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.CSharp))); + this.Write(">\r\n {\r\n /// \r\n /// Initializes a new instan" + + "ce of the class.\r\n /// \r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(ApplicationContext applicationContext, IMqttPubSubClient mqttClient)\r\n " + + " : base(applicationContext, mqttClient, new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializerClassName)); + this.Write("())\r\n {\r\n if (mqttClient.ClientId != null)\r\n " + + " {\r\n TopicTokenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.EventSenderId)); + this.Write("\"] = mqttClient.ClientId;\r\n }\r\n }\r\n }\r\n }\r\n}\r" + + "\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetTelemetrySenderBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetrySender.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetrySender.tt new file mode 100644 index 0000000000..1d72c32edc --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Events/t4/DotNetTelemetrySender.tt @@ -0,0 +1,36 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System.Collections.Generic; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.Telemetry; + using Azure.Iot.Operations.Protocol.Models; + using <#=this.projectName#>; + + public static partial class <#=this.serviceName.GetTypeName(TargetLanguage.CSharp)#> + { + /// + /// Specializes the TelemetrySender class for type <#=this.schemaType.GetTypeName(TargetLanguage.CSharp)#>. + /// + [TelemetryTopic("<#=this.topicPattern#>")] + public class <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#> : TelemetrySender<<#=this.schemaType.GetTypeName(TargetLanguage.CSharp)#>> + { + /// + /// Initializes a new instance of the class. + /// + public <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#>(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) + : base(applicationContext, mqttClient, new <#=this.serializerClassName#>()) + { + if (mqttClient.ClientId != null) + { + TopicTokenMap["<#=MqttTopicTokens.EventSenderId#>"] = mqttClient.ClientId; + } + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Project/code/DotNetProject.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Project/code/DotNetProject.cs new file mode 100644 index 0000000000..223189d4ec --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Project/code/DotNetProject.cs @@ -0,0 +1,36 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System; + using System.Reflection; + using System.Runtime.Versioning; + using System.Text.RegularExpressions; + + public partial class DotNetProject : IEnvoyTemplateTransform + { + internal const string SdkPackageName = "Azure.Iot.Operations.Protocol"; + internal const string SdkProjectName = $"{SdkPackageName}.csproj"; + + private static readonly Regex MajorMinorRegex = new("^(\\d+\\.\\d+).", RegexOptions.Compiled); + + private readonly string projectName; + private readonly string? sdkProjPath; + private readonly string? sdkVersion; + private readonly string? targetFramework; + + public DotNetProject(string projectName, string? sdkPath) + { + this.projectName = projectName; + this.sdkProjPath = sdkPath != null ? $"{sdkPath.Replace('/', '\\')}\\{SdkProjectName}" : null; + + Match? majorMinorMatch = MajorMinorRegex.Match(Assembly.GetExecutingAssembly().GetName().Version!.ToString()); + sdkVersion = majorMinorMatch.Success ? $"{majorMinorMatch.Groups[1].Captures[0].Value}.*-*" : null; + + Version frameworkVersion = new FrameworkName(Assembly.GetExecutingAssembly().GetCustomAttribute()?.FrameworkName!).Version; + this.targetFramework = $"net{frameworkVersion}"; + } + + public string FileName { get => $"{this.projectName}.csproj"; } + + public string FolderPath { get => string.Empty; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Project/t4/DotNetProject.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Project/t4/DotNetProject.cs new file mode 100644 index 0000000000..88abaf42ae --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Project/t4/DotNetProject.cs @@ -0,0 +1,317 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetProject : DotNetProjectBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("\r\n\r\n \r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.targetFramework)); + this.Write("\r\n latest-recommended\r\n enable\r\n SA0001,SA1101,SA1633\r\n \r\n\r\n \r\n"); + if (this.sdkProjPath != null) { + this.Write(" \r\n"); + } else { + this.Write(" \r\n"); + } + this.Write(" \r\n\r\n\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetProjectBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Project/t4/DotNetProject.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Project/t4/DotNetProject.tt new file mode 100644 index 0000000000..b2ddb5291e --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Project/t4/DotNetProject.tt @@ -0,0 +1,19 @@ +<#@ template language="C#" linePragmas="false" #> + + + + <#=this.targetFramework#> + latest-recommended + enable + SA0001,SA1101,SA1633 + + + +<# if (this.sdkProjPath != null) { #> + +<# } else { #> + /> +<# } #> + + + diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/code/DotNetPropertyConsumer.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/code/DotNetPropertyConsumer.cs new file mode 100644 index 0000000000..5f63d6591f --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/code/DotNetPropertyConsumer.cs @@ -0,0 +1,70 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetPropertyConsumer : IEnvoyTemplateTransform + { + private readonly CodeName propertyName; + private readonly CodeName componentName; + private readonly CodeName readerName; + private readonly CodeName writerName; + private readonly string readCommandName; + private readonly string writeCommandName; + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly CodeName serviceName; + private readonly string readSerializerClassName; + private readonly EmptyTypeName readSerializerEmptyType; + private readonly string writeSerializerClassName; + private readonly EmptyTypeName writeSerializerEmptyType; + private readonly CodeName? readRespSchema; + private readonly CodeName? writeReqSchema; + private readonly CodeName? writeRespSchema; + private readonly string readTopicPattern; + private readonly string writeTopicPattern; + + public DotNetPropertyConsumer( + string propertyName, + string componentName, + string readerName, + string writerName, + string readCommandName, + string writeCommandName, + string projectName, + CodeName genNamespace, + CodeName serviceName, + string readSerializerClassName, + EmptyTypeName readSerializerEmptyType, + string writeSerializerClassName, + EmptyTypeName writeSerializerEmptyType, + string? readRespSchema, + string? writeReqSchema, + string? writeRespSchema, + string readTopicPattern, + string writeTopicPattern) + { + this.propertyName = new CodeName(propertyName); + this.componentName = new CodeName(componentName); + this.readerName = new CodeName(readerName); + this.writerName = new CodeName(writerName); + this.readCommandName = readCommandName; + this.writeCommandName = writeCommandName; + this.projectName = projectName; + this.genNamespace = genNamespace; + this.serviceName = serviceName; + this.readSerializerClassName = readSerializerClassName; + this.readSerializerEmptyType = readSerializerEmptyType; + this.writeSerializerClassName = writeSerializerClassName; + this.writeSerializerEmptyType = writeSerializerEmptyType; + this.readRespSchema = readRespSchema != null ? new CodeName(readRespSchema) : null; + this.writeReqSchema = writeReqSchema != null ? new CodeName(writeReqSchema) : null; + this.writeRespSchema = writeRespSchema != null ? new CodeName(writeRespSchema) : null; + this.readTopicPattern = readTopicPattern; + this.writeTopicPattern = writeTopicPattern; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/code/DotNetPropertyMaintainer.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/code/DotNetPropertyMaintainer.cs new file mode 100644 index 0000000000..d27d482650 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/code/DotNetPropertyMaintainer.cs @@ -0,0 +1,70 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetPropertyMaintainer : IEnvoyTemplateTransform + { + private readonly CodeName propertyName; + private readonly CodeName componentName; + private readonly CodeName readerName; + private readonly CodeName writerName; + private readonly string readCommandName; + private readonly string writeCommandName; + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly CodeName serviceName; + private readonly string readSerializerClassName; + private readonly EmptyTypeName readSerializerEmptyType; + private readonly string writeSerializerClassName; + private readonly EmptyTypeName writeSerializerEmptyType; + private readonly CodeName? readRespSchema; + private readonly CodeName? writeReqSchema; + private readonly CodeName? writeRespSchema; + private readonly string readTopicPattern; + private readonly string writeTopicPattern; + + public DotNetPropertyMaintainer( + string propertyName, + string componentName, + string readerName, + string writerName, + string readCommandName, + string writeCommandName, + string projectName, + CodeName genNamespace, + CodeName serviceName, + string readSerializerClassName, + EmptyTypeName readSerializerEmptyType, + string writeSerializerClassName, + EmptyTypeName writeSerializerEmptyType, + string? readRespSchema, + string? writeReqSchema, + string? writeRespSchema, + string readTopicPattern, + string writeTopicPattern) + { + this.propertyName = new CodeName(propertyName); + this.componentName = new CodeName(componentName); + this.readerName = new CodeName(readerName); + this.writerName = new CodeName(writerName); + this.readCommandName = readCommandName; + this.writeCommandName = writeCommandName; + this.projectName = projectName; + this.genNamespace = genNamespace; + this.serviceName = serviceName; + this.readSerializerClassName = readSerializerClassName; + this.readSerializerEmptyType = readSerializerEmptyType; + this.writeSerializerClassName = writeSerializerClassName; + this.writeSerializerEmptyType = writeSerializerEmptyType; + this.readRespSchema = readRespSchema != null ? new CodeName(readRespSchema) : null; + this.writeReqSchema = writeReqSchema != null ? new CodeName(writeReqSchema) : null; + this.writeRespSchema = writeRespSchema != null ? new CodeName(writeRespSchema) : null; + this.readTopicPattern = readTopicPattern; + this.writeTopicPattern = writeTopicPattern; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyConsumer.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyConsumer.cs new file mode 100644 index 0000000000..41f6f333e3 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyConsumer.cs @@ -0,0 +1,422 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetPropertyConsumer : DotNetPropertyConsumerBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write(@" +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Xml; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.RPC; + using Azure.Iot.Operations.Protocol.Models; + using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n public static partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n /// \r\n"); + if (!this.propertyName.IsEmpty) { + this.Write(" /// Specializes CommandInvoker classes for reading"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema != null ? " and writing" : "")); + this.Write(" Property \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("\'.\r\n"); + } else { + this.Write(" /// Specializes CommandInvoker classes for reading"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema != null ? " and writing" : "")); + this.Write(" a Property collection.\r\n"); + } + this.Write(" /// \r\n public static class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n"); + if (this.readRespSchema != null) { + this.Write(" /// \r\n"); + if (!this.propertyName.IsEmpty) { + this.Write(" /// Specializes a CommandInvoker class for requesting to read " + + "Property \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("\'.\r\n"); + } else { + this.Write(" /// Specializes a CommandInvoker class for requesting to read " + + "a Property collection.\r\n"); + } + this.Write(" /// \r\n [PropertyTopic(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readTopicPattern)); + this.Write("\")]\r\n public class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readerName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" : CommandInvoker"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ReadTypeParams())); + this.Write("\r\n {\r\n /// \r\n /// Initializes a" + + " new instance of the class.\r\n /// \r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readerName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(ApplicationContext applicationContext, IMqttPubSubClient mqttClient)\r\n " + + " : base(applicationContext, mqttClient, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readCommandName)); + this.Write("\", new "); + this.Write(this.ToStringHelper.ToStringWithCulture(string.Format(this.readSerializerClassName, this.ReadTypeParams()))); + this.Write("())\r\n {\r\n RequestTopicPattern = AttributeRetrie" + + "ver.GetAttribute(this)?.Topic ?? string.Empty;\r\n\r\n " + + " TopicTokenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyAction)); + this.Write("\"] = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyActionValues.Read)); + this.Write("\";\r\n if (mqttClient.ClientId != null)\r\n {\r\n" + + " TopicTokenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyConsumerId)); + this.Write("\"] = mqttClient.ClientId;\r\n }\r\n }\r\n " + + "}\r\n"); + } + if (this.readRespSchema != null && this.writeReqSchema != null) { + this.Write("\r\n"); + } + if (this.writeReqSchema != null) { + this.Write(" /// \r\n"); + if (!this.propertyName.IsEmpty) { + this.Write(" /// Specializes a CommandInvoker class for requesting to write" + + " Property \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("\'.\r\n"); + } else { + this.Write(" /// Specializes a CommandInvoker class for requesting to write" + + " a Property collection.\r\n"); + } + this.Write(" /// \r\n [PropertyTopic(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeTopicPattern)); + this.Write("\")]\r\n public class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writerName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" : CommandInvoker"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.WriteTypeParams())); + this.Write("\r\n {\r\n /// \r\n /// Initializes a" + + " new instance of the class.\r\n /// \r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writerName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(ApplicationContext applicationContext, IMqttPubSubClient mqttClient)\r\n " + + " : base(applicationContext, mqttClient, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeCommandName)); + this.Write("\", new "); + this.Write(this.ToStringHelper.ToStringWithCulture(string.Format(this.writeSerializerClassName, this.WriteTypeParams()))); + this.Write("())\r\n {\r\n RequestTopicPattern = AttributeRetrie" + + "ver.GetAttribute(this)?.Topic ?? string.Empty;\r\n\r\n " + + " TopicTokenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyAction)); + this.Write("\"] = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyActionValues.Write)); + this.Write("\";\r\n if (mqttClient.ClientId != null)\r\n {\r\n" + + " TopicTokenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyConsumerId)); + this.Write("\"] = mqttClient.ClientId;\r\n }\r\n }\r\n " + + "}\r\n"); + } + this.Write(" }\r\n }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + + private string ReadTypeParams() => $"<{readSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)}, {this.readRespSchema.GetTypeName(TargetLanguage.CSharp)}>"; + + private string WriteTypeParams() => $"<{this.writeReqSchema!.GetTypeName(TargetLanguage.CSharp)}, {this.writeRespSchema?.GetTypeName(TargetLanguage.CSharp) ?? writeSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"; + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetPropertyConsumerBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyConsumer.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyConsumer.tt new file mode 100644 index 0000000000..3b5c05140b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyConsumer.tt @@ -0,0 +1,93 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Xml; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.RPC; + using Azure.Iot.Operations.Protocol.Models; + using <#=this.projectName#>; + + public static partial class <#=this.serviceName.GetTypeName(TargetLanguage.CSharp)#> + { + /// +<# if (!this.propertyName.IsEmpty) { #> + /// Specializes CommandInvoker classes for reading<#=this.writeReqSchema != null ? " and writing" : ""#> Property '<#=this.propertyName.AsGiven#>'. +<# } else { #> + /// Specializes CommandInvoker classes for reading<#=this.writeReqSchema != null ? " and writing" : ""#> a Property collection. +<# } #> + /// + public static class <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#> + { +<# if (this.readRespSchema != null) { #> + /// +<# if (!this.propertyName.IsEmpty) { #> + /// Specializes a CommandInvoker class for requesting to read Property '<#=this.propertyName.AsGiven#>'. +<# } else { #> + /// Specializes a CommandInvoker class for requesting to read a Property collection. +<# } #> + /// + [PropertyTopic("<#=this.readTopicPattern#>")] + public class <#=this.readerName.GetTypeName(TargetLanguage.CSharp)#> : CommandInvoker<#=this.ReadTypeParams()#> + { + /// + /// Initializes a new instance of the class. + /// + public <#=this.readerName.GetTypeName(TargetLanguage.CSharp)#>(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) + : base(applicationContext, mqttClient, "<#=this.readCommandName#>", new <#=string.Format(this.readSerializerClassName, this.ReadTypeParams())#>()) + { + RequestTopicPattern = AttributeRetriever.GetAttribute(this)?.Topic ?? string.Empty; + + TopicTokenMap["<#=MqttTopicTokens.PropertyAction#>"] = "<#=MqttTopicTokens.PropertyActionValues.Read#>"; + if (mqttClient.ClientId != null) + { + TopicTokenMap["<#=MqttTopicTokens.PropertyConsumerId#>"] = mqttClient.ClientId; + } + } + } +<# } #> +<# if (this.readRespSchema != null && this.writeReqSchema != null) { #> + +<# } #> +<# if (this.writeReqSchema != null) { #> + /// +<# if (!this.propertyName.IsEmpty) { #> + /// Specializes a CommandInvoker class for requesting to write Property '<#=this.propertyName.AsGiven#>'. +<# } else { #> + /// Specializes a CommandInvoker class for requesting to write a Property collection. +<# } #> + /// + [PropertyTopic("<#=this.writeTopicPattern#>")] + public class <#=this.writerName.GetTypeName(TargetLanguage.CSharp)#> : CommandInvoker<#=this.WriteTypeParams()#> + { + /// + /// Initializes a new instance of the class. + /// + public <#=this.writerName.GetTypeName(TargetLanguage.CSharp)#>(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) + : base(applicationContext, mqttClient, "<#=this.writeCommandName#>", new <#=string.Format(this.writeSerializerClassName, this.WriteTypeParams())#>()) + { + RequestTopicPattern = AttributeRetriever.GetAttribute(this)?.Topic ?? string.Empty; + + TopicTokenMap["<#=MqttTopicTokens.PropertyAction#>"] = "<#=MqttTopicTokens.PropertyActionValues.Write#>"; + if (mqttClient.ClientId != null) + { + TopicTokenMap["<#=MqttTopicTokens.PropertyConsumerId#>"] = mqttClient.ClientId; + } + } + } +<# } #> + } + } +} +<#+ + private string ReadTypeParams() => $"<{readSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)}, {this.readRespSchema.GetTypeName(TargetLanguage.CSharp)}>"; + + private string WriteTypeParams() => $"<{this.writeReqSchema!.GetTypeName(TargetLanguage.CSharp)}, {this.writeRespSchema?.GetTypeName(TargetLanguage.CSharp) ?? writeSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"; +#> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyMaintainer.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyMaintainer.cs new file mode 100644 index 0000000000..83cf88de98 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyMaintainer.cs @@ -0,0 +1,425 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetPropertyMaintainer : DotNetPropertyMaintainerBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write(@" +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Xml; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.RPC; + using Azure.Iot.Operations.Protocol.Models; + using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n public static partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n /// \r\n"); + if (!this.propertyName.IsEmpty) { + this.Write(" /// Specializes CommandExecutor classes for reading"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema != null ? " and writing" : "")); + this.Write(" Property \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("\'.\r\n"); + } else { + this.Write(" /// Specializes CommandExecutor classes for reading"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema != null ? " and writing" : "")); + this.Write(" a Property collection.\r\n"); + } + this.Write(" /// \r\n public static class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n"); + if (this.readRespSchema != null) { + this.Write(" /// \r\n"); + if (!this.propertyName.IsEmpty) { + this.Write(" /// Specializes a CommandExecutor class for responding to read" + + " requests for Property \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("\'.\r\n"); + } else { + this.Write(" /// Specializes a CommandExecutor class for responding to read" + + " requests for Property collection.\r\n"); + } + this.Write(" /// \r\n [CommandBehavior(idempotent: true)]\r\n " + + " [PropertyTopic(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readTopicPattern)); + this.Write("\")]\r\n public class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readerName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" : CommandExecutor"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ReadTypeParams())); + this.Write("\r\n {\r\n /// \r\n /// Initializes a" + + " new instance of the class.\r\n /// \r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readerName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(ApplicationContext applicationContext, IMqttPubSubClient mqttClient)\r\n " + + " : base(applicationContext, mqttClient, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readCommandName)); + this.Write("\", new "); + this.Write(this.ToStringHelper.ToStringWithCulture(string.Format(this.readSerializerClassName, this.ReadTypeParams()))); + this.Write("())\r\n {\r\n ServiceGroupId = string.Empty;\r\n " + + " RequestTopicPattern = AttributeRetriever.GetAttribute(this)?.Topic ?? string.Empty;\r\n\r\n TopicTokenMap[" + + "\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyAction)); + this.Write("\"] = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyActionValues.Read)); + this.Write("\";\r\n if (mqttClient.ClientId != null)\r\n {\r\n" + + " TopicTokenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyMaintainerId)); + this.Write("\"] = mqttClient.ClientId;\r\n }\r\n }\r\n " + + "}\r\n"); + } + if (this.readRespSchema != null && this.writeReqSchema != null) { + this.Write("\r\n"); + } + if (this.writeReqSchema != null) { + this.Write(" /// \r\n"); + if (!this.propertyName.IsEmpty) { + this.Write(" /// Specializes a CommandExecutor class for responding to writ" + + "e requests for Property \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("\'.\r\n"); + } else { + this.Write(" /// Specializes a CommandExecutor class for responding to writ" + + "e requests for Property collection.\r\n"); + } + this.Write(" /// \r\n [CommandBehavior(idempotent: false)]\r\n " + + " [PropertyTopic(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeTopicPattern)); + this.Write("\")]\r\n public class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writerName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" : CommandExecutor"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.WriteTypeParams())); + this.Write("\r\n {\r\n /// \r\n /// Initializes a" + + " new instance of the class.\r\n /// \r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writerName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(ApplicationContext applicationContext, IMqttPubSubClient mqttClient)\r\n " + + " : base(applicationContext, mqttClient, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeCommandName)); + this.Write("\", new "); + this.Write(this.ToStringHelper.ToStringWithCulture(string.Format(this.writeSerializerClassName, this.WriteTypeParams()))); + this.Write("())\r\n {\r\n RequestTopicPattern = AttributeRetrie" + + "ver.GetAttribute(this)?.Topic ?? string.Empty;\r\n\r\n " + + " TopicTokenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyAction)); + this.Write("\"] = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyActionValues.Write)); + this.Write("\";\r\n if (mqttClient.ClientId != null)\r\n {\r\n" + + " TopicTokenMap[\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyMaintainerId)); + this.Write("\"] = mqttClient.ClientId;\r\n }\r\n }\r\n " + + "}\r\n"); + } + this.Write(" }\r\n }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + + private string ReadTypeParams() => $"<{readSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)}, {this.readRespSchema.GetTypeName(TargetLanguage.CSharp)}>"; + + private string WriteTypeParams() => $"<{this.writeReqSchema!.GetTypeName(TargetLanguage.CSharp)}, {this.writeRespSchema?.GetTypeName(TargetLanguage.CSharp) ?? writeSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"; + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetPropertyMaintainerBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyMaintainer.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyMaintainer.tt new file mode 100644 index 0000000000..28fa384663 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Properties/t4/DotNetPropertyMaintainer.tt @@ -0,0 +1,96 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Xml; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.RPC; + using Azure.Iot.Operations.Protocol.Models; + using <#=this.projectName#>; + + public static partial class <#=this.serviceName.GetTypeName(TargetLanguage.CSharp)#> + { + /// +<# if (!this.propertyName.IsEmpty) { #> + /// Specializes CommandExecutor classes for reading<#=this.writeReqSchema != null ? " and writing" : ""#> Property '<#=this.propertyName.AsGiven#>'. +<# } else { #> + /// Specializes CommandExecutor classes for reading<#=this.writeReqSchema != null ? " and writing" : ""#> a Property collection. +<# } #> + /// + public static class <#=this.componentName.GetTypeName(TargetLanguage.CSharp)#> + { +<# if (this.readRespSchema != null) { #> + /// +<# if (!this.propertyName.IsEmpty) { #> + /// Specializes a CommandExecutor class for responding to read requests for Property '<#=this.propertyName.AsGiven#>'. +<# } else { #> + /// Specializes a CommandExecutor class for responding to read requests for Property collection. +<# } #> + /// + [CommandBehavior(idempotent: true)] + [PropertyTopic("<#=this.readTopicPattern#>")] + public class <#=this.readerName.GetTypeName(TargetLanguage.CSharp)#> : CommandExecutor<#=this.ReadTypeParams()#> + { + /// + /// Initializes a new instance of the class. + /// + public <#=this.readerName.GetTypeName(TargetLanguage.CSharp)#>(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) + : base(applicationContext, mqttClient, "<#=this.readCommandName#>", new <#=string.Format(this.readSerializerClassName, this.ReadTypeParams())#>()) + { + ServiceGroupId = string.Empty; + RequestTopicPattern = AttributeRetriever.GetAttribute(this)?.Topic ?? string.Empty; + + TopicTokenMap["<#=MqttTopicTokens.PropertyAction#>"] = "<#=MqttTopicTokens.PropertyActionValues.Read#>"; + if (mqttClient.ClientId != null) + { + TopicTokenMap["<#=MqttTopicTokens.PropertyMaintainerId#>"] = mqttClient.ClientId; + } + } + } +<# } #> +<# if (this.readRespSchema != null && this.writeReqSchema != null) { #> + +<# } #> +<# if (this.writeReqSchema != null) { #> + /// +<# if (!this.propertyName.IsEmpty) { #> + /// Specializes a CommandExecutor class for responding to write requests for Property '<#=this.propertyName.AsGiven#>'. +<# } else { #> + /// Specializes a CommandExecutor class for responding to write requests for Property collection. +<# } #> + /// + [CommandBehavior(idempotent: false)] + [PropertyTopic("<#=this.writeTopicPattern#>")] + public class <#=this.writerName.GetTypeName(TargetLanguage.CSharp)#> : CommandExecutor<#=this.WriteTypeParams()#> + { + /// + /// Initializes a new instance of the class. + /// + public <#=this.writerName.GetTypeName(TargetLanguage.CSharp)#>(ApplicationContext applicationContext, IMqttPubSubClient mqttClient) + : base(applicationContext, mqttClient, "<#=this.writeCommandName#>", new <#=string.Format(this.writeSerializerClassName, this.WriteTypeParams())#>()) + { + RequestTopicPattern = AttributeRetriever.GetAttribute(this)?.Topic ?? string.Empty; + + TopicTokenMap["<#=MqttTopicTokens.PropertyAction#>"] = "<#=MqttTopicTokens.PropertyActionValues.Write#>"; + if (mqttClient.ClientId != null) + { + TopicTokenMap["<#=MqttTopicTokens.PropertyMaintainerId#>"] = mqttClient.ClientId; + } + } + } +<# } #> + } + } +} +<#+ + private string ReadTypeParams() => $"<{readSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)}, {this.readRespSchema.GetTypeName(TargetLanguage.CSharp)}>"; + + private string WriteTypeParams() => $"<{this.writeReqSchema!.GetTypeName(TargetLanguage.CSharp)}, {this.writeRespSchema?.GetTypeName(TargetLanguage.CSharp) ?? writeSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)}>"; +#> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Service/code/DotNetService.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Service/code/DotNetService.cs new file mode 100644 index 0000000000..9feaa3b328 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Service/code/DotNetService.cs @@ -0,0 +1,56 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetService : IEnvoyTemplateTransform + { + private readonly CodeName readRequesterName; + private readonly CodeName writeRequesterName; + private readonly CodeName readResponderName; + private readonly CodeName writeResponderName; + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly CodeName serviceName; + private readonly List actionSpecs; + private readonly List propSpecs; + private readonly List eventSpec; + private readonly bool generateClient; + private readonly bool generateServer; + private readonly bool defaultImpl; + + public DotNetService( + string readRequesterName, + string writeRequesterName, + string readResponderName, + string writeResponderName, + string projectName, + CodeName genNamespace, + CodeName serviceName, + List actionSpecs, + List propSpecs, + List eventSpec, + bool generateClient, + bool generateServer, + bool defaultImpl) + { + this.readRequesterName = new CodeName(readRequesterName); + this.writeRequesterName = new CodeName(writeRequesterName); + this.readResponderName = new CodeName(readResponderName); + this.writeResponderName = new CodeName(writeResponderName); + this.projectName = projectName; + this.genNamespace = genNamespace; + this.serviceName = serviceName; + this.actionSpecs = actionSpecs; + this.propSpecs = propSpecs; + this.eventSpec = eventSpec; + this.generateClient = generateClient; + this.generateServer = generateServer; + this.defaultImpl = defaultImpl; + } + + public string FileName { get => $"{this.serviceName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Service/t4/DotNetService.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Service/t4/DotNetService.cs new file mode 100644 index 0000000000..05c6df9cb5 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Service/t4/DotNetService.cs @@ -0,0 +1,1440 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetService : DotNetServiceBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n using System;\r\n using System.Collections.Generic;\r\n using System.L" + + "inq;\r\n"); + if (this.actionSpecs.Any() || this.propSpecs.Any() || this.eventSpec.Any()) { + this.Write(" using System.Threading;\r\n"); + } + this.Write(" using System.Threading.Tasks;\r\n using Azure.Iot.Operations.Protocol.Models" + + ";\r\n using Azure.Iot.Operations.Protocol;\r\n using Azure.Iot.Operations.Prot" + + "ocol.RPC;\r\n using Azure.Iot.Operations.Protocol.Telemetry;\r\n using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n [System.CodeDom.Compiler.GeneratedCode(\"Azure.Iot.Operations.ProtocolCom" + + "pilerLib\", \""); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("\")]\r\n public static partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceName.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n"); + if (this.generateServer) { + this.Write(" public abstract partial class Service : IAsyncDisposable\r\n {\r\n " + + " private ApplicationContext applicationContext;\r\n private IMqt" + + "tPubSubClient mqttClient;\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" private readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetVariableName(TargetLanguage.CSharp))); + this.Write(";\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" private readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readResponderName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder"))); + this.Write(";\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" private readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeResponderName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder"))); + this.Write(";\r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" private readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp))); + this.Write(";\r\n"); + } + this.Write(@" + /// + /// Construct a new instance of this service. + /// + /// The shared context for your application. + /// The MQTT client to use. + /// + /// The topic token replacement map to use for all operations by default. Generally, this will include the token values + /// for topic tokens that should be the same for the duration of this service's lifetime. Note that + /// additional topic tokens can be specified per event message. + /// + public Service(ApplicationContext applicationContext, IMqttPubSubClient mqttClient, Dictionary? topicTokenMap = null) + { + this.applicationContext = applicationContext; + this.mqttClient = mqttClient; + + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException(""No MQTT client Id configured. Must connect to MQTT broker before invoking command.""); + } + +"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetVariableName(TargetLanguage.CSharp))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetTypeName(TargetLanguage.CSharp))); + this.Write("(applicationContext, mqttClient) { OnCommandReceived = "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "int"))); + this.Write(" };\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder"))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readResponderName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(applicationContext, mqttClient) { OnCommandReceived = "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "read"))); + this.Write(" };\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder"))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeResponderName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(applicationContext, mqttClient) { OnCommandReceived = "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "write"))); + this.Write(" };\r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetTypeName(TargetLanguage.CSharp))); + this.Write("(applicationContext, mqttClient);\r\n"); + } + this.Write("\r\n if (topicTokenMap != null)\r\n {\r\n " + + " foreach (string topicTokenKey in topicTokenMap.Keys)\r\n {\r" + + "\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetVariableName(TargetLanguage.CSharp))); + this.Write(".TopicTokenMap.TryAdd(\"ex:\" + topicTokenKey, topicTokenMap[topicTokenKey]);\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder"))); + this.Write(".TopicTokenMap.TryAdd(\"ex:\" + topicTokenKey, topicTokenMap[topicTokenKey]);\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder"))); + this.Write(".TopicTokenMap.TryAdd(\"ex:\" + topicTokenKey, topicTokenMap[topicTokenKey]);\r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp))); + this.Write(".TopicTokenMap.TryAdd(\"ex:\" + topicTokenKey, topicTokenMap[topicTokenKey]);\r\n"); + } + this.Write(" }\r\n }\r\n\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetVariableName(TargetLanguage.CSharp))); + this.Write(".TopicTokenMap.TryAdd(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.ActionExecutorId)); + this.Write("\", clientId);\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder"))); + this.Write(".TopicTokenMap.TryAdd(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyMaintainerId)); + this.Write("\", clientId);\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder"))); + this.Write(".TopicTokenMap.TryAdd(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyMaintainerId)); + this.Write("\", clientId);\r\n"); + } + } + this.Write(" }\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetTypeName(TargetLanguage.CSharp))); + this.Write(" { get => this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetVariableName(TargetLanguage.CSharp))); + this.Write("; }\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readResponderName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetTypeName(TargetLanguage.CSharp, "read", "responder"))); + this.Write(" { get => this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder"))); + this.Write("; }\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeResponderName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetTypeName(TargetLanguage.CSharp, "write", "responder"))); + this.Write(" { get => this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder"))); + this.Write("; }\r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetTypeName(TargetLanguage.CSharp))); + this.Write(" { get => this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp))); + this.Write("; }\r\n"); + } + foreach (var actionSpec in this.actionSpecs) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.defaultImpl ? "virtual" : "abstract")); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ExtRespType(actionSpec))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "async"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ReqParam(actionSpec))); + this.Write("CommandRequestMetadata requestMetadata, CancellationToken cancellationToken)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.defaultImpl ? "" : ";")); + this.Write("\r\n"); + if (this.defaultImpl) { + this.Write(" {\r\n return "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.EmptyResp(actionSpec))); + this.Write(";\r\n }\r\n"); + } + } + foreach (var propSpec in this.propSpecs) { + this.Write("\r\n public abstract Task> "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "read"))); + this.Write("(CommandRequestMetadata requestMetadata, CancellationToken cancellationToken);\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write("\r\n public abstract Task "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "write"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteReqSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp))); + this.Write(", CommandRequestMetadata requestMetadata, CancellationToken cancellationToken);\r\n" + + ""); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(@" + /// + /// Send telemetry. + /// + /// The payload of the telemetry. + /// The metadata of the telemetry. + /// + /// The topic token replacement map to use in addition to the topic token map provided in the constructor. If this map + /// contains any keys that topic token map provided in the constructor also has, then values specified in this map will take precedence. + /// + /// The quality of service to send the telemetry with. + /// How long the telemetry message will be available on the broker for a receiver to receive. + /// Cancellation token. + public async Task "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.TelemMethodName(telemEnvoyInfo, "send", "async"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Schema.GetTypeName(TargetLanguage.CSharp))); + this.Write(@" telemetry, OutgoingTelemetryMetadata metadata, Dictionary? additionalTopicTokenMap = null, MqttQualityOfServiceLevel qos = MqttQualityOfServiceLevel.AtLeastOnce, TimeSpan? telemetryTimeout = null, CancellationToken cancellationToken = default) + { + additionalTopicTokenMap ??= new(); + + Dictionary prefixedAdditionalTopicTokenMap = new(); + foreach (string key in additionalTopicTokenMap.Keys) + { + prefixedAdditionalTopicTokenMap[""ex:"" + key] = additionalTopicTokenMap[key]; + } + await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp))); + this.Write(".SendTelemetryAsync(telemetry, metadata, prefixedAdditionalTopicTokenMap, qos, te" + + "lemetryTimeout, cancellationToken);\r\n }\r\n"); + } + if (this.actionSpecs.Any() || this.propSpecs.Any()) { + this.Write(@" + /// + /// Begin accepting command invocations for all command executors. + /// + /// The dispatch concurrency count for the command response cache to use. + /// Cancellation token. + public async Task StartAsync(int? preferredDispatchConcurrency = null, CancellationToken cancellationToken = default) + { + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException(""No MQTT client Id configured. Must connect to MQTT broker before starting service.""); + } + + await Task.WhenAll( +"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetVariableName(TargetLanguage.CSharp))); + this.Write(".StartAsync(preferredDispatchConcurrency, cancellationToken)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IsLast(actionSpec) ? ").ConfigureAwait(false);" : ",")); + this.Write("\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder"))); + this.Write(".StartAsync(preferredDispatchConcurrency, cancellationToken)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IsLast(propSpec) && propSpec.WriteReqSchema == null ? ").ConfigureAwait(false);" : ",")); + this.Write("\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder"))); + this.Write(".StartAsync(preferredDispatchConcurrency, cancellationToken)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IsLast(propSpec) ? ").ConfigureAwait(false);" : ",")); + this.Write("\r\n"); + } + } + this.Write(" }\r\n\r\n public async Task StopAsync(CancellationToken cancel" + + "lationToken = default)\r\n {\r\n await Task.WhenAll(\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetVariableName(TargetLanguage.CSharp))); + this.Write(".StopAsync(cancellationToken)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IsLast(actionSpec) ? ").ConfigureAwait(false);" : ",")); + this.Write("\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder"))); + this.Write(".StopAsync(cancellationToken)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IsLast(propSpec) && propSpec.WriteReqSchema == null ? ").ConfigureAwait(false);" : ",")); + this.Write("\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder"))); + this.Write(".StopAsync(cancellationToken)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IsLast(propSpec) ? ").ConfigureAwait(false);" : ",")); + this.Write("\r\n"); + } + } + this.Write(" }\r\n"); + } + foreach (var actionSpec in this.actionSpecs) { + this.Write("\r\n private async Task> "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "int"))); + this.Write("(ExtendedRequest<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.RequestSchema, actionSpec.SerializerEmptyType))); + this.Write("> req, CancellationToken cancellationToken)\r\n {\r\n"); + if (actionSpec.ErrorResultName != null) { + this.Write(" try\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IntLValue(actionSpec))); + this.Write("await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "async"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ReqArgs(actionSpec, "req"))); + this.Write(", cancellationToken);\r\n\r\n return new ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType))); + this.Write(">\r\n {\r\n\r\n Response = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType))); + this.Write("\r\n {\r\n"); + foreach (CodeName normalResultName in actionSpec.NormalResultNames) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultName.GetFieldName(TargetLanguage.CSharp))); + this.Write(",\r\n"); + } + this.Write(" },\r\n ResponseMetadata = extended.R" + + "esponseMetadata,\r\n };\r\n }\r\n cat" + + "ch ("); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultSchema.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write(" intEx)\r\n {\r\n ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType))); + this.Write("> extendedResponse = ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType))); + this.Write(">.CreateFromResponse(new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType))); + this.Write(" { "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = intEx."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultSchema.GetFieldName(TargetLanguage.CSharp))); + this.Write(" });\r\n"); + if (actionSpec.ErrorCodeName != null) { + this.Write("\r\n if (intEx.TryGetApplicationError(out "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorCodeSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorCodeName.GetVariableName(TargetLanguage.CSharp))); + this.Write(", out "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.GetInfoSchema(actionSpec.ErrorInfoSchema))); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.GetInfoName(actionSpec.ErrorInfoName))); + this.Write("))\r\n {\r\n extendedResponse = extendedRes" + + "ponse.WithApplicationError(("); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorCodeSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write(")"); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorCodeName.GetVariableName(TargetLanguage.CSharp))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.GetInfoName(actionSpec.ErrorInfoName))); + this.Write(");\r\n }\r\n\r\n"); + } + this.Write(" return extendedResponse;\r\n }\r\n"); + } else { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IntLValue(actionSpec))); + this.Write("await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "async"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ReqArgs(actionSpec, "req"))); + this.Write(", cancellationToken);\r\n return new ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType))); + this.Write("> { "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IntRValue(actionSpec))); + this.Write("};\r\n"); + } + this.Write(" }\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write("\r\n private async Task> "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "read"))); + this.Write("(ExtendedRequest<"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadSerializerEmptyType.GetTypeName(TargetLanguage.CSharp))); + this.Write("> req, CancellationToken cancellationToken)\r\n {\r\n"); + if (propSpec.ReadErrorName != null) { + this.Write(" try\r\n {\r\n ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.PropSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write("> extended = await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "read"))); + this.Write("(req.RequestMetadata!, cancellationToken);\r\n\r\n return new Exte" + + "ndedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadRespSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write(">\r\n {\r\n Response = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadRespSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write(" { "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = extended.Response"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.IsAggregate ? "" : $".{propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp)}")); + this.Write(" },\r\n ResponseMetadata = extended.ResponseMetadata,\r\n " + + " };\r\n }\r\n catch ("); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write(" intEx)\r\n {\r\n ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.ReadRespSchema, propSpec.ReadSerializerEmptyType))); + this.Write("> extendedResponse = ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.ReadRespSchema, propSpec.ReadSerializerEmptyType))); + this.Write(">.CreateFromResponse(new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.ReadRespSchema, propSpec.ReadSerializerEmptyType))); + this.Write(" { "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadErrorName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = intEx."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadErrorSchema.GetFieldName(TargetLanguage.CSharp))); + this.Write(" });\r\n return extendedResponse;\r\n }\r\n"); + } else { + this.Write(" return await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "read"))); + this.Write("(req.RequestMetadata!, cancellationToken);\r\n"); + } + this.Write(" }\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write("\r\n private async Task> "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "write"))); + this.Write("(ExtendedRequest<"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteReqSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write("> req, CancellationToken cancellationToken)\r\n {\r\n"); + if (propSpec.WriteRespSchema != null) { + this.Write(" try\r\n {\r\n CommandResponseMetada" + + "ta? respMetadata = await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "write"))); + this.Write("(req.Request!, req.RequestMetadata!, cancellationToken);\r\n\r\n r" + + "eturn new ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteRespSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write(">\r\n {\r\n Response = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteRespSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = null,\r\n },\r\n ResponseMetadata = " + + "respMetadata,\r\n };\r\n }\r\n catch " + + "("); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write(" intEx)\r\n {\r\n ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.WriteRespSchema, propSpec.WriteSerializerEmptyType))); + this.Write("> extendedResponse = ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.WriteRespSchema, propSpec.WriteSerializerEmptyType))); + this.Write(">.CreateFromResponse(new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.WriteRespSchema, propSpec.WriteSerializerEmptyType))); + this.Write(" { "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = intEx."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorSchema.GetFieldName(TargetLanguage.CSharp))); + this.Write(" });\r\n return extendedResponse;\r\n }\r\n"); + } else { + this.Write(" CommandResponseMetadata? respMetadata = await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "write"))); + this.Write("(req.Request!, req.RequestMetadata!, cancellationToken);\r\n return " + + "new ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteSerializerEmptyType.GetTypeName(TargetLanguage.CSharp))); + this.Write(">\r\n {\r\n Response = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteSerializerEmptyType.GetTypeName(TargetLanguage.CSharp))); + this.Write("(),\r\n ResponseMetadata = respMetadata,\r\n };\r\n"); + } + this.Write(" }\r\n"); + } + } + this.Write("\r\n public async ValueTask DisposeAsync()\r\n {\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetVariableName(TargetLanguage.CSharp))); + this.Write(".DisposeAsync().ConfigureAwait(false);\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder"))); + this.Write(".DisposeAsync().ConfigureAwait(false);\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder"))); + this.Write(".DisposeAsync().ConfigureAwait(false); \r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp))); + this.Write(".DisposeAsync().ConfigureAwait(false);\r\n"); + } + this.Write(" }\r\n\r\n public async ValueTask DisposeAsync(bool disposing)\r" + + "\n {\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Executor.GetVariableName(TargetLanguage.CSharp))); + this.Write(".DisposeAsync(disposing).ConfigureAwait(false);\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder"))); + this.Write(".DisposeAsync(disposing).ConfigureAwait(false);\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder"))); + this.Write(".DisposeAsync(disposing).ConfigureAwait(false); \r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp))); + this.Write(".DisposeAsync(disposing).ConfigureAwait(false);\r\n"); + } + this.Write(" }\r\n }\r\n"); + } + if (this.generateServer && this.generateClient) { + this.Write("\r\n"); + } + if (this.generateClient) { + this.Write(" public abstract partial class Client"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.actionSpecs.Any() || this.propSpecs.Any() ? " : IAsyncDisposable" : "")); + this.Write("\r\n {\r\n private ApplicationContext applicationContext;\r\n " + + " private IMqttPubSubClient mqttClient;\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" private readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp))); + this.Write(";\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" private readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Consumer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRequesterName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester"))); + this.Write(";\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" private readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Consumer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeRequesterName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester"))); + this.Write(";\r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" private readonly "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp))); + this.Write(";\r\n"); + } + this.Write(@" + /// + /// Construct a new instance of this client. + /// + /// The shared context for your application. + /// The MQTT client to use. + /// + /// The topic token replacement map to use for all operations by default. Generally, this will include the token values + /// for topic tokens that should be the same for the duration of this client's lifetime. + /// + public Client(ApplicationContext applicationContext, IMqttPubSubClient mqttClient, Dictionary? topicTokenMap = null) + { + this.applicationContext = applicationContext; + this.mqttClient = mqttClient; + +"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetTypeName(TargetLanguage.CSharp))); + this.Write("(applicationContext, mqttClient);\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester"))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Consumer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRequesterName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(applicationContext, mqttClient);\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester"))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Consumer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeRequesterName.GetTypeName(TargetLanguage.CSharp))); + this.Write("(applicationContext, mqttClient);\r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetTypeName(TargetLanguage.CSharp))); + this.Write("(applicationContext, mqttClient) { OnTelemetryReceived = this."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.TelemMethodName(telemEnvoyInfo, "receive"))); + this.Write(" };\r\n"); + } + this.Write("\r\n if (topicTokenMap != null)\r\n {\r\n " + + " foreach (string topicTokenKey in topicTokenMap.Keys)\r\n {\r" + + "\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp))); + this.Write(".TopicTokenMap.TryAdd(\"ex:\" + topicTokenKey, topicTokenMap[topicTokenKey]);\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester"))); + this.Write(".TopicTokenMap.TryAdd(\"ex:\" + topicTokenKey, topicTokenMap[topicTokenKey]);\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester"))); + this.Write(".TopicTokenMap.TryAdd(\"ex:\" + topicTokenKey, topicTokenMap[topicTokenKey]);\r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp))); + this.Write(".TopicTokenMap.TryAdd(\"ex:\" + topicTokenKey, topicTokenMap[topicTokenKey]);\r\n"); + } + this.Write(" }\r\n }\r\n }\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetTypeName(TargetLanguage.CSharp))); + this.Write(" { get => this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp))); + this.Write("; }\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Consumer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRequesterName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetTypeName(TargetLanguage.CSharp, "read", "requester"))); + this.Write(" { get => this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester"))); + this.Write("; }\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Consumer.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeRequesterName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetTypeName(TargetLanguage.CSharp, "write", "requester"))); + this.Write(" { get => this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester"))); + this.Write("; }\r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetTypeName(TargetLanguage.CSharp))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetTypeName(TargetLanguage.CSharp))); + this.Write(" { get => this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp))); + this.Write("; }\r\n"); + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.defaultImpl ? "virtual" : "abstract")); + this.Write(" Task "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.TelemMethodName(telemEnvoyInfo, "receive"))); + this.Write("(string senderId, "); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Schema.GetTypeName(TargetLanguage.CSharp))); + this.Write(" telemetry, IncomingTelemetryMetadata metadata)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.defaultImpl ? "" : ";")); + this.Write("\r\n"); + if (this.defaultImpl) { + this.Write(" {\r\n return Task.CompletedTask;\r\n }\r\n"); + } + } + foreach (var actionSpec in this.actionSpecs) { + this.Write("\r\n /// \r\n /// Invoke a command.\r\n /// <" + + "/summary>\r\n"); + if (actionSpec.DoesTargetExecutor) { + this.Write(" /// The identifier of the executor targeted " + + "by this command request.\r\n"); + } + if (actionSpec.RequestSchema != null) { + this.Write(" /// The data for this command request.\r" + + "\n"); + } + this.Write(@" /// The metadata for this command request. + /// + /// The topic token replacement map to use in addition to the topic tokens specified in the constructor. If this map + /// contains any keys that the topic tokens specified in the constructor also has, then values specified in this map will take precedence. + /// + /// How long the command will be available on the broker for an executor to receive. + /// Cancellation token. + /// The command response. + public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.CallAsyncType(actionSpec))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "async"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ExecParam(actionSpec))); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ReqParam(actionSpec))); + this.Write(@"CommandRequestMetadata? requestMetadata = null, Dictionary? additionalTopicTokenMap = null, TimeSpan? commandTimeout = default, CancellationToken cancellationToken = default) + { + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException(""No MQTT client Id configured. Must connect to MQTT broker before invoking command.""); + } + + CommandRequestMetadata metadata = requestMetadata ?? new CommandRequestMetadata(); + additionalTopicTokenMap ??= new(); + + Dictionary prefixedAdditionalTopicTokenMap = new(); + foreach (string key in additionalTopicTokenMap.Keys) + { + prefixedAdditionalTopicTokenMap[""ex:"" + key] = additionalTopicTokenMap[key]; + } + + prefixedAdditionalTopicTokenMap[""invokerClientId""] = clientId; +"); + if (actionSpec.DoesTargetExecutor) { + this.Write(" prefixedAdditionalTopicTokenMap[\"executorId\"] = executorId;\r\n"); + } + this.Write("\r\n return new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.CallAsyncType(actionSpec))); + this.Write("(this."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IntMethod(actionSpec))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.RequestSchema != null ? $"request" : actionSpec.SerializerEmptyType.GetAllocator(TargetLanguage.CSharp))); + this.Write(", metadata, prefixedAdditionalTopicTokenMap, commandTimeout, cancellationToken), " + + "metadata.CorrelationId);\r\n }\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write("\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.CallAsyncType(propSpec))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "read"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ReadMaintParam(propSpec))); + this.Write(@"CommandRequestMetadata? requestMetadata = null, Dictionary? additionalTopicTokenMap = null, TimeSpan? commandTimeout = default, CancellationToken cancellationToken = default) + { + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException(""No MQTT client Id configured. Must connect to MQTT broker before requesting to read property.""); + } + + CommandRequestMetadata metadata = requestMetadata ?? new CommandRequestMetadata(); + additionalTopicTokenMap ??= new(); + + Dictionary prefixedAdditionalTopicTokenMap = new(); + foreach (string key in additionalTopicTokenMap.Keys) + { + prefixedAdditionalTopicTokenMap[""ex:"" + key] = additionalTopicTokenMap[key]; + } + + prefixedAdditionalTopicTokenMap[""consumerClientId""] = clientId; +"); + if (propSpec.DoesReadTargetMaintainer) { + this.Write(" prefixedAdditionalTopicTokenMap[\"maintainerId\"] = maintainerId;\r\n" + + ""); + } + this.Write("\r\n return new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.CallAsyncType(propSpec))); + this.Write("(this."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IntReadMethod(propSpec))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadSerializerEmptyType.GetAllocator(TargetLanguage.CSharp))); + this.Write(", metadata, prefixedAdditionalTopicTokenMap, commandTimeout, cancellationToken), " + + "metadata.CorrelationId);\r\n }\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write("\r\n public RpcCallAsync<"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadSerializerEmptyType.GetTypeName(TargetLanguage.CSharp))); + this.Write("> "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "write"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.WriteMaintParam(propSpec))); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteReqSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write(@" request, CommandRequestMetadata? requestMetadata = null, Dictionary? additionalTopicTokenMap = null, TimeSpan? commandTimeout = default, CancellationToken cancellationToken = default) + { + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException(""No MQTT client Id configured. Must connect to MQTT broker before requesting to write property.""); + } + + CommandRequestMetadata metadata = requestMetadata ?? new CommandRequestMetadata(); + additionalTopicTokenMap ??= new(); + + Dictionary prefixedAdditionalTopicTokenMap = new(); + foreach (string key in additionalTopicTokenMap.Keys) + { + prefixedAdditionalTopicTokenMap[""ex:"" + key] = additionalTopicTokenMap[key]; + } + + prefixedAdditionalTopicTokenMap[""consumerClientId""] = clientId; +"); + if (propSpec.DoesWriteTargetMaintainer) { + this.Write(" prefixedAdditionalTopicTokenMap[\"maintainerId\"] = maintainerId;\r\n" + + ""); + } + this.Write("\r\n return new RpcCallAsync<"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteSerializerEmptyType.GetTypeName(TargetLanguage.CSharp))); + this.Write(">(this."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IntWriteMethod(propSpec))); + this.Write("(request, metadata, prefixedAdditionalTopicTokenMap, commandTimeout, cancellation" + + "Token), metadata.CorrelationId);\r\n }\r\n"); + } + } + if (this.eventSpec.Any()) { + this.Write(@" + /// + /// Begin accepting telemetry for all telemetry receivers. + /// + /// Cancellation token. + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await Task.WhenAll( +"); + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp))); + this.Write(".StartAsync(cancellationToken)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IsLast(telemEnvoyInfo) ? ").ConfigureAwait(false);" : ",")); + this.Write("\r\n"); + } + this.Write(" }\r\n"); + } + if (this.eventSpec.Any()) { + this.Write(@" + /// + /// Stop accepting telemetry for all telemetry receivers. + /// + /// Cancellation token. + public async Task StopAsync(CancellationToken cancellationToken = default) + { + await Task.WhenAll( +"); + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp))); + this.Write(".StopAsync(cancellationToken)"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IsLast(telemEnvoyInfo) ? ").ConfigureAwait(false);" : ",")); + this.Write("\r\n"); + } + this.Write(" }\r\n"); + } + foreach (var actionSpec in this.actionSpecs) { + if (actionSpec.ErrorResultName != null) { + this.Write("\r\n private async Task> "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "int"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.RequestSchema, actionSpec.SerializerEmptyType))); + this.Write(" request, CommandRequestMetadata? requestMetadata, Dictionary? pr" + + "efixedAdditionalTopicTokenMap, TimeSpan? commandTimeout, CancellationToken cance" + + "llationToken)\r\n {\r\n ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType))); + this.Write("> extended = await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp))); + this.Write(".InvokeCommandAsync(request, requestMetadata, prefixedAdditionalTopicTokenMap, co" + + "mmandTimeout, cancellationToken);\r\n\r\n if (extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" != null)\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultSchema.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultSchema.GetVariableName(TargetLanguage.CSharp, "exception"))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultSchema.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write("(extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultName.GetFieldName(TargetLanguage.CSharp))); + this.Write(");\r\n"); + if (actionSpec.ErrorCodeName != null) { + this.Write("\r\n if (extended.TryGetApplicationError(out "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorCodeSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorCodeName.GetVariableName(TargetLanguage.CSharp))); + this.Write(", out "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.GetInfoSchema(actionSpec.ErrorInfoSchema))); + this.Write("? "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.GetInfoName(actionSpec.ErrorInfoName))); + this.Write("))\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultSchema.GetVariableName(TargetLanguage.CSharp, "exception"))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultSchema.GetVariableName(TargetLanguage.CSharp, "exception"))); + this.Write(".WithApplicationError(("); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorCodeSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write(")"); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorCodeName.GetVariableName(TargetLanguage.CSharp))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.GetInfoName(actionSpec.ErrorInfoName))); + this.Write(");\r\n }\r\n\r\n"); + } + this.Write(" throw "); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.ErrorResultSchema.GetVariableName(TargetLanguage.CSharp, "exception"))); + this.Write(";\r\n }\r\n else\r\n {\r\n " + + " return new ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType))); + this.Write(">\r\n {\r\n Response = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType))); + this.Write("\r\n {\r\n"); + foreach (CodeName normalResultName in actionSpec.NormalResultNames) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultName.GetFieldName(TargetLanguage.CSharp))); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.NormalRequiredNames.Contains(normalResultName) ? ".Value()" : "")); + this.Write(",\r\n"); + } + this.Write(" },\r\n ResponseMetadata = extended.R" + + "esponseMetadata,\r\n };\r\n }\r\n }\r\n"); + } + } + foreach (var propSpec in this.propSpecs) { + if (propSpec.ReadErrorName != null) { + this.Write("\r\n private async Task> "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "read"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadSerializerEmptyType.GetTypeName(TargetLanguage.CSharp))); + this.Write(" request, CommandRequestMetadata? requestMetadata, Dictionary? pr" + + "efixedAdditionalTopicTokenMap, TimeSpan? commandTimeout, CancellationToken cance" + + "llationToken)\r\n {\r\n ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.ReadRespSchema, propSpec.ReadSerializerEmptyType))); + this.Write("> extended = await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester"))); + this.Write(".InvokeCommandAsync(request, requestMetadata, prefixedAdditionalTopicTokenMap, co" + + "mmandTimeout, cancellationToken);\r\n\r\n if (extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadErrorName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" != null)\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadErrorSchema.GetVariableName(TargetLanguage.CSharp, "exception"))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write("(extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadErrorName.GetFieldName(TargetLanguage.CSharp))); + this.Write(");\r\n throw "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.ReadErrorSchema.GetVariableName(TargetLanguage.CSharp, "exception"))); + this.Write(";\r\n }\r\n else if (extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp))); + this.Write(@" == null) + { + throw new AkriMqttException(""Property read response has neither normal nor error payload content"") + { + Kind = AkriMqttErrorKind.PayloadInvalid, + IsShallow = false, + IsRemote = false, + }; + } + else + { + return new ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.PropSchema, propSpec.ReadSerializerEmptyType))); + this.Write(">\r\n {\r\n"); + if (propSpec.IsAggregate) { + this.Write(" Response = extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp))); + this.Write(",\r\n"); + } else { + this.Write(" Response = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.PropSchema, propSpec.ReadSerializerEmptyType))); + this.Write(" { "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" = extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp))); + this.Write(".Value() },\r\n"); + } + this.Write(" ResponseMetadata = extended.ResponseMetadata,\r\n " + + " };\r\n }\r\n }\r\n"); + } + if (propSpec.WriteReqSchema != null && propSpec.WriteErrorName != null) { + this.Write("\r\n private async Task> "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "write"))); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteReqSchema.GetTypeName(TargetLanguage.CSharp))); + this.Write(" request, CommandRequestMetadata? requestMetadata, Dictionary? pr" + + "efixedAdditionalTopicTokenMap, TimeSpan? commandTimeout, CancellationToken cance" + + "llationToken)\r\n {\r\n ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.SchemaType(propSpec.WriteRespSchema, propSpec.WriteSerializerEmptyType))); + this.Write("> extended = await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester"))); + this.Write(".InvokeCommandAsync(request, requestMetadata, prefixedAdditionalTopicTokenMap, co" + + "mmandTimeout, cancellationToken);\r\n\r\n if (extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorName.GetFieldName(TargetLanguage.CSharp))); + this.Write(" != null)\r\n {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorSchema.GetVariableName(TargetLanguage.CSharp, "exception"))); + this.Write(" = new "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception"))); + this.Write("(extended.Response."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorName.GetFieldName(TargetLanguage.CSharp))); + this.Write(");\r\n throw "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteErrorSchema.GetVariableName(TargetLanguage.CSharp, "exception"))); + this.Write(";\r\n }\r\n else\r\n {\r\n " + + " return new ExtendedResponse<"); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteSerializerEmptyType.GetTypeName(TargetLanguage.CSharp))); + this.Write(">\r\n {\r\n Response = "); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.WriteSerializerEmptyType.GetAllocator(TargetLanguage.CSharp))); + this.Write(",\r\n ResponseMetadata = extended.ResponseMetadata,\r\n " + + " };\r\n }\r\n }\r\n"); + } + } + this.Write("\r\n public async ValueTask DisposeAsync()\r\n {\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp))); + this.Write(".DisposeAsync().ConfigureAwait(false);\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester"))); + this.Write(".DisposeAsync().ConfigureAwait(false);\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester"))); + this.Write(".DisposeAsync().ConfigureAwait(false); \r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp))); + this.Write(".DisposeAsync().ConfigureAwait(false);\r\n"); + } + this.Write(" }\r\n\r\n public async ValueTask DisposeAsync(bool disposing)\r" + + "\n {\r\n"); + foreach (var actionSpec in this.actionSpecs) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp))); + this.Write(".DisposeAsync(disposing).ConfigureAwait(false);\r\n"); + } + foreach (var propSpec in this.propSpecs) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester"))); + this.Write(".DisposeAsync(disposing).ConfigureAwait(false);\r\n"); + if (propSpec.WriteReqSchema != null) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester"))); + this.Write(".DisposeAsync(disposing).ConfigureAwait(false); \r\n"); + } + } + foreach (var telemEnvoyInfo in this.eventSpec) { + this.Write(" await this."); + this.Write(this.ToStringHelper.ToStringWithCulture(telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp))); + this.Write(".DisposeAsync(disposing).ConfigureAwait(false);\r\n"); + } + this.Write(" }\r\n }\r\n"); + } + this.Write(" }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + + private string IntLValue(ActionSpec actionSpec) => (actionSpec.ResponseSchema != null ? $"ExtendedResponse<{this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}> extended = " : $"CommandResponseMetadata? responseMetadata = "); + + private string IntRValue(ActionSpec actionSpec) => (actionSpec.ResponseSchema != null ? "Response = extended.Response, ResponseMetadata = extended.ResponseMetadata " : "ResponseMetadata = responseMetadata "); + + private string ExtRespType(ActionSpec actionSpec) => this.CondWrap(actionSpec.ResponseSchema != null ? $"ExtendedResponse<{this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}>" : "CommandResponseMetadata?"); + + private string EmptyResp(ActionSpec actionSpec) => this.CondFrom(actionSpec.ResponseSchema != null ? $"new ExtendedResponse<{this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}> {{ Response = new {this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}() }}" : "(CommandResponseMetadata?)new CommandResponseMetadata()"); + + private string CondWrap(string type) => $"Task<{type}>"; + + private string CondFrom(string res) => $"Task.FromResult({res})"; + + private string ReqParam(ActionSpec actionSpec) => actionSpec.RequestSchema != null ? $"{this.SchemaType(actionSpec.RequestSchema, actionSpec.SerializerEmptyType)} request, " : ""; + + private string ReqArgs(ActionSpec actionSpec, string reqVar) => actionSpec.RequestSchema != null ? $"{reqVar}.Request!, {reqVar}.RequestMetadata!" : $"{reqVar}.RequestMetadata!"; + + private string CallAsyncType(ActionSpec actionSpec) => $"RpcCallAsync<{this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}>"; + + private string CallAsyncType(PropertySpec propSpec) => $"RpcCallAsync<{this.SchemaType(propSpec.PropSchema, propSpec.ReadSerializerEmptyType)}>"; + + private string IntMethod(ActionSpec actionSpec) => actionSpec.ErrorResultName != null ? actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "int") : $"{actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp)}.InvokeCommandAsync"; + + private string IntReadMethod(PropertySpec propSpec) => propSpec.ReadErrorName != null ? propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "read") : $"{propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester")}.InvokeCommandAsync"; + + private string IntWriteMethod(PropertySpec propSpec) => propSpec.WriteErrorName != null ? propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "write") : $"{propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester")}.InvokeCommandAsync"; + + private string SchemaType(ITypeName schema, EmptyTypeName emptyType) => schema?.GetTypeName(TargetLanguage.CSharp) ?? emptyType.GetTypeName(TargetLanguage.CSharp); + + private string ExecParam(ActionSpec actionSpec) => actionSpec.DoesTargetExecutor ? "string executorId, " : ""; + + private string ReadMaintParam(PropertySpec propSpec) => propSpec.DoesReadTargetMaintainer ? "string maintainerId, " : ""; + + private string WriteMaintParam(PropertySpec propSpec) => propSpec.DoesWriteTargetMaintainer ? "string maintainerId, " : ""; + + private bool IsLast(ActionSpec actionSpec) => actionSpec.Name.AsGiven == this.actionSpecs.Last().Name.AsGiven && !this.propSpecs.Any(); + + private bool IsLast(PropertySpec propSpec) => propSpec.Name == null || propSpec.Name.AsGiven == this.propSpecs.Last().Name.AsGiven; + + private bool IsLast(EventSpec telemEnvoyInfo) => telemEnvoyInfo.Name.AsGiven == this.eventSpec.Last().Name.AsGiven; + + private string TelemMethodName(EventSpec telemEnvoyInfo, string prefix, string suffix = null) => ((telemEnvoyInfo.Schema is RawTypeName || telemEnvoyInfo.Schema is CustomTypeName) ? telemEnvoyInfo.Name : new CodeName()).GetMethodName(TargetLanguage.CSharp, "telemetry", suffix, prefix: prefix); + + private string GetInfoName(CodeName infoName) => infoName?.GetVariableName(TargetLanguage.CSharp) ?? "errorPayload"; + + private string GetInfoSchema(CodeName infoSchema) => infoSchema?.GetTypeName(TargetLanguage.CSharp) ?? "string"; + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetServiceBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Service/t4/DotNetService.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Service/t4/DotNetService.tt new file mode 100644 index 0000000000..2c8bd79ab8 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/dotnet/Service/t4/DotNetService.tt @@ -0,0 +1,732 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; + using System.Collections.Generic; + using System.Linq; +<# if (this.actionSpecs.Any() || this.propSpecs.Any() || this.eventSpec.Any()) { #> + using System.Threading; +<# } #> + using System.Threading.Tasks; + using Azure.Iot.Operations.Protocol.Models; + using Azure.Iot.Operations.Protocol; + using Azure.Iot.Operations.Protocol.RPC; + using Azure.Iot.Operations.Protocol.Telemetry; + using <#=this.projectName#>; + + [System.CodeDom.Compiler.GeneratedCode("Azure.Iot.Operations.ProtocolCompilerLib", "<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>")] + public static partial class <#=this.serviceName.GetTypeName(TargetLanguage.CSharp)#> + { +<# if (this.generateServer) { #> + public abstract partial class Service : IAsyncDisposable + { + private ApplicationContext applicationContext; + private IMqttPubSubClient mqttClient; +<# foreach (var actionSpec in this.actionSpecs) { #> + private readonly <#=actionSpec.Executor.GetTypeName(TargetLanguage.CSharp)#> <#=actionSpec.Executor.GetVariableName(TargetLanguage.CSharp)#>; +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + private readonly <#=propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.readResponderName.GetTypeName(TargetLanguage.CSharp)#> <#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder")#>; +<# if (propSpec.WriteReqSchema != null) { #> + private readonly <#=propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.writeResponderName.GetTypeName(TargetLanguage.CSharp)#> <#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder")#>; +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + private readonly <#=telemEnvoyInfo.Sender.GetTypeName(TargetLanguage.CSharp)#> <#=telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp)#>; +<# } #> + + /// + /// Construct a new instance of this service. + /// + /// The shared context for your application. + /// The MQTT client to use. + /// + /// The topic token replacement map to use for all operations by default. Generally, this will include the token values + /// for topic tokens that should be the same for the duration of this service's lifetime. Note that + /// additional topic tokens can be specified per event message. + /// + public Service(ApplicationContext applicationContext, IMqttPubSubClient mqttClient, Dictionary? topicTokenMap = null) + { + this.applicationContext = applicationContext; + this.mqttClient = mqttClient; + + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException("No MQTT client Id configured. Must connect to MQTT broker before invoking command."); + } + +<# foreach (var actionSpec in this.actionSpecs) { #> + this.<#=actionSpec.Executor.GetVariableName(TargetLanguage.CSharp)#> = new <#=actionSpec.Executor.GetTypeName(TargetLanguage.CSharp)#>(applicationContext, mqttClient) { OnCommandReceived = <#=actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "int")#> }; +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder")#> = new <#=propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.readResponderName.GetTypeName(TargetLanguage.CSharp)#>(applicationContext, mqttClient) { OnCommandReceived = <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "read")#> }; +<# if (propSpec.WriteReqSchema != null) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder")#> = new <#=propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.writeResponderName.GetTypeName(TargetLanguage.CSharp)#>(applicationContext, mqttClient) { OnCommandReceived = <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "write")#> }; +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + this.<#=telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp)#> = new <#=telemEnvoyInfo.Sender.GetTypeName(TargetLanguage.CSharp)#>(applicationContext, mqttClient); +<# } #> + + if (topicTokenMap != null) + { + foreach (string topicTokenKey in topicTokenMap.Keys) + { +<# foreach (var actionSpec in this.actionSpecs) { #> + this.<#=actionSpec.Executor.GetVariableName(TargetLanguage.CSharp)#>.TopicTokenMap.TryAdd("ex:" + topicTokenKey, topicTokenMap[topicTokenKey]); +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder")#>.TopicTokenMap.TryAdd("ex:" + topicTokenKey, topicTokenMap[topicTokenKey]); +<# if (propSpec.WriteReqSchema != null) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder")#>.TopicTokenMap.TryAdd("ex:" + topicTokenKey, topicTokenMap[topicTokenKey]); +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + this.<#=telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp)#>.TopicTokenMap.TryAdd("ex:" + topicTokenKey, topicTokenMap[topicTokenKey]); +<# } #> + } + } + +<# foreach (var actionSpec in this.actionSpecs) { #> + this.<#=actionSpec.Executor.GetVariableName(TargetLanguage.CSharp)#>.TopicTokenMap.TryAdd("<#=MqttTopicTokens.ActionExecutorId#>", clientId); +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder")#>.TopicTokenMap.TryAdd("<#=MqttTopicTokens.PropertyMaintainerId#>", clientId); +<# if (propSpec.WriteReqSchema != null) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder")#>.TopicTokenMap.TryAdd("<#=MqttTopicTokens.PropertyMaintainerId#>", clientId); +<# } #> +<# } #> + } +<# foreach (var actionSpec in this.actionSpecs) { #> + + public <#=actionSpec.Executor.GetTypeName(TargetLanguage.CSharp)#> <#=actionSpec.Executor.GetTypeName(TargetLanguage.CSharp)#> { get => this.<#=actionSpec.Executor.GetVariableName(TargetLanguage.CSharp)#>; } +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + + public <#=propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.readResponderName.GetTypeName(TargetLanguage.CSharp)#> <#=propSpec.Name.GetTypeName(TargetLanguage.CSharp, "read", "responder")#> { get => this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder")#>; } +<# if (propSpec.WriteReqSchema != null) { #> + + public <#=propSpec.Maintainer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.writeResponderName.GetTypeName(TargetLanguage.CSharp)#> <#=propSpec.Name.GetTypeName(TargetLanguage.CSharp, "write", "responder")#> { get => this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder")#>; } +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + + public <#=telemEnvoyInfo.Sender.GetTypeName(TargetLanguage.CSharp)#> <#=telemEnvoyInfo.Sender.GetTypeName(TargetLanguage.CSharp)#> { get => this.<#=telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp)#>; } +<# } #> +<# foreach (var actionSpec in this.actionSpecs) { #> + + public <#=this.defaultImpl ? "virtual" : "abstract"#> <#=this.ExtRespType(actionSpec)#> <#=actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "async")#>(<#=this.ReqParam(actionSpec)#>CommandRequestMetadata requestMetadata, CancellationToken cancellationToken)<#=this.defaultImpl ? "" : ";"#> +<# if (this.defaultImpl) { #> + { + return <#=this.EmptyResp(actionSpec)#>; + } +<# } #> +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + + public abstract Task>> <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "read")#>(CommandRequestMetadata requestMetadata, CancellationToken cancellationToken); +<# if (propSpec.WriteReqSchema != null) { #> + + public abstract Task <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "write")#>(<#=propSpec.WriteReqSchema.GetTypeName(TargetLanguage.CSharp)#> <#=propSpec.Name.GetVariableName(TargetLanguage.CSharp)#>, CommandRequestMetadata requestMetadata, CancellationToken cancellationToken); +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + + /// + /// Send telemetry. + /// + /// The payload of the telemetry. + /// The metadata of the telemetry. + /// + /// The topic token replacement map to use in addition to the topic token map provided in the constructor. If this map + /// contains any keys that topic token map provided in the constructor also has, then values specified in this map will take precedence. + /// + /// The quality of service to send the telemetry with. + /// How long the telemetry message will be available on the broker for a receiver to receive. + /// Cancellation token. + public async Task <#=this.TelemMethodName(telemEnvoyInfo, "send", "async")#>(<#=telemEnvoyInfo.Schema.GetTypeName(TargetLanguage.CSharp)#> telemetry, OutgoingTelemetryMetadata metadata, Dictionary? additionalTopicTokenMap = null, MqttQualityOfServiceLevel qos = MqttQualityOfServiceLevel.AtLeastOnce, TimeSpan? telemetryTimeout = null, CancellationToken cancellationToken = default) + { + additionalTopicTokenMap ??= new(); + + Dictionary prefixedAdditionalTopicTokenMap = new(); + foreach (string key in additionalTopicTokenMap.Keys) + { + prefixedAdditionalTopicTokenMap["ex:" + key] = additionalTopicTokenMap[key]; + } + await this.<#=telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp)#>.SendTelemetryAsync(telemetry, metadata, prefixedAdditionalTopicTokenMap, qos, telemetryTimeout, cancellationToken); + } +<# } #> +<# if (this.actionSpecs.Any() || this.propSpecs.Any()) { #> + + /// + /// Begin accepting command invocations for all command executors. + /// + /// The dispatch concurrency count for the command response cache to use. + /// Cancellation token. + public async Task StartAsync(int? preferredDispatchConcurrency = null, CancellationToken cancellationToken = default) + { + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException("No MQTT client Id configured. Must connect to MQTT broker before starting service."); + } + + await Task.WhenAll( +<# foreach (var actionSpec in this.actionSpecs) { #> + this.<#=actionSpec.Executor.GetVariableName(TargetLanguage.CSharp)#>.StartAsync(preferredDispatchConcurrency, cancellationToken)<#=this.IsLast(actionSpec) ? ").ConfigureAwait(false);" : ","#> +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder")#>.StartAsync(preferredDispatchConcurrency, cancellationToken)<#=this.IsLast(propSpec) && propSpec.WriteReqSchema == null ? ").ConfigureAwait(false);" : ","#> +<# if (propSpec.WriteReqSchema != null) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder")#>.StartAsync(preferredDispatchConcurrency, cancellationToken)<#=this.IsLast(propSpec) ? ").ConfigureAwait(false);" : ","#> +<# } #> +<# } #> + } + + public async Task StopAsync(CancellationToken cancellationToken = default) + { + await Task.WhenAll( +<# foreach (var actionSpec in this.actionSpecs) { #> + this.<#=actionSpec.Executor.GetVariableName(TargetLanguage.CSharp)#>.StopAsync(cancellationToken)<#=this.IsLast(actionSpec) ? ").ConfigureAwait(false);" : ","#> +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder")#>.StopAsync(cancellationToken)<#=this.IsLast(propSpec) && propSpec.WriteReqSchema == null ? ").ConfigureAwait(false);" : ","#> +<# if (propSpec.WriteReqSchema != null) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder")#>.StopAsync(cancellationToken)<#=this.IsLast(propSpec) ? ").ConfigureAwait(false);" : ","#> +<# } #> +<# } #> + } +<# } #> +<# foreach (var actionSpec in this.actionSpecs) { #> + + private async Task>> <#=actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "int")#>(ExtendedRequest<<#=this.SchemaType(actionSpec.RequestSchema, actionSpec.SerializerEmptyType)#>> req, CancellationToken cancellationToken) + { +<# if (actionSpec.ErrorResultName != null) { #> + try + { + <#=this.IntLValue(actionSpec)#>await this.<#=actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "async")#>(<#=this.ReqArgs(actionSpec, "req")#>, cancellationToken); + + return new ExtendedResponse<<#=this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)#>> + { + + Response = new <#=this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)#> + { +<# foreach (CodeName normalResultName in actionSpec.NormalResultNames) { #> + <#=normalResultName.GetFieldName(TargetLanguage.CSharp)#> = extended.Response.<#=normalResultName.GetFieldName(TargetLanguage.CSharp)#>, +<# } #> + }, + ResponseMetadata = extended.ResponseMetadata, + }; + } + catch (<#=actionSpec.ErrorResultSchema.GetTypeName(TargetLanguage.CSharp, "exception")#> intEx) + { + ExtendedResponse<<#=this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)#>> extendedResponse = ExtendedResponse<<#=this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)#>>.CreateFromResponse(new <#=this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)#> { <#=actionSpec.ErrorResultName.GetFieldName(TargetLanguage.CSharp)#> = intEx.<#=actionSpec.ErrorResultSchema.GetFieldName(TargetLanguage.CSharp)#> }); +<# if (actionSpec.ErrorCodeName != null) { #> + + if (intEx.TryGetApplicationError(out <#=actionSpec.ErrorCodeSchema.GetTypeName(TargetLanguage.CSharp)#>? <#=actionSpec.ErrorCodeName.GetVariableName(TargetLanguage.CSharp)#>, out <#=this.GetInfoSchema(actionSpec.ErrorInfoSchema)#>? <#=this.GetInfoName(actionSpec.ErrorInfoName)#>)) + { + extendedResponse = extendedResponse.WithApplicationError((<#=actionSpec.ErrorCodeSchema.GetTypeName(TargetLanguage.CSharp)#>)<#=actionSpec.ErrorCodeName.GetVariableName(TargetLanguage.CSharp)#>, <#=this.GetInfoName(actionSpec.ErrorInfoName)#>); + } + +<# } #> + return extendedResponse; + } +<# } else { #> + <#=this.IntLValue(actionSpec)#>await this.<#=actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "async")#>(<#=this.ReqArgs(actionSpec, "req")#>, cancellationToken); + return new ExtendedResponse<<#=this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)#>> { <#=this.IntRValue(actionSpec)#>}; +<# } #> + } +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + + private async Task>> <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "read")#>(ExtendedRequest<<#=propSpec.ReadSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)#>> req, CancellationToken cancellationToken) + { +<# if (propSpec.ReadErrorName != null) { #> + try + { + ExtendedResponse<<#=propSpec.PropSchema.GetTypeName(TargetLanguage.CSharp)#>> extended = await this.<#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "read")#>(req.RequestMetadata!, cancellationToken); + + return new ExtendedResponse<<#=propSpec.ReadRespSchema.GetTypeName(TargetLanguage.CSharp)#>> + { + Response = new <#=propSpec.ReadRespSchema.GetTypeName(TargetLanguage.CSharp)#> { <#=propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp)#> = extended.Response<#=propSpec.IsAggregate ? "" : $".{propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp)}"#> }, + ResponseMetadata = extended.ResponseMetadata, + }; + } + catch (<#=propSpec.ReadErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception")#> intEx) + { + ExtendedResponse<<#=this.SchemaType(propSpec.ReadRespSchema, propSpec.ReadSerializerEmptyType)#>> extendedResponse = ExtendedResponse<<#=this.SchemaType(propSpec.ReadRespSchema, propSpec.ReadSerializerEmptyType)#>>.CreateFromResponse(new <#=this.SchemaType(propSpec.ReadRespSchema, propSpec.ReadSerializerEmptyType)#> { <#=propSpec.ReadErrorName.GetFieldName(TargetLanguage.CSharp)#> = intEx.<#=propSpec.ReadErrorSchema.GetFieldName(TargetLanguage.CSharp)#> }); + return extendedResponse; + } +<# } else { #> + return await this.<#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "read")#>(req.RequestMetadata!, cancellationToken); +<# } #> + } +<# if (propSpec.WriteReqSchema != null) { #> + + private async Task>> <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "write")#>(ExtendedRequest<<#=propSpec.WriteReqSchema.GetTypeName(TargetLanguage.CSharp)#>> req, CancellationToken cancellationToken) + { +<# if (propSpec.WriteRespSchema != null) { #> + try + { + CommandResponseMetadata? respMetadata = await this.<#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "write")#>(req.Request!, req.RequestMetadata!, cancellationToken); + + return new ExtendedResponse<<#=propSpec.WriteRespSchema.GetTypeName(TargetLanguage.CSharp)#>> + { + Response = new <#=propSpec.WriteRespSchema.GetTypeName(TargetLanguage.CSharp)#> + { + <#=propSpec.WriteErrorName.GetFieldName(TargetLanguage.CSharp)#> = null, + }, + ResponseMetadata = respMetadata, + }; + } + catch (<#=propSpec.WriteErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception")#> intEx) + { + ExtendedResponse<<#=this.SchemaType(propSpec.WriteRespSchema, propSpec.WriteSerializerEmptyType)#>> extendedResponse = ExtendedResponse<<#=this.SchemaType(propSpec.WriteRespSchema, propSpec.WriteSerializerEmptyType)#>>.CreateFromResponse(new <#=this.SchemaType(propSpec.WriteRespSchema, propSpec.WriteSerializerEmptyType)#> { <#=propSpec.WriteErrorName.GetFieldName(TargetLanguage.CSharp)#> = intEx.<#=propSpec.WriteErrorSchema.GetFieldName(TargetLanguage.CSharp)#> }); + return extendedResponse; + } +<# } else { #> + CommandResponseMetadata? respMetadata = await this.<#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "write")#>(req.Request!, req.RequestMetadata!, cancellationToken); + return new ExtendedResponse<<#=propSpec.WriteSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)#>> + { + Response = new <#=propSpec.WriteSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)#>(), + ResponseMetadata = respMetadata, + }; +<# } #> + } +<# } #> +<# } #> + + public async ValueTask DisposeAsync() + { +<# foreach (var actionSpec in this.actionSpecs) { #> + await this.<#=actionSpec.Executor.GetVariableName(TargetLanguage.CSharp)#>.DisposeAsync().ConfigureAwait(false); +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder")#>.DisposeAsync().ConfigureAwait(false); +<# if (propSpec.WriteReqSchema != null) { #> + await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder")#>.DisposeAsync().ConfigureAwait(false); +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + await this.<#=telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp)#>.DisposeAsync().ConfigureAwait(false); +<# } #> + } + + public async ValueTask DisposeAsync(bool disposing) + { +<# foreach (var actionSpec in this.actionSpecs) { #> + await this.<#=actionSpec.Executor.GetVariableName(TargetLanguage.CSharp)#>.DisposeAsync(disposing).ConfigureAwait(false); +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "responder")#>.DisposeAsync(disposing).ConfigureAwait(false); +<# if (propSpec.WriteReqSchema != null) { #> + await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "responder")#>.DisposeAsync(disposing).ConfigureAwait(false); +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + await this.<#=telemEnvoyInfo.Sender.GetVariableName(TargetLanguage.CSharp)#>.DisposeAsync(disposing).ConfigureAwait(false); +<# } #> + } + } +<# } #> +<# if (this.generateServer && this.generateClient) { #> + +<# } #> +<# if (this.generateClient) { #> + public abstract partial class Client<#=this.actionSpecs.Any() || this.propSpecs.Any() ? " : IAsyncDisposable" : ""#> + { + private ApplicationContext applicationContext; + private IMqttPubSubClient mqttClient; +<# foreach (var actionSpec in this.actionSpecs) { #> + private readonly <#=actionSpec.Invoker.GetTypeName(TargetLanguage.CSharp)#> <#=actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp)#>; +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + private readonly <#=propSpec.Consumer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.readRequesterName.GetTypeName(TargetLanguage.CSharp)#> <#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester")#>; +<# if (propSpec.WriteReqSchema != null) { #> + private readonly <#=propSpec.Consumer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.writeRequesterName.GetTypeName(TargetLanguage.CSharp)#> <#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester")#>; +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + private readonly <#=telemEnvoyInfo.Receiver.GetTypeName(TargetLanguage.CSharp)#> <#=telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp)#>; +<# } #> + + /// + /// Construct a new instance of this client. + /// + /// The shared context for your application. + /// The MQTT client to use. + /// + /// The topic token replacement map to use for all operations by default. Generally, this will include the token values + /// for topic tokens that should be the same for the duration of this client's lifetime. + /// + public Client(ApplicationContext applicationContext, IMqttPubSubClient mqttClient, Dictionary? topicTokenMap = null) + { + this.applicationContext = applicationContext; + this.mqttClient = mqttClient; + +<# foreach (var actionSpec in this.actionSpecs) { #> + this.<#=actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp)#> = new <#=actionSpec.Invoker.GetTypeName(TargetLanguage.CSharp)#>(applicationContext, mqttClient); +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester")#> = new <#=propSpec.Consumer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.readRequesterName.GetTypeName(TargetLanguage.CSharp)#>(applicationContext, mqttClient); +<# if (propSpec.WriteReqSchema != null) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester")#> = new <#=propSpec.Consumer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.writeRequesterName.GetTypeName(TargetLanguage.CSharp)#>(applicationContext, mqttClient); +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + this.<#=telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp)#> = new <#=telemEnvoyInfo.Receiver.GetTypeName(TargetLanguage.CSharp)#>(applicationContext, mqttClient) { OnTelemetryReceived = this.<#=this.TelemMethodName(telemEnvoyInfo, "receive")#> }; +<# } #> + + if (topicTokenMap != null) + { + foreach (string topicTokenKey in topicTokenMap.Keys) + { +<# foreach (var actionSpec in this.actionSpecs) { #> + this.<#=actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp)#>.TopicTokenMap.TryAdd("ex:" + topicTokenKey, topicTokenMap[topicTokenKey]); +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester")#>.TopicTokenMap.TryAdd("ex:" + topicTokenKey, topicTokenMap[topicTokenKey]); +<# if (propSpec.WriteReqSchema != null) { #> + this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester")#>.TopicTokenMap.TryAdd("ex:" + topicTokenKey, topicTokenMap[topicTokenKey]); +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + this.<#=telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp)#>.TopicTokenMap.TryAdd("ex:" + topicTokenKey, topicTokenMap[topicTokenKey]); +<# } #> + } + } + } +<# foreach (var actionSpec in this.actionSpecs) { #> + + public <#=actionSpec.Invoker.GetTypeName(TargetLanguage.CSharp)#> <#=actionSpec.Invoker.GetTypeName(TargetLanguage.CSharp)#> { get => this.<#=actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp)#>; } +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + + public <#=propSpec.Consumer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.readRequesterName.GetTypeName(TargetLanguage.CSharp)#> <#=propSpec.Name.GetTypeName(TargetLanguage.CSharp, "read", "requester")#> { get => this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester")#>; } +<# if (propSpec.WriteReqSchema != null) { #> + + public <#=propSpec.Consumer.GetTypeName(TargetLanguage.CSharp)#>.<#=this.writeRequesterName.GetTypeName(TargetLanguage.CSharp)#> <#=propSpec.Name.GetTypeName(TargetLanguage.CSharp, "write", "requester")#> { get => this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester")#>; } +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + + public <#=telemEnvoyInfo.Receiver.GetTypeName(TargetLanguage.CSharp)#> <#=telemEnvoyInfo.Receiver.GetTypeName(TargetLanguage.CSharp)#> { get => this.<#=telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp)#>; } +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + + public <#=this.defaultImpl ? "virtual" : "abstract"#> Task <#=this.TelemMethodName(telemEnvoyInfo, "receive")#>(string senderId, <#=telemEnvoyInfo.Schema.GetTypeName(TargetLanguage.CSharp)#> telemetry, IncomingTelemetryMetadata metadata)<#=this.defaultImpl ? "" : ";"#> +<# if (this.defaultImpl) { #> + { + return Task.CompletedTask; + } +<# } #> +<# } #> +<# foreach (var actionSpec in this.actionSpecs) { #> + + /// + /// Invoke a command. + /// +<# if (actionSpec.DoesTargetExecutor) { #> + /// The identifier of the executor targeted by this command request. +<# } #> +<# if (actionSpec.RequestSchema != null) { #> + /// The data for this command request. +<# } #> + /// The metadata for this command request. + /// + /// The topic token replacement map to use in addition to the topic tokens specified in the constructor. If this map + /// contains any keys that the topic tokens specified in the constructor also has, then values specified in this map will take precedence. + /// + /// How long the command will be available on the broker for an executor to receive. + /// Cancellation token. + /// The command response. + public <#=this.CallAsyncType(actionSpec)#> <#=actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "async")#>(<#=this.ExecParam(actionSpec)#><#=this.ReqParam(actionSpec)#>CommandRequestMetadata? requestMetadata = null, Dictionary? additionalTopicTokenMap = null, TimeSpan? commandTimeout = default, CancellationToken cancellationToken = default) + { + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException("No MQTT client Id configured. Must connect to MQTT broker before invoking command."); + } + + CommandRequestMetadata metadata = requestMetadata ?? new CommandRequestMetadata(); + additionalTopicTokenMap ??= new(); + + Dictionary prefixedAdditionalTopicTokenMap = new(); + foreach (string key in additionalTopicTokenMap.Keys) + { + prefixedAdditionalTopicTokenMap["ex:" + key] = additionalTopicTokenMap[key]; + } + + prefixedAdditionalTopicTokenMap["invokerClientId"] = clientId; +<# if (actionSpec.DoesTargetExecutor) { #> + prefixedAdditionalTopicTokenMap["executorId"] = executorId; +<# } #> + + return new <#=this.CallAsyncType(actionSpec)#>(this.<#=this.IntMethod(actionSpec)#>(<#=actionSpec.RequestSchema != null ? $"request" : actionSpec.SerializerEmptyType.GetAllocator(TargetLanguage.CSharp)#>, metadata, prefixedAdditionalTopicTokenMap, commandTimeout, cancellationToken), metadata.CorrelationId); + } +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + + public <#=this.CallAsyncType(propSpec)#> <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "read")#>(<#=this.ReadMaintParam(propSpec)#>CommandRequestMetadata? requestMetadata = null, Dictionary? additionalTopicTokenMap = null, TimeSpan? commandTimeout = default, CancellationToken cancellationToken = default) + { + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException("No MQTT client Id configured. Must connect to MQTT broker before requesting to read property."); + } + + CommandRequestMetadata metadata = requestMetadata ?? new CommandRequestMetadata(); + additionalTopicTokenMap ??= new(); + + Dictionary prefixedAdditionalTopicTokenMap = new(); + foreach (string key in additionalTopicTokenMap.Keys) + { + prefixedAdditionalTopicTokenMap["ex:" + key] = additionalTopicTokenMap[key]; + } + + prefixedAdditionalTopicTokenMap["consumerClientId"] = clientId; +<# if (propSpec.DoesReadTargetMaintainer) { #> + prefixedAdditionalTopicTokenMap["maintainerId"] = maintainerId; +<# } #> + + return new <#=this.CallAsyncType(propSpec)#>(this.<#=this.IntReadMethod(propSpec)#>(<#=propSpec.ReadSerializerEmptyType.GetAllocator(TargetLanguage.CSharp)#>, metadata, prefixedAdditionalTopicTokenMap, commandTimeout, cancellationToken), metadata.CorrelationId); + } +<# if (propSpec.WriteReqSchema != null) { #> + + public RpcCallAsync<<#=propSpec.ReadSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)#>> <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "async", prefix: "write")#>(<#=this.WriteMaintParam(propSpec)#><#=propSpec.WriteReqSchema.GetTypeName(TargetLanguage.CSharp)#> request, CommandRequestMetadata? requestMetadata = null, Dictionary? additionalTopicTokenMap = null, TimeSpan? commandTimeout = default, CancellationToken cancellationToken = default) + { + string? clientId = this.mqttClient.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + throw new InvalidOperationException("No MQTT client Id configured. Must connect to MQTT broker before requesting to write property."); + } + + CommandRequestMetadata metadata = requestMetadata ?? new CommandRequestMetadata(); + additionalTopicTokenMap ??= new(); + + Dictionary prefixedAdditionalTopicTokenMap = new(); + foreach (string key in additionalTopicTokenMap.Keys) + { + prefixedAdditionalTopicTokenMap["ex:" + key] = additionalTopicTokenMap[key]; + } + + prefixedAdditionalTopicTokenMap["consumerClientId"] = clientId; +<# if (propSpec.DoesWriteTargetMaintainer) { #> + prefixedAdditionalTopicTokenMap["maintainerId"] = maintainerId; +<# } #> + + return new RpcCallAsync<<#=propSpec.WriteSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)#>>(this.<#=this.IntWriteMethod(propSpec)#>(request, metadata, prefixedAdditionalTopicTokenMap, commandTimeout, cancellationToken), metadata.CorrelationId); + } +<# } #> +<# } #> +<# if (this.eventSpec.Any()) { #> + + /// + /// Begin accepting telemetry for all telemetry receivers. + /// + /// Cancellation token. + public async Task StartAsync(CancellationToken cancellationToken = default) + { + await Task.WhenAll( +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + this.<#=telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp)#>.StartAsync(cancellationToken)<#=this.IsLast(telemEnvoyInfo) ? ").ConfigureAwait(false);" : ","#> +<# } #> + } +<# } #> +<# if (this.eventSpec.Any()) { #> + + /// + /// Stop accepting telemetry for all telemetry receivers. + /// + /// Cancellation token. + public async Task StopAsync(CancellationToken cancellationToken = default) + { + await Task.WhenAll( +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + this.<#=telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp)#>.StopAsync(cancellationToken)<#=this.IsLast(telemEnvoyInfo) ? ").ConfigureAwait(false);" : ","#> +<# } #> + } +<# } #> +<# foreach (var actionSpec in this.actionSpecs) { #> +<# if (actionSpec.ErrorResultName != null) { #> + + private async Task>> <#=actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "int")#>(<#=this.SchemaType(actionSpec.RequestSchema, actionSpec.SerializerEmptyType)#> request, CommandRequestMetadata? requestMetadata, Dictionary? prefixedAdditionalTopicTokenMap, TimeSpan? commandTimeout, CancellationToken cancellationToken) + { + ExtendedResponse<<#=this.SchemaType(actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)#>> extended = await this.<#=actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp)#>.InvokeCommandAsync(request, requestMetadata, prefixedAdditionalTopicTokenMap, commandTimeout, cancellationToken); + + if (extended.Response.<#=actionSpec.ErrorResultName.GetFieldName(TargetLanguage.CSharp)#> != null) + { + <#=actionSpec.ErrorResultSchema.GetTypeName(TargetLanguage.CSharp, "exception")#> <#=actionSpec.ErrorResultSchema.GetVariableName(TargetLanguage.CSharp, "exception")#> = new <#=actionSpec.ErrorResultSchema.GetTypeName(TargetLanguage.CSharp, "exception")#>(extended.Response.<#=actionSpec.ErrorResultName.GetFieldName(TargetLanguage.CSharp)#>); +<# if (actionSpec.ErrorCodeName != null) { #> + + if (extended.TryGetApplicationError(out <#=actionSpec.ErrorCodeSchema.GetTypeName(TargetLanguage.CSharp)#>? <#=actionSpec.ErrorCodeName.GetVariableName(TargetLanguage.CSharp)#>, out <#=this.GetInfoSchema(actionSpec.ErrorInfoSchema)#>? <#=this.GetInfoName(actionSpec.ErrorInfoName)#>)) + { + <#=actionSpec.ErrorResultSchema.GetVariableName(TargetLanguage.CSharp, "exception")#> = <#=actionSpec.ErrorResultSchema.GetVariableName(TargetLanguage.CSharp, "exception")#>.WithApplicationError((<#=actionSpec.ErrorCodeSchema.GetTypeName(TargetLanguage.CSharp)#>)<#=actionSpec.ErrorCodeName.GetVariableName(TargetLanguage.CSharp)#>, <#=this.GetInfoName(actionSpec.ErrorInfoName)#>); + } + +<# } #> + throw <#=actionSpec.ErrorResultSchema.GetVariableName(TargetLanguage.CSharp, "exception")#>; + } + else + { + return new ExtendedResponse<<#=this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)#>> + { + Response = new <#=this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)#> + { +<# foreach (CodeName normalResultName in actionSpec.NormalResultNames) { #> + <#=normalResultName.GetFieldName(TargetLanguage.CSharp)#> = extended.Response.<#=normalResultName.GetFieldName(TargetLanguage.CSharp)#><#=actionSpec.NormalRequiredNames.Contains(normalResultName) ? ".Value()" : ""#>, +<# } #> + }, + ResponseMetadata = extended.ResponseMetadata, + }; + } + } +<# } #> +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> +<# if (propSpec.ReadErrorName != null) { #> + + private async Task>> <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "read")#>(<#=propSpec.ReadSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)#> request, CommandRequestMetadata? requestMetadata, Dictionary? prefixedAdditionalTopicTokenMap, TimeSpan? commandTimeout, CancellationToken cancellationToken) + { + ExtendedResponse<<#=this.SchemaType(propSpec.ReadRespSchema, propSpec.ReadSerializerEmptyType)#>> extended = await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester")#>.InvokeCommandAsync(request, requestMetadata, prefixedAdditionalTopicTokenMap, commandTimeout, cancellationToken); + + if (extended.Response.<#=propSpec.ReadErrorName.GetFieldName(TargetLanguage.CSharp)#> != null) + { + <#=propSpec.ReadErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception")#> <#=propSpec.ReadErrorSchema.GetVariableName(TargetLanguage.CSharp, "exception")#> = new <#=propSpec.ReadErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception")#>(extended.Response.<#=propSpec.ReadErrorName.GetFieldName(TargetLanguage.CSharp)#>); + throw <#=propSpec.ReadErrorSchema.GetVariableName(TargetLanguage.CSharp, "exception")#>; + } + else if (extended.Response.<#=propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp)#> == null) + { + throw new AkriMqttException("Property read response has neither normal nor error payload content") + { + Kind = AkriMqttErrorKind.PayloadInvalid, + IsShallow = false, + IsRemote = false, + }; + } + else + { + return new ExtendedResponse<<#=this.SchemaType(propSpec.PropSchema, propSpec.ReadSerializerEmptyType)#>> + { +<# if (propSpec.IsAggregate) { #> + Response = extended.Response.<#=propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp)#>, +<# } else { #> + Response = new <#=this.SchemaType(propSpec.PropSchema, propSpec.ReadSerializerEmptyType)#> { <#=propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp)#> = extended.Response.<#=propSpec.PropValueName.GetFieldName(TargetLanguage.CSharp)#>.Value() }, +<# } #> + ResponseMetadata = extended.ResponseMetadata, + }; + } + } +<# } #> +<# if (propSpec.WriteReqSchema != null && propSpec.WriteErrorName != null) { #> + + private async Task>> <#=propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "write")#>(<#=propSpec.WriteReqSchema.GetTypeName(TargetLanguage.CSharp)#> request, CommandRequestMetadata? requestMetadata, Dictionary? prefixedAdditionalTopicTokenMap, TimeSpan? commandTimeout, CancellationToken cancellationToken) + { + ExtendedResponse<<#=this.SchemaType(propSpec.WriteRespSchema, propSpec.WriteSerializerEmptyType)#>> extended = await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester")#>.InvokeCommandAsync(request, requestMetadata, prefixedAdditionalTopicTokenMap, commandTimeout, cancellationToken); + + if (extended.Response.<#=propSpec.WriteErrorName.GetFieldName(TargetLanguage.CSharp)#> != null) + { + <#=propSpec.WriteErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception")#> <#=propSpec.WriteErrorSchema.GetVariableName(TargetLanguage.CSharp, "exception")#> = new <#=propSpec.WriteErrorSchema.GetTypeName(TargetLanguage.CSharp, "exception")#>(extended.Response.<#=propSpec.WriteErrorName.GetFieldName(TargetLanguage.CSharp)#>); + throw <#=propSpec.WriteErrorSchema.GetVariableName(TargetLanguage.CSharp, "exception")#>; + } + else + { + return new ExtendedResponse<<#=propSpec.WriteSerializerEmptyType.GetTypeName(TargetLanguage.CSharp)#>> + { + Response = <#=propSpec.WriteSerializerEmptyType.GetAllocator(TargetLanguage.CSharp)#>, + ResponseMetadata = extended.ResponseMetadata, + }; + } + } +<# } #> +<# } #> + + public async ValueTask DisposeAsync() + { +<# foreach (var actionSpec in this.actionSpecs) { #> + await this.<#=actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp)#>.DisposeAsync().ConfigureAwait(false); +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester")#>.DisposeAsync().ConfigureAwait(false); +<# if (propSpec.WriteReqSchema != null) { #> + await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester")#>.DisposeAsync().ConfigureAwait(false); +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + await this.<#=telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp)#>.DisposeAsync().ConfigureAwait(false); +<# } #> + } + + public async ValueTask DisposeAsync(bool disposing) + { +<# foreach (var actionSpec in this.actionSpecs) { #> + await this.<#=actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp)#>.DisposeAsync(disposing).ConfigureAwait(false); +<# } #> +<# foreach (var propSpec in this.propSpecs) { #> + await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester")#>.DisposeAsync(disposing).ConfigureAwait(false); +<# if (propSpec.WriteReqSchema != null) { #> + await this.<#=propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester")#>.DisposeAsync(disposing).ConfigureAwait(false); +<# } #> +<# } #> +<# foreach (var telemEnvoyInfo in this.eventSpec) { #> + await this.<#=telemEnvoyInfo.Receiver.GetVariableName(TargetLanguage.CSharp)#>.DisposeAsync(disposing).ConfigureAwait(false); +<# } #> + } + } +<# } #> + } +} +<#+ + private string IntLValue(ActionSpec actionSpec) => (actionSpec.ResponseSchema != null ? $"ExtendedResponse<{this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}> extended = " : $"CommandResponseMetadata? responseMetadata = "); + + private string IntRValue(ActionSpec actionSpec) => (actionSpec.ResponseSchema != null ? "Response = extended.Response, ResponseMetadata = extended.ResponseMetadata " : "ResponseMetadata = responseMetadata "); + + private string ExtRespType(ActionSpec actionSpec) => this.CondWrap(actionSpec.ResponseSchema != null ? $"ExtendedResponse<{this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}>" : "CommandResponseMetadata?"); + + private string EmptyResp(ActionSpec actionSpec) => this.CondFrom(actionSpec.ResponseSchema != null ? $"new ExtendedResponse<{this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}> {{ Response = new {this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}() }}" : "(CommandResponseMetadata?)new CommandResponseMetadata()"); + + private string CondWrap(string type) => $"Task<{type}>"; + + private string CondFrom(string res) => $"Task.FromResult({res})"; + + private string ReqParam(ActionSpec actionSpec) => actionSpec.RequestSchema != null ? $"{this.SchemaType(actionSpec.RequestSchema, actionSpec.SerializerEmptyType)} request, " : ""; + + private string ReqArgs(ActionSpec actionSpec, string reqVar) => actionSpec.RequestSchema != null ? $"{reqVar}.Request!, {reqVar}.RequestMetadata!" : $"{reqVar}.RequestMetadata!"; + + private string CallAsyncType(ActionSpec actionSpec) => $"RpcCallAsync<{this.SchemaType(actionSpec.NormalResultSchema ?? actionSpec.ResponseSchema, actionSpec.SerializerEmptyType)}>"; + + private string CallAsyncType(PropertySpec propSpec) => $"RpcCallAsync<{this.SchemaType(propSpec.PropSchema, propSpec.ReadSerializerEmptyType)}>"; + + private string IntMethod(ActionSpec actionSpec) => actionSpec.ErrorResultName != null ? actionSpec.Name.GetMethodName(TargetLanguage.CSharp, "int") : $"{actionSpec.Invoker.GetVariableName(TargetLanguage.CSharp)}.InvokeCommandAsync"; + + private string IntReadMethod(PropertySpec propSpec) => propSpec.ReadErrorName != null ? propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "read") : $"{propSpec.Name.GetVariableName(TargetLanguage.CSharp, "read", "requester")}.InvokeCommandAsync"; + + private string IntWriteMethod(PropertySpec propSpec) => propSpec.WriteErrorName != null ? propSpec.Name.GetMethodName(TargetLanguage.CSharp, "int", prefix: "write") : $"{propSpec.Name.GetVariableName(TargetLanguage.CSharp, "write", "requester")}.InvokeCommandAsync"; + + private string SchemaType(ITypeName schema, EmptyTypeName emptyType) => schema?.GetTypeName(TargetLanguage.CSharp) ?? emptyType.GetTypeName(TargetLanguage.CSharp); + + private string ExecParam(ActionSpec actionSpec) => actionSpec.DoesTargetExecutor ? "string executorId, " : ""; + + private string ReadMaintParam(PropertySpec propSpec) => propSpec.DoesReadTargetMaintainer ? "string maintainerId, " : ""; + + private string WriteMaintParam(PropertySpec propSpec) => propSpec.DoesWriteTargetMaintainer ? "string maintainerId, " : ""; + + private bool IsLast(ActionSpec actionSpec) => actionSpec.Name.AsGiven == this.actionSpecs.Last().Name.AsGiven && !this.propSpecs.Any(); + + private bool IsLast(PropertySpec propSpec) => propSpec.Name == null || propSpec.Name.AsGiven == this.propSpecs.Last().Name.AsGiven; + + private bool IsLast(EventSpec telemEnvoyInfo) => telemEnvoyInfo.Name.AsGiven == this.eventSpec.Last().Name.AsGiven; + + private string TelemMethodName(EventSpec telemEnvoyInfo, string prefix, string suffix = null) => ((telemEnvoyInfo.Schema is RawTypeName || telemEnvoyInfo.Schema is CustomTypeName) ? telemEnvoyInfo.Name : new CodeName()).GetMethodName(TargetLanguage.CSharp, "telemetry", suffix, prefix: prefix); + + private string GetInfoName(CodeName infoName) => infoName?.GetVariableName(TargetLanguage.CSharp) ?? "errorPayload"; + + private string GetInfoSchema(CodeName infoSchema) => infoSchema?.GetTypeName(TargetLanguage.CSharp) ?? "string"; +#> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandExecutor.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandExecutor.cs new file mode 100644 index 0000000000..5695e0dba1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandExecutor.cs @@ -0,0 +1,63 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustCommandExecutor : IEnvoyTemplateTransform + { + private readonly CodeName commandName; + private readonly CodeName componentName; + private readonly CodeName genNamespace; + private readonly EmptyTypeName serializerEmptyType; + private readonly ITypeName? reqSchema; + private readonly ITypeName? respSchema; + private readonly List normalResultFields; + private readonly List normalRequiredFields; + private readonly ITypeName? normalResultSchema; + private readonly CodeName? errorResultName; + private readonly CodeName? errorResultSchema; + private readonly bool isIdempotent; + private readonly string? serviceGroupId; + private readonly string topicPattern; + private readonly string srcSubdir; + + public RustCommandExecutor( + string commandName, + string componentName, + CodeName genNamespace, + EmptyTypeName serializerEmptyType, + ITypeName? reqSchema, + ITypeName? respSchema, + List normalResultFields, + List normalRequiredFields, + ITypeName? normalResultSchema, + CodeName? errorResultName, + CodeName? errorResultSchema, + bool isIdempotent, + string? serviceGroupId, + string topicPattern, + string srcSubdir) + { + this.commandName = new CodeName(commandName); + this.componentName = new CodeName(componentName); + this.genNamespace = genNamespace; + this.serializerEmptyType = serializerEmptyType; + this.reqSchema = reqSchema; + this.respSchema = respSchema; + this.normalResultFields = normalResultFields; + this.normalRequiredFields = normalRequiredFields; + this.normalResultSchema = normalResultSchema; + this.errorResultName = errorResultName; + this.errorResultSchema = errorResultSchema; + this.isIdempotent = isIdempotent; + this.serviceGroupId = serviceGroupId; + this.topicPattern = topicPattern; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandExecutorHeaders.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandExecutorHeaders.cs new file mode 100644 index 0000000000..c48e1f52ef --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandExecutorHeaders.cs @@ -0,0 +1,45 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustCommandExecutorHeaders : IEnvoyTemplateTransform + { + private readonly CodeName commandName; + private readonly CodeName componentName; + private readonly CodeName genNamespace; + private readonly CodeName errorCodeName; + private readonly CodeName errorCodeSchema; + private readonly CodeName? errorInfoName; + private readonly CodeName? errorInfoSchema; + List errorCodeValues; + private readonly string srcSubdir; + + public RustCommandExecutorHeaders( + string commandName, + string componentName, + CodeName genNamespace, + CodeName errorCodeName, + CodeName errorCodeSchema, + CodeName? errorInfoName, + CodeName? errorInfoSchema, + List errorCodeValues, + string srcSubdir) + { + this.commandName = new CodeName(commandName); + this.componentName = new CodeName(componentName); + this.genNamespace = genNamespace; + this.errorCodeName = errorCodeName; + this.errorCodeSchema = errorCodeSchema; + this.errorInfoName = errorInfoName; + this.errorInfoSchema = errorInfoSchema; + this.errorCodeValues = errorCodeValues; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.Rust, "headers")}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandInvoker.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandInvoker.cs new file mode 100644 index 0000000000..b98a9f9a48 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandInvoker.cs @@ -0,0 +1,60 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustCommandInvoker : IEnvoyTemplateTransform + { + private readonly CodeName commandName; + private readonly CodeName componentName; + private readonly CodeName genNamespace; + private readonly EmptyTypeName serializerEmptyType; + private readonly ITypeName? reqSchema; + private readonly ITypeName? respSchema; + private readonly List normalResultFields; + private readonly List normalRequiredFields; + private readonly ITypeName? normalResultSchema; + private readonly CodeName? errorResultName; + private readonly CodeName? errorResultSchema; + private readonly string topicPattern; + private readonly bool doesCommandTargetExecutor; + private readonly string srcSubdir; + + public RustCommandInvoker( + string commandName, + string componentName, + CodeName genNamespace, + EmptyTypeName serializerEmptyType, + ITypeName? reqSchema, + ITypeName? respSchema, + List normalResultFields, + List normalRequiredFields, + ITypeName? normalResultSchema, + CodeName? errorResultName, + CodeName? errorResultSchema, + string topicPattern, + bool doesCommandTargetExecutor, + string srcSubdir) + { + this.commandName = new CodeName(commandName); + this.componentName = new CodeName(componentName); + this.genNamespace = genNamespace; + this.serializerEmptyType = serializerEmptyType; + this.reqSchema = reqSchema; + this.respSchema = respSchema; + this.normalResultFields = normalResultFields; + this.normalRequiredFields = normalRequiredFields; + this.normalResultSchema = normalResultSchema; + this.errorResultName = errorResultName; + this.errorResultSchema = errorResultSchema; + this.topicPattern = topicPattern; + this.doesCommandTargetExecutor = doesCommandTargetExecutor; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandInvokerHeaders.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandInvokerHeaders.cs new file mode 100644 index 0000000000..4c6ad4c9bf --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/code/RustCommandInvokerHeaders.cs @@ -0,0 +1,45 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustCommandInvokerHeaders : IEnvoyTemplateTransform + { + private readonly CodeName commandName; + private readonly CodeName componentName; + private readonly CodeName genNamespace; + private readonly CodeName errorCodeName; + private readonly CodeName errorCodeSchema; + private readonly CodeName? errorInfoName; + private readonly CodeName? errorInfoSchema; + List errorCodeValues; + private readonly string srcSubdir; + + public RustCommandInvokerHeaders( + string commandName, + string componentName, + CodeName genNamespace, + CodeName errorCodeName, + CodeName errorCodeSchema, + CodeName? errorInfoName, + CodeName? errorInfoSchema, + List errorCodeValues, + string srcSubdir) + { + this.commandName = new CodeName(commandName); + this.componentName = new CodeName(componentName); + this.genNamespace = genNamespace; + this.errorCodeName = errorCodeName; + this.errorCodeSchema = errorCodeSchema; + this.errorInfoName = errorInfoName; + this.errorInfoSchema = errorInfoSchema; + this.errorCodeValues = errorCodeValues; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.Rust, "headers")}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutor.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutor.cs new file mode 100644 index 0000000000..2eaa9e24d1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutor.cs @@ -0,0 +1,519 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustCommandExecutor : RustCommandExecutorBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write(@"; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +"); + if (this.respSchema != null) { + this.Write("use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize;\r\n" + + ""); + } + this.Write("use azure_iot_operations_protocol::rpc_command;\r\n\r\n"); + if (this.reqSchema is CustomTypeName || this.respSchema is CustomTypeName) { + this.Write("use super::super::common_types::custom_payload::CustomPayload;\r\n"); + } + if (this.reqSchema == null || this.respSchema == null) { + this.Write("use super::super::common_types::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializerEmptyType.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("use super::super::common_types::options::CommandExecutorOptions;\r\n"); + if (this.reqSchema is CodeName) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.reqSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.reqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.respSchema is CodeName) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.errorResultName != null) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.normalResultSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.normalResultSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\nuse super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request"))); + this.Write(" =\r\n rpc_command::executor::Request<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.RequestType())); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ResponseType())); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response"))); + this.Write(" = rpc_command::executor::Response<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ResponseType())); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response", "builder", "error"))); + this.Write(" = rpc_command::executor::ResponseBuilderError;\r\n\r\n/// Builder for [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response"))); + this.Write("`]\r\n#[derive(Default)]\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response", "builder"))); + this.Write(" {\r\n inner_builder: rpc_command::executor::ResponseBuilder<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ResponseType())); + this.Write(">,\r\n}\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response", "builder"))); + this.Write(@" { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event(&mut self, cloud_event: Option) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + +"); + if (this.respSchema != null) { + this.Write(" /// Payload of the response\r\n ///\r\n /// # Errors\r\n /// If the payloa" + + "d cannot be serialized\r\n pub fn payload(\r\n &mut self,\r\n payload" + + ": "); + this.Write(this.ToStringHelper.ToStringWithCulture((this.normalResultSchema ?? this.respSchema).GetTypeName(TargetLanguage.Rust))); + this.Write(",\r\n ) -> Result<&mut Self, AIOProtocolError> {\r\n"); + if (this.errorResultName != null) { + this.Write(" self.inner_builder.payload("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n"); + foreach (CodeName normalResultField in this.normalResultFields) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultField.GetFieldName(TargetLanguage.Rust))); + this.Write(": "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.CondSome($"payload.{normalResultField.GetFieldName(TargetLanguage.Rust)}", this.normalRequiredFields.Contains(normalResultField)))); + this.Write(",\r\n"); + } + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultName.GetFieldName(TargetLanguage.Rust))); + this.Write(": None,\r\n })?;\r\n"); + } else { + this.Write(" self.inner_builder.payload(payload)?;\r\n"); + } + this.Write(" Ok(self)\r\n }\r\n\r\n"); + if (this.errorResultName != null) { + this.Write(" pub fn error(&mut self, error: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(") -> Result<&mut Self, AIOProtocolError> {\r\n self.inner_builder.payload("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n"); + foreach (CodeName normalResultField in this.normalResultFields) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultField.GetFieldName(TargetLanguage.Rust))); + this.Write(": None,\r\n"); + } + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultName.GetFieldName(TargetLanguage.Rust))); + this.Write(": Some(error),\r\n })?;\r\n Ok(self)\r\n }\r\n\r\n"); + } + } + this.Write(" /// Builds a new `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response"))); + this.Write("`\r\n ///\r\n /// # Errors\r\n /// If a required field has not been initialize" + + "d\r\n #[allow(clippy::missing_panics_doc)] // The panic is not possible\r\n pu" + + "b fn build(&mut self) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response"))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response", "builder", "error"))); + this.Write("> {\r\n"); + if (this.respSchema == null) { + this.Write(" self.inner_builder.payload("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializerEmptyType.GetAllocator(TargetLanguage.Rust))); + this.Write(").unwrap();\r\n\r\n"); + } + this.Write(" self.inner_builder.build()\r\n }\r\n}\r\n\r\n/// Command Executor for `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.AsGiven)); + this.Write("`\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("(\r\n rpc_command::Executor<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.RequestType())); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ResponseType())); + this.Write(">,\r\n);\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("\r\n{\r\n /// Creates a new [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write(@"`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandExecutorOptions) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!(""ex:{k}""), v)) + .collect(); + + topic_token_map.insert("""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.ActionExecutorId)); + this.Write("\".to_string(), client.client_id().to_string());\r\n\r\n let executor_options =" + + " executor_options_builder\r\n .request_topic_pattern(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.topicPattern)); + this.Write("\")\r\n .command_name(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.AsGiven)); + this.Write("\")\r\n .is_idempotent("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.isIdempotent ? "true" : "false")); + this.Write(")\r\n .topic_token_map(topic_token_map)\r\n"); + if (this.serviceGroupId != null) { + this.Write(" .service_group_id(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceGroupId)); + this.Write("\".to_string())\r\n"); + } + this.Write(@" .build() + .expect(""DTDL schema generated invalid arguments""); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect(""DTDL schema generated invalid arguments""), + ) + } + + /// Receive the next [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request"))); + this.Write("`] or [`None`] if there will be no more requests\r\n ///\r\n /// # Errors\r\n " + + "/// [`AIOProtocolError`] if there is a failure receiving a request\r\n pub asyn" + + "c fn recv(&mut self) -> Option> {\r\n self.0.recv().await\r\n }\r\n\r\n /// Shutdown th" + + "e [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write(@"`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +"); + return this.GenerationEnvironment.ToString(); + } + + private string RequestType() => this.reqSchema?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); + + private string ResponseType() => this.respSchema?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); + + private string CondSome(string expr, bool doWrap) => doWrap ? $"Some({expr})" : expr; + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustCommandExecutorBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutor.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutor.tt new file mode 100644 index 0000000000..3c182fc725 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutor.tt @@ -0,0 +1,172 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +<# if (this.respSchema != null) { #> +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +<# } #> +use azure_iot_operations_protocol::rpc_command; + +<# if (this.reqSchema is CustomTypeName || this.respSchema is CustomTypeName) { #> +use super::super::common_types::custom_payload::CustomPayload; +<# } #> +<# if (this.reqSchema == null || this.respSchema == null) { #> +use super::super::common_types::<#=this.serializerEmptyType.GetFileName(TargetLanguage.Rust)#>::<#=this.serializerEmptyType.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +use super::super::common_types::options::CommandExecutorOptions; +<# if (this.reqSchema is CodeName) { #> +use super::<#=this.reqSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.reqSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.respSchema is CodeName) { #> +use super::<#=this.respSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.respSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.errorResultName != null) { #> +use super::<#=this.normalResultSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.normalResultSchema.GetTypeName(TargetLanguage.Rust)#>; +use super::<#=this.errorResultSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.errorResultSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> + +pub type <#=this.commandName.GetTypeName(TargetLanguage.Rust, "request")#> = + rpc_command::executor::Request<<#=this.RequestType()#>, <#=this.ResponseType()#>>; +pub type <#=this.commandName.GetTypeName(TargetLanguage.Rust, "response")#> = rpc_command::executor::Response<<#=this.ResponseType()#>>; +pub type <#=this.commandName.GetTypeName(TargetLanguage.Rust, "response", "builder", "error")#> = rpc_command::executor::ResponseBuilderError; + +/// Builder for [`<#=this.commandName.GetTypeName(TargetLanguage.Rust, "response")#>`] +#[derive(Default)] +pub struct <#=this.commandName.GetTypeName(TargetLanguage.Rust, "response", "builder")#> { + inner_builder: rpc_command::executor::ResponseBuilder<<#=this.ResponseType()#>>, +} + +impl <#=this.commandName.GetTypeName(TargetLanguage.Rust, "response", "builder")#> { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event(&mut self, cloud_event: Option) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + +<# if (this.respSchema != null) { #> + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: <#=(this.normalResultSchema ?? this.respSchema).GetTypeName(TargetLanguage.Rust)#>, + ) -> Result<&mut Self, AIOProtocolError> { +<# if (this.errorResultName != null) { #> + self.inner_builder.payload(<#=this.respSchema.GetTypeName(TargetLanguage.Rust)#> { +<# foreach (CodeName normalResultField in this.normalResultFields) { #> + <#=normalResultField.GetFieldName(TargetLanguage.Rust)#>: <#=this.CondSome($"payload.{normalResultField.GetFieldName(TargetLanguage.Rust)}", this.normalRequiredFields.Contains(normalResultField))#>, +<# } #> + <#=this.errorResultName.GetFieldName(TargetLanguage.Rust)#>: None, + })?; +<# } else { #> + self.inner_builder.payload(payload)?; +<# } #> + Ok(self) + } + +<# if (this.errorResultName != null) { #> + pub fn error(&mut self, error: <#=this.errorResultSchema.GetTypeName(TargetLanguage.Rust)#>) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(<#=this.respSchema.GetTypeName(TargetLanguage.Rust)#> { +<# foreach (CodeName normalResultField in this.normalResultFields) { #> + <#=normalResultField.GetFieldName(TargetLanguage.Rust)#>: None, +<# } #> + <#=this.errorResultName.GetFieldName(TargetLanguage.Rust)#>: Some(error), + })?; + Ok(self) + } + +<# } #> +<# } #> + /// Builds a new `<#=this.commandName.GetTypeName(TargetLanguage.Rust, "response")#>` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build(&mut self) -> Result<<#=this.commandName.GetTypeName(TargetLanguage.Rust, "response")#>, <#=this.commandName.GetTypeName(TargetLanguage.Rust, "response", "builder", "error")#>> { +<# if (this.respSchema == null) { #> + self.inner_builder.payload(<#=this.serializerEmptyType.GetAllocator(TargetLanguage.Rust)#>).unwrap(); + +<# } #> + self.inner_builder.build() + } +} + +/// Command Executor for `<#=this.commandName.AsGiven#>` +pub struct <#=this.componentName.GetTypeName(TargetLanguage.Rust)#>( + rpc_command::Executor<<#=this.RequestType()#>, <#=this.ResponseType()#>>, +); + +impl <#=this.componentName.GetTypeName(TargetLanguage.Rust)#> +{ + /// Creates a new [`<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandExecutorOptions) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("<#=MqttTopicTokens.ActionExecutorId#>".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("<#=this.topicPattern#>") + .command_name("<#=this.commandName.AsGiven#>") + .is_idempotent(<#=this.isIdempotent ? "true" : "false"#>) + .topic_token_map(topic_token_map) +<# if (this.serviceGroupId != null) { #> + .service_group_id("<#=this.serviceGroupId#>".to_string()) +<# } #> + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`<#=this.commandName.GetTypeName(TargetLanguage.Rust, "request")#>`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv(&mut self) -> Option, AIOProtocolError>> { + self.0.recv().await + } + + /// Shutdown the [`<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +<#+ + private string RequestType() => this.reqSchema?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); + + private string ResponseType() => this.respSchema?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); + + private string CondSome(string expr, bool doWrap) => doWrap ? $"Some({expr})" : expr; +#> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutorHeaders.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutorHeaders.cs new file mode 100644 index 0000000000..092d713631 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutorHeaders.cs @@ -0,0 +1,352 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustCommandExecutorHeaders : RustCommandExecutorHeadersBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\nuse azure_iot_operations_mqtt::session::SessionManagedClient" + + ";\r\nuse serde_json;\r\n\r\nuse super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + if (this.errorInfoSchema != null) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("pub use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n\r\nuse azure_iot_operations_protocol::rpc_command::executor;\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("\r\n{\r\n pub fn application_error_headers(\r\n custom_user_data: &mut Vec<(S" + + "tring, String)>,\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeName.GetVariableName(TargetLanguage.Rust))); + this.Write(": "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(",\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoName?.GetVariableName(TargetLanguage.Rust) ?? "error_payload")); + this.Write(": "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoSchema?.GetTypeName(TargetLanguage.Rust) ?? "String")); + this.Write(",\r\n ) -> Result<(), String> {\r\n let error_code = match "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeName.GetVariableName(TargetLanguage.Rust))); + this.Write(" {\r\n"); + foreach (string codeValue in this.errorCodeValues) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeSchema.GetTypeName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(codeValue)); + this.Write(" => \""); + this.Write(this.ToStringHelper.ToStringWithCulture(codeValue)); + this.Write("\".to_string(),\r\n"); + } + this.Write(" };\r\n\r\n"); + if (this.errorInfoSchema != null) { + this.Write(" let error_payload = serde_json::to_string(&"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoName.GetVariableName(TargetLanguage.Rust))); + this.Write(").unwrap_or_default();\r\n\r\n"); + } + this.Write(" executor::application_error_headers(custom_user_data, error_code, error_p" + + "ayload)\r\n }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustCommandExecutorHeadersBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutorHeaders.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutorHeaders.tt new file mode 100644 index 0000000000..ed7a96c52a --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandExecutorHeaders.tt @@ -0,0 +1,36 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use serde_json; + +use super::<#=this.errorCodeSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.errorCodeSchema.GetTypeName(TargetLanguage.Rust)#>; +<# if (this.errorInfoSchema != null) { #> +use super::<#=this.errorInfoSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.errorInfoSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +pub use super::<#=this.componentName.GetFileName(TargetLanguage.Rust)#>::<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>; + +use azure_iot_operations_protocol::rpc_command::executor; + +impl <#=this.componentName.GetTypeName(TargetLanguage.Rust)#> +{ + pub fn application_error_headers( + custom_user_data: &mut Vec<(String, String)>, + <#=this.errorCodeName.GetVariableName(TargetLanguage.Rust)#>: <#=this.errorCodeSchema.GetTypeName(TargetLanguage.Rust)#>, + <#=this.errorInfoName?.GetVariableName(TargetLanguage.Rust) ?? "error_payload"#>: <#=this.errorInfoSchema?.GetTypeName(TargetLanguage.Rust) ?? "String"#>, + ) -> Result<(), String> { + let error_code = match <#=this.errorCodeName.GetVariableName(TargetLanguage.Rust)#> { +<# foreach (string codeValue in this.errorCodeValues) { #> + <#=this.errorCodeSchema.GetTypeName(TargetLanguage.Rust)#>::<#=codeValue#> => "<#=codeValue#>".to_string(), +<# } #> + }; + +<# if (this.errorInfoSchema != null) { #> + let error_payload = serde_json::to_string(&<#=this.errorInfoName.GetVariableName(TargetLanguage.Rust)#>).unwrap_or_default(); + +<# } #> + executor::application_error_headers(custom_user_data, error_code, error_payload) + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvoker.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvoker.cs new file mode 100644 index 0000000000..a6eefb47ee --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvoker.cs @@ -0,0 +1,617 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustCommandInvoker : RustCommandInvokerBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\nuse std::collections::HashMap;\r\n"); + if (this.errorResultName != null) { + this.Write("use std::error::Error;\r\n"); + } + this.Write("use std::time::Duration;\r\n\r\nuse azure_iot_operations_mqtt::session::SessionManage" + + "dClient;\r\nuse azure_iot_operations_protocol::application::ApplicationContext;\r\nu" + + "se azure_iot_operations_protocol::common::aio_protocol_error::{\r\n AIOProtocol" + + "Error,\r\n"); + if (this.errorResultName != null) { + this.Write(" AIOProtocolErrorKind,\r\n"); + } + this.Write("};\r\n"); + if (this.reqSchema != null) { + this.Write("use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize;\r\n" + + ""); + } + this.Write("use azure_iot_operations_protocol::rpc_command;\r\n\r\n"); + if (this.reqSchema is CustomTypeName || this.respSchema is CustomTypeName) { + this.Write("use super::super::common_types::custom_payload::CustomPayload;\r\n"); + } + if (this.reqSchema == null || this.respSchema == null) { + this.Write("use super::super::common_types::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializerEmptyType.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("use super::super::common_types::options::CommandInvokerOptions;\r\n"); + if (this.reqSchema is CodeName) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.reqSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.reqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.respSchema is CodeName) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.respSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.errorResultName != null) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.normalResultSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.normalResultSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\nuse super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request"))); + this.Write(" = rpc_command::invoker::Request<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.RequestType())); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response"))); + this.Write(" = rpc_command::invoker::Response<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.InternalResponseType())); + this.Write(">;\r\n"); + if (this.errorResultSchema != null) { + this.Write("pub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response", "error"))); + this.Write(" = rpc_command::invoker::Response<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\n"); + } + this.Write("pub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder", "error"))); + this.Write(" = rpc_command::invoker::RequestBuilderError;\r\n\r\n#[derive(Default)]\r\n/// Builder " + + "for [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request"))); + this.Write("`]\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder"))); + this.Write(" {\r\n inner_builder: rpc_command::invoker::RequestBuilder<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.RequestType())); + this.Write(">,\r\n"); + if (this.doesCommandTargetExecutor) { + this.Write(" set_executor_id: bool,\r\n"); + } + this.Write(" topic_tokens: HashMap,\r\n}\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder"))); + this.Write(@" { + /// Custom user data to set on the request + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the request + pub fn cloud_event(&mut self, cloud_event: Option) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Topic token keys/values to be replaced into the publish topic of the request message. + /// A prefix of ""ex:"" will be prepended to each key before scanning the topic pattern. + /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced. + pub fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self { + for (k, v) in topic_tokens { + self.topic_tokens.insert(format!(""ex:{k}""), v); + } + self + } + + /// Timeout for the request + pub fn timeout(&mut self, timeout: Duration) -> &mut Self { + self.inner_builder.timeout(timeout); + self + } + +"); + if (this.doesCommandTargetExecutor) { + this.Write(@" /// Target executor ID + pub fn executor_id(&mut self, executor_id: &str) -> &mut Self { + self.topic_tokens + .insert(""executorId"".to_string(), executor_id.to_string()); + self.set_executor_id = true; + self + } + +"); + } + if (this.reqSchema != null) { + this.Write(" /// Payload of the request\r\n ///\r\n /// # Errors\r\n /// If the payload" + + " cannot be serialized\r\n pub fn payload(\r\n &mut self,\r\n payload:" + + " "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.reqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(",\r\n ) -> Result<&mut Self, AIOProtocolError> {\r\n self.inner_builder.pay" + + "load(payload)?;\r\n Ok(self)\r\n }\r\n\r\n"); + } + this.Write(" /// Builds a new `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request"))); + this.Write("`\r\n ///\r\n /// # Errors\r\n /// If a required field has not been initialize" + + "d\r\n #[allow(clippy::missing_panics_doc)] // The panic is not possible\r\n pu" + + "b fn build(&mut self) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request"))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder", "error"))); + this.Write("> {\r\n"); + if (this.doesCommandTargetExecutor) { + this.Write(" if !self.set_executor_id {\r\n return Err("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder", "error"))); + this.Write("::UninitializedField(\r\n \"executor_id\",\r\n ));\r\n }" + + "\r\n\r\n"); + } + if (this.reqSchema == null) { + this.Write(" self.inner_builder.payload("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializerEmptyType.GetAllocator(TargetLanguage.Rust))); + this.Write(").unwrap();\r\n\r\n"); + } + this.Write(" self.inner_builder.topic_tokens(self.topic_tokens.clone());\r\n\r\n se" + + "lf.inner_builder.build()\r\n }\r\n}\r\n\r\n/// Command Invoker for `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.AsGiven)); + this.Write("`\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("(\r\n rpc_command::Invoker<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.RequestType())); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.ExternalResponseType())); + this.Write(">,\r\n);\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("\r\n{\r\n /// Creates a new [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write(@"`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandInvokerOptions) -> Self { + let mut invoker_options_builder = rpc_command::invoker::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + invoker_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!(""ex:{k}""), v)) + .collect(); + + topic_token_map.insert("""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.ActionInvokerId)); + this.Write("\".to_string(), client.client_id().to_string());\r\n\r\n let invoker_options = " + + "invoker_options_builder\r\n .request_topic_pattern(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.topicPattern)); + this.Write("\")\r\n .command_name(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.AsGiven)); + this.Write(@""") + .topic_token_map(topic_token_map) + .response_topic_prefix(options.response_topic_prefix.clone()) + .response_topic_suffix(options.response_topic_suffix.clone()) + .build() + .expect(""DTDL schema generated invalid arguments""); + + Self( + rpc_command::Invoker::new(application_context, client, invoker_options) + .expect(""DTDL schema generated invalid arguments""), + ) + } + + /// Invokes the [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request"))); + this.Write("`]\r\n ///\r\n /// # Errors\r\n /// [`AIOProtocolError`] if there is a failure" + + " invoking the request\r\n pub async fn invoke(\r\n &self,\r\n request" + + ": "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "request"))); + this.Write(",\r\n"); + if (this.errorResultName != null) { + this.Write(" ) -> Result, AIOProtocolError> {\r\n let response = self.0.invoke(request).await;\r\n " + + " match response {\r\n Ok(response) => {\r\n if let Som" + + "e("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultName.GetVariableName(TargetLanguage.Rust))); + this.Write(") = response.payload."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultName.GetFieldName(TargetLanguage.Rust))); + this.Write(" {\r\n Ok(Err("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response", "error"))); + this.Write(" {\r\n payload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorResultName.GetVariableName(TargetLanguage.Rust))); + this.Write(@", + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } else { + Ok(Ok("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response"))); + this.Write(" {\r\n payload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.normalResultSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n"); + foreach (CodeName normalResultField in this.normalResultFields) { + if (this.normalRequiredFields.Contains(normalResultField)) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultField.GetFieldName(TargetLanguage.Rust))); + this.Write(": response.payload."); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultField.GetFieldName(TargetLanguage.Rust))); + this.Write(".ok_or("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("::get_err(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultField.AsGiven)); + this.Write("\"))?,\r\n"); + } else { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultField.GetFieldName(TargetLanguage.Rust))); + this.Write(": response.payload."); + this.Write(this.ToStringHelper.ToStringWithCulture(normalResultField.GetFieldName(TargetLanguage.Rust))); + this.Write(",\r\n"); + } + } + this.Write(@" }, + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } + } + Err(err) => Err(err), + } +"); + } else { + this.Write(" ) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.GetTypeName(TargetLanguage.Rust, "response"))); + this.Write(", AIOProtocolError> {\r\n self.0.invoke(request).await\r\n"); + } + this.Write(" }\r\n\r\n /// Shutdown the [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write(@"`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +"); + if (this.errorResultName != null && this.normalRequiredFields.Any()) { + this.Write(@" + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!(""Command response missing field {field_name}"")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandName.AsGiven)); + this.Write("\".to_string()),\r\n protocol_version: None,\r\n supported_proto" + + "col_major_versions: None,\r\n }\r\n }\r\n"); + } + this.Write("}\r\n"); + return this.GenerationEnvironment.ToString(); + } + + private string RequestType() => this.reqSchema?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); + + private string ExternalResponseType() => this.respSchema?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); + + private string InternalResponseType() => (this.normalResultSchema ?? this.respSchema)?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustCommandInvokerBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvoker.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvoker.tt new file mode 100644 index 0000000000..b6a82e2ef4 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvoker.tt @@ -0,0 +1,262 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use std::collections::HashMap; +<# if (this.errorResultName != null) { #> +use std::error::Error; +<# } #> +use std::time::Duration; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::{ + AIOProtocolError, +<# if (this.errorResultName != null) { #> + AIOProtocolErrorKind, +<# } #> +}; +<# if (this.reqSchema != null) { #> +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +<# } #> +use azure_iot_operations_protocol::rpc_command; + +<# if (this.reqSchema is CustomTypeName || this.respSchema is CustomTypeName) { #> +use super::super::common_types::custom_payload::CustomPayload; +<# } #> +<# if (this.reqSchema == null || this.respSchema == null) { #> +use super::super::common_types::<#=this.serializerEmptyType.GetFileName(TargetLanguage.Rust)#>::<#=this.serializerEmptyType.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +use super::super::common_types::options::CommandInvokerOptions; +<# if (this.reqSchema is CodeName) { #> +use super::<#=this.reqSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.reqSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.respSchema is CodeName) { #> +use super::<#=this.respSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.respSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.errorResultName != null) { #> +use super::<#=this.normalResultSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.normalResultSchema.GetTypeName(TargetLanguage.Rust)#>; +use super::<#=this.errorResultSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.errorResultSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> + +pub type <#=this.commandName.GetTypeName(TargetLanguage.Rust, "request")#> = rpc_command::invoker::Request<<#=this.RequestType()#>>; +pub type <#=this.commandName.GetTypeName(TargetLanguage.Rust, "response")#> = rpc_command::invoker::Response<<#=this.InternalResponseType()#>>; +<# if (this.errorResultSchema != null) { #> +pub type <#=this.commandName.GetTypeName(TargetLanguage.Rust, "response", "error")#> = rpc_command::invoker::Response<<#=this.errorResultSchema.GetTypeName(TargetLanguage.Rust)#>>; +<# } #> +pub type <#=this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder", "error")#> = rpc_command::invoker::RequestBuilderError; + +#[derive(Default)] +/// Builder for [`<#=this.commandName.GetTypeName(TargetLanguage.Rust, "request")#>`] +pub struct <#=this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder")#> { + inner_builder: rpc_command::invoker::RequestBuilder<<#=this.RequestType()#>>, +<# if (this.doesCommandTargetExecutor) { #> + set_executor_id: bool, +<# } #> + topic_tokens: HashMap, +} + +impl <#=this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder")#> { + /// Custom user data to set on the request + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the request + pub fn cloud_event(&mut self, cloud_event: Option) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Topic token keys/values to be replaced into the publish topic of the request message. + /// A prefix of "ex:" will be prepended to each key before scanning the topic pattern. + /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced. + pub fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self { + for (k, v) in topic_tokens { + self.topic_tokens.insert(format!("ex:{k}"), v); + } + self + } + + /// Timeout for the request + pub fn timeout(&mut self, timeout: Duration) -> &mut Self { + self.inner_builder.timeout(timeout); + self + } + +<# if (this.doesCommandTargetExecutor) { #> + /// Target executor ID + pub fn executor_id(&mut self, executor_id: &str) -> &mut Self { + self.topic_tokens + .insert("executorId".to_string(), executor_id.to_string()); + self.set_executor_id = true; + self + } + +<# } #> +<# if (this.reqSchema != null) { #> + /// Payload of the request + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: <#=this.reqSchema.GetTypeName(TargetLanguage.Rust)#>, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(payload)?; + Ok(self) + } + +<# } #> + /// Builds a new `<#=this.commandName.GetTypeName(TargetLanguage.Rust, "request")#>` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build(&mut self) -> Result<<#=this.commandName.GetTypeName(TargetLanguage.Rust, "request")#>, <#=this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder", "error")#>> { +<# if (this.doesCommandTargetExecutor) { #> + if !self.set_executor_id { + return Err(<#=this.commandName.GetTypeName(TargetLanguage.Rust, "request", "builder", "error")#>::UninitializedField( + "executor_id", + )); + } + +<# } #> +<# if (this.reqSchema == null) { #> + self.inner_builder.payload(<#=this.serializerEmptyType.GetAllocator(TargetLanguage.Rust)#>).unwrap(); + +<# } #> + self.inner_builder.topic_tokens(self.topic_tokens.clone()); + + self.inner_builder.build() + } +} + +/// Command Invoker for `<#=this.commandName.AsGiven#>` +pub struct <#=this.componentName.GetTypeName(TargetLanguage.Rust)#>( + rpc_command::Invoker<<#=this.RequestType()#>, <#=this.ExternalResponseType()#>>, +); + +impl <#=this.componentName.GetTypeName(TargetLanguage.Rust)#> +{ + /// Creates a new [`<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandInvokerOptions) -> Self { + let mut invoker_options_builder = rpc_command::invoker::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + invoker_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("<#=MqttTopicTokens.ActionInvokerId#>".to_string(), client.client_id().to_string()); + + let invoker_options = invoker_options_builder + .request_topic_pattern("<#=this.topicPattern#>") + .command_name("<#=this.commandName.AsGiven#>") + .topic_token_map(topic_token_map) + .response_topic_prefix(options.response_topic_prefix.clone()) + .response_topic_suffix(options.response_topic_suffix.clone()) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Invoker::new(application_context, client, invoker_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Invokes the [`<#=this.commandName.GetTypeName(TargetLanguage.Rust, "request")#>`] + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure invoking the request + pub async fn invoke( + &self, + request: <#=this.commandName.GetTypeName(TargetLanguage.Rust, "request")#>, +<# if (this.errorResultName != null) { #> + ) -> Result, <#=this.commandName.GetTypeName(TargetLanguage.Rust, "response", "error")#>>, AIOProtocolError> { + let response = self.0.invoke(request).await; + match response { + Ok(response) => { + if let Some(<#=this.errorResultName.GetVariableName(TargetLanguage.Rust)#>) = response.payload.<#=this.errorResultName.GetFieldName(TargetLanguage.Rust)#> { + Ok(Err(<#=this.commandName.GetTypeName(TargetLanguage.Rust, "response", "error")#> { + payload: <#=this.errorResultName.GetVariableName(TargetLanguage.Rust)#>, + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } else { + Ok(Ok(<#=this.commandName.GetTypeName(TargetLanguage.Rust, "response")#> { + payload: <#=this.normalResultSchema.GetTypeName(TargetLanguage.Rust)#> { +<# foreach (CodeName normalResultField in this.normalResultFields) { #> +<# if (this.normalRequiredFields.Contains(normalResultField)) { #> + <#=normalResultField.GetFieldName(TargetLanguage.Rust)#>: response.payload.<#=normalResultField.GetFieldName(TargetLanguage.Rust)#>.ok_or(<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>::get_err("<#=normalResultField.AsGiven#>"))?, +<# } else { #> + <#=normalResultField.GetFieldName(TargetLanguage.Rust)#>: response.payload.<#=normalResultField.GetFieldName(TargetLanguage.Rust)#>, +<# } #> +<# } #> + }, + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } + } + Err(err) => Err(err), + } +<# } else { #> + ) -> Result<<#=this.commandName.GetTypeName(TargetLanguage.Rust, "response")#>, AIOProtocolError> { + self.0.invoke(request).await +<# } #> + } + + /// Shutdown the [`<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +<# if (this.errorResultName != null && this.normalRequiredFields.Any()) { #> + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("<#=this.commandName.AsGiven#>".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } +<# } #> +} +<#+ + private string RequestType() => this.reqSchema?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); + + private string ExternalResponseType() => this.respSchema?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); + + private string InternalResponseType() => (this.normalResultSchema ?? this.respSchema)?.GetTypeName(TargetLanguage.Rust) ?? this.serializerEmptyType.GetTypeName(TargetLanguage.Rust); +#> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvokerHeaders.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvokerHeaders.cs new file mode 100644 index 0000000000..0668accaef --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvokerHeaders.cs @@ -0,0 +1,355 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustCommandInvokerHeaders : RustCommandInvokerHeadersBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\nuse azure_iot_operations_mqtt::session::SessionManagedClient" + + ";\r\nuse serde_json;\r\n\r\n"); + if (this.errorInfoSchema != null) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\npub use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n\r\nuse azure_iot_operations_protocol::rpc_command::invoker;\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("\r\n{\r\n pub fn application_error_headers(\r\n custom_user_data: &Vec<(Strin" + + "g, String)>,\r\n ) -> (Option<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">, Option<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoSchema?.GetTypeName(TargetLanguage.Rust) ?? "String")); + this.Write(">) {\r\n let (error_code, error_payload) = invoker::application_error_header" + + "s(custom_user_data);\r\n\r\n let "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeName.GetVariableName(TargetLanguage.Rust))); + this.Write(" = match error_code.as_deref() {\r\n"); + foreach (string codeValue in this.errorCodeValues) { + this.Write(" Some(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(codeValue)); + this.Write("\") => Some("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeSchema.GetTypeName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(codeValue)); + this.Write("),\r\n"); + } + this.Write(" Some(_) => None,\r\n None => None,\r\n };\r\n\r\n"); + if (this.errorInfoSchema != null) { + this.Write(" let "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoName.GetVariableName(TargetLanguage.Rust))); + this.Write(" = if let Some(error_payload) = error_payload {\r\n serde_json::from_str" + + "::<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">(&error_payload).ok()\r\n } else {\r\n None\r\n };\r\n\r\n"); + } + this.Write(" ("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorCodeName.GetVariableName(TargetLanguage.Rust))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorInfoName?.GetVariableName(TargetLanguage.Rust) ?? "error_payload")); + this.Write(")\r\n }\r\n}\r\n\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustCommandInvokerHeadersBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvokerHeaders.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvokerHeaders.tt new file mode 100644 index 0000000000..d3fcd3bb7d --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Actions/t4/RustCommandInvokerHeaders.tt @@ -0,0 +1,43 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use serde_json; + +<# if (this.errorInfoSchema != null) { #> +use super::<#=this.errorInfoSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.errorInfoSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +use super::<#=this.errorCodeSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.errorCodeSchema.GetTypeName(TargetLanguage.Rust)#>; +pub use super::<#=this.componentName.GetFileName(TargetLanguage.Rust)#>::<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>; + +use azure_iot_operations_protocol::rpc_command::invoker; + +impl <#=this.componentName.GetTypeName(TargetLanguage.Rust)#> +{ + pub fn application_error_headers( + custom_user_data: &Vec<(String, String)>, + ) -> (Option<<#=this.errorCodeSchema.GetTypeName(TargetLanguage.Rust)#>>, Option<<#=this.errorInfoSchema?.GetTypeName(TargetLanguage.Rust) ?? "String"#>>) { + let (error_code, error_payload) = invoker::application_error_headers(custom_user_data); + + let <#=this.errorCodeName.GetVariableName(TargetLanguage.Rust)#> = match error_code.as_deref() { +<# foreach (string codeValue in this.errorCodeValues) { #> + Some("<#=codeValue#>") => Some(<#=this.errorCodeSchema.GetTypeName(TargetLanguage.Rust)#>::<#=codeValue#>), +<# } #> + Some(_) => None, + None => None, + }; + +<# if (this.errorInfoSchema != null) { #> + let <#=this.errorInfoName.GetVariableName(TargetLanguage.Rust)#> = if let Some(error_payload) = error_payload { + serde_json::from_str::<<#=this.errorInfoSchema.GetTypeName(TargetLanguage.Rust)#>>(&error_payload).ok() + } else { + None + }; + +<# } #> + (<#=this.errorCodeName.GetVariableName(TargetLanguage.Rust)#>, <#=this.errorInfoName?.GetVariableName(TargetLanguage.Rust) ?? "error_payload"#>) + } +} + diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Constants/code/RustConstants.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Constants/code/RustConstants.cs new file mode 100644 index 0000000000..69c9f5d1e8 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Constants/code/RustConstants.cs @@ -0,0 +1,51 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustConstants : IEnvoyTemplateTransform + { + private readonly CodeName schemaName; + private readonly CodeName genNamespace; + private readonly ConstantsSpec constantSpec; + private readonly string srcSubdir; + + public RustConstants(CodeName schemaName, CodeName genNamespace, ConstantsSpec constantSpec, string srcSubdir) + { + this.schemaName = schemaName; + this.genNamespace = genNamespace; + this.constantSpec = constantSpec; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.schemaName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + + private static string GetRustType(string type) + { + return type switch + { + TDValues.TypeString => "&str", + TDValues.TypeNumber => "f64", + TDValues.TypeInteger => "i32", + TDValues.TypeBoolean => "bool", + _ => throw new System.ArgumentException($"Unsupported constant type: {type}"), + }; + } + + private static string GetRustValue(object value) + { + return value switch + { + string s => $"\"{s}\"", + double d => d.ToString(CultureInfo.InvariantCulture), + int i => i.ToString(CultureInfo.InvariantCulture), + bool b => b ? "true" : "false", + _ => throw new System.ArgumentException($"Unsupported constant value type: {value.GetType()}"), + }; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Constants/t4/RustConstants.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Constants/t4/RustConstants.cs new file mode 100644 index 0000000000..012d43b69b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Constants/t4/RustConstants.cs @@ -0,0 +1,325 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustConstants : RustConstantsBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n"); + if (this.constantSpec.Description != null) { + this.Write("\r\n// "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.constantSpec.Description)); + this.Write("\r\n"); + } + foreach (KeyValuePair constant in this.constantSpec.Constants) { + this.Write("\r\n"); + if (constant.Value.Description != null) { + this.Write("/// "); + this.Write(this.ToStringHelper.ToStringWithCulture(constant.Value.Description)); + this.Write("\r\n"); + } + this.Write("pub const "); + this.Write(this.ToStringHelper.ToStringWithCulture(constant.Key.GetConstantName(TargetLanguage.Rust))); + this.Write(": "); + this.Write(this.ToStringHelper.ToStringWithCulture(GetRustType(constant.Value.Type))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(GetRustValue(constant.Value.Value))); + this.Write(";\r\n"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustConstantsBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Constants/t4/RustConstants.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Constants/t4/RustConstants.tt new file mode 100644 index 0000000000..2d7cc32b66 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Constants/t4/RustConstants.tt @@ -0,0 +1,15 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ +<# if (this.constantSpec.Description != null) { #> + +// <#=this.constantSpec.Description#> +<# } #> +<# foreach (KeyValuePair constant in this.constantSpec.Constants) { #> + +<# if (constant.Value.Description != null) { #> +/// <#=constant.Value.Description#> +<# } #> +pub const <#=constant.Key.GetConstantName(TargetLanguage.Rust)#>: <#=GetRustType(constant.Value.Type)#> = <#=GetRustValue(constant.Value.Value)#>; +<# } #> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/code/RustAggregateError.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/code/RustAggregateError.cs new file mode 100644 index 0000000000..bff71f6c90 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/code/RustAggregateError.cs @@ -0,0 +1,26 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustAggregateError : IEnvoyTemplateTransform + { + private readonly CodeName schemaName; + private readonly CodeName genNamespace; + private readonly List<(CodeName, CodeName)> innerNameSchemas; + private readonly string srcSubdir; + + public RustAggregateError(CodeName schemaName, CodeName genNamespace, List<(CodeName, CodeName)> innerNameSchemas, string srcSubdir) + { + this.schemaName = schemaName; + this.genNamespace = genNamespace; + this.innerNameSchemas = innerNameSchemas; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.schemaName.GetFileName(TargetLanguage.Rust, "error")}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/code/RustError.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/code/RustError.cs new file mode 100644 index 0000000000..3a69b7edeb --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/code/RustError.cs @@ -0,0 +1,29 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustError : IEnvoyTemplateTransform + { + private readonly CodeName schemaName; + private readonly CodeName genNamespace; + private readonly string description; + private readonly CodeName? messageField; + private readonly bool messageIsRequired; + private readonly string srcSubdir; + + public RustError(CodeName schemaName, CodeName genNamespace, string description, CodeName? messageField, bool messageIsRequired, string srcSubdir) + { + this.schemaName = schemaName; + this.genNamespace = genNamespace; + this.description = description; + this.messageField = messageField; + this.messageIsRequired = messageIsRequired; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.schemaName.GetFileName(TargetLanguage.Rust, "error")}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustAggregateError.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustAggregateError.cs new file mode 100644 index 0000000000..d8754f7202 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustAggregateError.cs @@ -0,0 +1,338 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustAggregateError : RustAggregateErrorBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\nuse std::error::Error;\r\nuse std::fmt;\r\n\r\nuse super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n\r\nimpl fmt::Display for "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n fn fmt(&self, f: &mut fmt::Formatter<\'_>) -> fmt::Result {\r\n let m" + + "ut err_count = 0;\r\n\r\n"); + foreach (var innerNameSchema in this.innerNameSchemas) { + this.Write("\r\n if let Some("); + this.Write(this.ToStringHelper.ToStringWithCulture(innerNameSchema.Item1.GetFieldName(TargetLanguage.Rust))); + this.Write(") = &self."); + this.Write(this.ToStringHelper.ToStringWithCulture(innerNameSchema.Item1.GetFieldName(TargetLanguage.Rust))); + this.Write(" {\r\n if err_count == 0 {\r\n return write!(f, \"{"); + this.Write(this.ToStringHelper.ToStringWithCulture(innerNameSchema.Item1.GetFieldName(TargetLanguage.Rust))); + this.Write("}\");\r\n }\r\n err_count += 1;\r\n }\r\n"); + } + this.Write(@" + // Error display message depends on how many specific errors are reported within the aggregate error. + // If there are none, the aggregate error must have been caused by one or more non-reportable errors. + // If there is exactly one, it has already been displayed. + // If there are two or more, only the first reported error has been displayed, so indicate how many more there are. + match err_count{ + 0 => write!(f, ""No reportable property errors.""), + 1 => Ok(()), + 2 => write!(f, ""(And 1 more reported error.)""), + _ => write!(f, ""(And {} more reported errors.)"", err_count - 1), + } + } +} + +impl Error for "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n fn source(&self) -> Option<&(dyn Error + \'static)> {\r\n None\r\n }" + + "\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustAggregateErrorBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustAggregateError.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustAggregateError.tt new file mode 100644 index 0000000000..f7212e7d72 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustAggregateError.tt @@ -0,0 +1,41 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use std::error::Error; +use std::fmt; + +use super::<#=this.schemaName.GetFileName(TargetLanguage.Rust)#>::<#=this.schemaName.GetTypeName(TargetLanguage.Rust)#>; + +impl fmt::Display for <#=this.schemaName.GetTypeName(TargetLanguage.Rust)#> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut err_count = 0; + +<# foreach (var innerNameSchema in this.innerNameSchemas) { #> + + if let Some(<#=innerNameSchema.Item1.GetFieldName(TargetLanguage.Rust)#>) = &self.<#=innerNameSchema.Item1.GetFieldName(TargetLanguage.Rust)#> { + if err_count == 0 { + return write!(f, "{<#=innerNameSchema.Item1.GetFieldName(TargetLanguage.Rust)#>}"); + } + err_count += 1; + } +<# } #> + + // Error display message depends on how many specific errors are reported within the aggregate error. + // If there are none, the aggregate error must have been caused by one or more non-reportable errors. + // If there is exactly one, it has already been displayed. + // If there are two or more, only the first reported error has been displayed, so indicate how many more there are. + match err_count{ + 0 => write!(f, "No reportable property errors."), + 1 => Ok(()), + 2 => write!(f, "(And 1 more reported error.)"), + _ => write!(f, "(And {} more reported errors.)", err_count - 1), + } + } +} + +impl Error for <#=this.schemaName.GetTypeName(TargetLanguage.Rust)#> { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustError.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustError.cs new file mode 100644 index 0000000000..4b37c80c3c --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustError.cs @@ -0,0 +1,332 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustError : RustErrorBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\nuse std::error::Error;\r\nuse std::fmt;\r\n\r\nuse super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n\r\nimpl fmt::Display for "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n fn fmt(&self, f: &mut fmt::Formatter<\'_>) -> fmt::Result {\r\n"); + if (this.messageField != null) { + if (this.messageIsRequired) { + this.Write(" write!(f, \"{}\", self."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageField.GetFieldName(TargetLanguage.Rust))); + this.Write(")\r\n"); + } else { + this.Write(" if let Some(message) = &self."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageField.GetFieldName(TargetLanguage.Rust))); + this.Write(" {\r\n write!(f, \"{message}\")\r\n } else {\r\n write!(f, \"" + + ""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.description)); + this.Write("\")\r\n }\r\n"); + } + } else { + this.Write(" write!(f, \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.description)); + this.Write("\")\r\n"); + } + this.Write(" }\r\n}\r\n\r\nimpl Error for "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n fn source(&self) -> Option<&(dyn Error + \'static)> {\r\n None\r\n }" + + "\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustErrorBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustError.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustError.tt new file mode 100644 index 0000000000..ed685fb405 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Error/t4/RustError.tt @@ -0,0 +1,32 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use std::error::Error; +use std::fmt; + +use super::<#=this.schemaName.GetFileName(TargetLanguage.Rust)#>::<#=this.schemaName.GetTypeName(TargetLanguage.Rust)#>; + +impl fmt::Display for <#=this.schemaName.GetTypeName(TargetLanguage.Rust)#> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +<# if (this.messageField != null) { #> +<# if (this.messageIsRequired) { #> + write!(f, "{}", self.<#=this.messageField.GetFieldName(TargetLanguage.Rust)#>) +<# } else { #> + if let Some(message) = &self.<#=this.messageField.GetFieldName(TargetLanguage.Rust)#> { + write!(f, "{message}") + } else { + write!(f, "<#=this.description#>") + } +<# } #> +<# } else { #> + write!(f, "<#=this.description#>") +<# } #> + } +} + +impl Error for <#=this.schemaName.GetTypeName(TargetLanguage.Rust)#> { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/code/RustTelemetryReceiver.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/code/RustTelemetryReceiver.cs new file mode 100644 index 0000000000..95c00370fe --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/code/RustTelemetryReceiver.cs @@ -0,0 +1,33 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustTelemetryReceiver : IEnvoyTemplateTransform + { + private readonly CodeName telemetryName; + private readonly CodeName componentName; + private readonly CodeName genNamespace; + private readonly ITypeName schemaType; + private readonly CodeName messageName; + private readonly string? serviceGroupId; + private readonly string topicPattern; + private readonly string srcSubdir; + + public RustTelemetryReceiver(string telemetryName, string componentName, CodeName genNamespace, ITypeName schemaType, string? serviceGroupId, string topicPattern, string messageStub, string srcSubdir) + { + this.telemetryName = new CodeName(telemetryName); + this.componentName = new CodeName(componentName); + this.genNamespace = genNamespace; + this.schemaType = schemaType; + this.messageName = new CodeName(messageStub, "message"); + this.serviceGroupId = serviceGroupId; + this.topicPattern = topicPattern; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/code/RustTelemetrySender.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/code/RustTelemetrySender.cs new file mode 100644 index 0000000000..880267af07 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/code/RustTelemetrySender.cs @@ -0,0 +1,31 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustTelemetrySender : IEnvoyTemplateTransform + { + private readonly CodeName telemetryName; + private readonly CodeName componentName; + private readonly CodeName genNamespace; + private readonly ITypeName schemaType; + private readonly string topicPattern; + private readonly CodeName messageName; + private readonly string srcSubdir; + + public RustTelemetrySender(string telemetryName, string componentName, CodeName genNamespace, ITypeName schemaType, string topicPattern, string messageStub, string srcSubdir) + { + this.telemetryName = new CodeName(telemetryName); + this.componentName = new CodeName(componentName); + this.genNamespace = genNamespace; + this.schemaType = schemaType; + this.topicPattern = topicPattern; + this.messageName = new CodeName(messageStub, "message"); + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetryReceiver.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetryReceiver.cs new file mode 100644 index 0000000000..318b2418da --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetryReceiver.cs @@ -0,0 +1,386 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustTelemetryReceiver : RustTelemetryReceiverBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write(@"; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::{session::SessionManagedClient, token::AckToken}; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::telemetry; +use azure_iot_operations_protocol::application::ApplicationContext; + +"); + if (this.schemaType is CodeName) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.schemaType is CustomTypeName) { + this.Write("use super::super::common_types::custom_payload::CustomPayload;\r\n"); + } + this.Write("use super::super::common_types::options::TelemetryReceiverOptions;\r\n\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write(" = telemetry::receiver::Message<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\n\r\n/// Telemetry Receiver for `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write("`\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("(\r\n telemetry::Receiver<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n);\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("\r\n{\r\n /// Creates a new [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write(@"`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &TelemetryReceiverOptions) -> Self { + let mut receiver_options_builder = telemetry::receiver::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + receiver_options_builder.topic_namespace(topic_namespace.clone()); + } + + let topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!(""ex:{k}""), v)) + .collect(); + + let receiver_options = receiver_options_builder + .topic_pattern("""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.topicPattern)); + this.Write("\")\r\n .topic_token_map(topic_token_map)\r\n .auto_ack(options." + + "auto_ack)\r\n"); + if (this.serviceGroupId != null) { + this.Write(" .service_group_id(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceGroupId)); + this.Write("\".to_string())\r\n"); + } + this.Write(@" .build() + .expect(""DTDL schema generated invalid arguments""); + + Self( + telemetry::Receiver::new(application_context, client, receiver_options) + .expect(""DTDL schema generated invalid arguments""), + ) + } + + /// Shut down the [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("`]\r\n ///\r\n /// # Errors\r\n /// [`AIOProtocolError`] if there is a failure" + + " in graceful shutdown\r\n pub async fn shutdown(&mut self) -> Result<(), AIOPro" + + "tocolError> {\r\n self.0.shutdown().await\r\n }\r\n\r\n /// Receive the nex" + + "t [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write("`]\r\n ///\r\n /// # Errors\r\n /// [`AIOProtocolError`] if there is a failure" + + " receiving a message\r\n pub async fn recv(\r\n &mut self,\r\n ) -> Optio" + + "n), AIOProtocolError>> {\r\n self.0.recv().await\r\n }\r\n}\r" + + "\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustTelemetryReceiverBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetryReceiver.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetryReceiver.tt new file mode 100644 index 0000000000..32445f35f5 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetryReceiver.tt @@ -0,0 +1,79 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::{session::SessionManagedClient, token::AckToken}; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::telemetry; +use azure_iot_operations_protocol::application::ApplicationContext; + +<# if (this.schemaType is CodeName) { #> +use super::<#=this.schemaType.GetFileName(TargetLanguage.Rust)#>::<#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.schemaType is CustomTypeName) { #> +use super::super::common_types::custom_payload::CustomPayload; +<# } #> +use super::super::common_types::options::TelemetryReceiverOptions; + +pub type <#=this.messageName.GetTypeName(TargetLanguage.Rust)#> = telemetry::receiver::Message<<#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>>; + +/// Telemetry Receiver for `<#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>` +pub struct <#=this.componentName.GetTypeName(TargetLanguage.Rust)#>( + telemetry::Receiver<<#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>>, +); + +impl <#=this.componentName.GetTypeName(TargetLanguage.Rust)#> +{ + /// Creates a new [`<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &TelemetryReceiverOptions) -> Self { + let mut receiver_options_builder = telemetry::receiver::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + receiver_options_builder.topic_namespace(topic_namespace.clone()); + } + + let topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + let receiver_options = receiver_options_builder + .topic_pattern("<#=this.topicPattern#>") + .topic_token_map(topic_token_map) + .auto_ack(options.auto_ack) +<# if (this.serviceGroupId != null) { #> + .service_group_id("<#=this.serviceGroupId#>".to_string()) +<# } #> + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + telemetry::Receiver::new(application_context, client, receiver_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Shut down the [`<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>`] + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure in graceful shutdown + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } + + /// Receive the next [`<#=this.messageName.GetTypeName(TargetLanguage.Rust)#>`] + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a message + pub async fn recv( + &mut self, + ) -> Option, Option), AIOProtocolError>> { + self.0.recv().await + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetrySender.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetrySender.cs new file mode 100644 index 0000000000..cac391ec2b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetrySender.cs @@ -0,0 +1,420 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustTelemetrySender : RustTelemetrySenderBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write(@"; DO NOT EDIT. */ + +use std::collections::HashMap; +use std::time::Duration; + +use azure_iot_operations_mqtt::control_packet::QoS; +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::telemetry; +use azure_iot_operations_protocol::application::ApplicationContext; + +"); + if (this.schemaType is CodeName) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.schemaType is CustomTypeName) { + this.Write("use super::super::common_types::custom_payload::CustomPayload;\r\n"); + } + this.Write("use super::super::common_types::options::TelemetrySenderOptions;\r\n\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write(" = telemetry::sender::Message<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write("BuilderError = telemetry::sender::MessageBuilderError;\r\n\r\n/// Builder for [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write("`]\r\n#[derive(Default)]\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write("Builder {\r\n inner_builder: telemetry::sender::MessageBuilder<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n topic_tokens: HashMap,\r\n}\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write("Builder {\r\n /// Quality of Service of the telemetry message. Can only be `AtMo" + + "stOnce` or `AtLeastOnce`.\r\n pub fn qos(&mut self, qos: QoS) -> &mut Self {\r\n " + + " self.inner_builder.qos(qos);\r\n self\r\n }\r\n\r\n /// Custom user " + + "data to set on the message\r\n pub fn custom_user_data(&mut self, custom_user_d" + + "ata: Vec<(String, String)>) -> &mut Self {\r\n self.inner_builder.custom_us" + + "er_data(custom_user_data);\r\n self\r\n }\r\n\r\n /// Topic token keys/valu" + + "es to be replaced into the publish topic of the telemetry message.\r\n /// A pr" + + "efix of \"ex:\" will be prepended to each key before scanning the topic pattern.\r\n" + + " /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced.\r\n pub " + + "fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self {" + + "\r\n for (k, v) in topic_tokens {\r\n self.topic_tokens.insert(for" + + "mat!(\"ex:{k}\"), v);\r\n }\r\n self\r\n }\r\n\r\n /// Time before messa" + + "ge expires\r\n pub fn message_expiry(&mut self, message_expiry: Duration) -> &m" + + "ut Self {\r\n self.inner_builder.message_expiry(message_expiry);\r\n s" + + "elf\r\n }\r\n\r\n /// Cloud event for the message\r\n pub fn cloud_event(&mut s" + + "elf, cloud_event: Option) -> &mut Self {\r\n " + + " self.inner_builder.cloud_event(cloud_event);\r\n self\r\n }\r\n\r\n /// Pa" + + "yload of the message\r\n ///\r\n /// # Errors\r\n /// If the payload cannot b" + + "e serialized\r\n pub fn payload(\r\n &mut self,\r\n payload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write(",\r\n ) -> Result<&mut Self, AIOProtocolError> {\r\n self.inner_builder.pay" + + "load(payload)?;\r\n Ok(self)\r\n }\r\n\r\n /// Builds a new `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write("`\r\n ///\r\n /// # Errors\r\n /// If a required field has not been initialize" + + "d\r\n pub fn build(&mut self) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write("BuilderError> {\r\n self.inner_builder.topic_tokens(self.topic_tokens.clone(" + + "));\r\n\r\n self.inner_builder.build()\r\n }\r\n}\r\n\r\n/// Telemetry Sender for " + + "`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write("`\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("(\r\n telemetry::Sender<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaType.GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n);\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write("\r\n{\r\n /// Creates a new [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.componentName.GetTypeName(TargetLanguage.Rust))); + this.Write(@"`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &TelemetrySenderOptions) -> Self { + let mut sender_options_builder = telemetry::sender::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + sender_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!(""ex:{k}""), v)) + .collect(); + + topic_token_map.insert("""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.EventSenderId)); + this.Write("\".to_string(), client.client_id().to_string());\r\n\r\n let sender_options = s" + + "ender_options_builder\r\n .topic_pattern(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.topicPattern)); + this.Write(@""") + .topic_token_map(topic_token_map) + .build() + .expect(""DTDL schema generated invalid arguments""); + + Self( + telemetry::Sender::new(application_context, client, sender_options) + .expect(""DTDL schema generated invalid arguments""), + ) + } + + /// Sends a [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write("`]\r\n ///\r\n /// # Error\r\n /// [`AIOProtocolError`] if there is a failure " + + "sending the message\r\n pub async fn send(&self, message: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.messageName.GetTypeName(TargetLanguage.Rust))); + this.Write(") -> Result<(), AIOProtocolError> {\r\n self.0.send(message).await\r\n }\r\n}" + + "\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustTelemetrySenderBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetrySender.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetrySender.tt new file mode 100644 index 0000000000..47f7bec80b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Events/t4/RustTelemetrySender.tt @@ -0,0 +1,136 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use std::collections::HashMap; +use std::time::Duration; + +use azure_iot_operations_mqtt::control_packet::QoS; +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::telemetry; +use azure_iot_operations_protocol::application::ApplicationContext; + +<# if (this.schemaType is CodeName) { #> +use super::<#=this.schemaType.GetFileName(TargetLanguage.Rust)#>::<#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.schemaType is CustomTypeName) { #> +use super::super::common_types::custom_payload::CustomPayload; +<# } #> +use super::super::common_types::options::TelemetrySenderOptions; + +pub type <#=this.messageName.GetTypeName(TargetLanguage.Rust)#> = telemetry::sender::Message<<#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>>; +pub type <#=this.messageName.GetTypeName(TargetLanguage.Rust)#>BuilderError = telemetry::sender::MessageBuilderError; + +/// Builder for [`<#=this.messageName.GetTypeName(TargetLanguage.Rust)#>`] +#[derive(Default)] +pub struct <#=this.messageName.GetTypeName(TargetLanguage.Rust)#>Builder { + inner_builder: telemetry::sender::MessageBuilder<<#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>>, + topic_tokens: HashMap, +} + +impl <#=this.messageName.GetTypeName(TargetLanguage.Rust)#>Builder { + /// Quality of Service of the telemetry message. Can only be `AtMostOnce` or `AtLeastOnce`. + pub fn qos(&mut self, qos: QoS) -> &mut Self { + self.inner_builder.qos(qos); + self + } + + /// Custom user data to set on the message + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Topic token keys/values to be replaced into the publish topic of the telemetry message. + /// A prefix of "ex:" will be prepended to each key before scanning the topic pattern. + /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced. + pub fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self { + for (k, v) in topic_tokens { + self.topic_tokens.insert(format!("ex:{k}"), v); + } + self + } + + /// Time before message expires + pub fn message_expiry(&mut self, message_expiry: Duration) -> &mut Self { + self.inner_builder.message_expiry(message_expiry); + self + } + + /// Cloud event for the message + pub fn cloud_event(&mut self, cloud_event: Option) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the message + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: <#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(payload)?; + Ok(self) + } + + /// Builds a new `<#=this.messageName.GetTypeName(TargetLanguage.Rust)#>` + /// + /// # Errors + /// If a required field has not been initialized + pub fn build(&mut self) -> Result<<#=this.messageName.GetTypeName(TargetLanguage.Rust)#>, <#=this.messageName.GetTypeName(TargetLanguage.Rust)#>BuilderError> { + self.inner_builder.topic_tokens(self.topic_tokens.clone()); + + self.inner_builder.build() + } +} + +/// Telemetry Sender for `<#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>` +pub struct <#=this.componentName.GetTypeName(TargetLanguage.Rust)#>( + telemetry::Sender<<#=this.schemaType.GetTypeName(TargetLanguage.Rust)#>>, +); + +impl <#=this.componentName.GetTypeName(TargetLanguage.Rust)#> +{ + /// Creates a new [`<#=this.componentName.GetTypeName(TargetLanguage.Rust)#>`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &TelemetrySenderOptions) -> Self { + let mut sender_options_builder = telemetry::sender::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + sender_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("<#=MqttTopicTokens.EventSenderId#>".to_string(), client.client_id().to_string()); + + let sender_options = sender_options_builder + .topic_pattern("<#=this.topicPattern#>") + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + telemetry::Sender::new(application_context, client, sender_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Sends a [`<#=this.messageName.GetTypeName(TargetLanguage.Rust)#>`] + /// + /// # Error + /// [`AIOProtocolError`] if there is a failure sending the message + pub async fn send(&self, message: <#=this.messageName.GetTypeName(TargetLanguage.Rust)#>) -> Result<(), AIOProtocolError> { + self.0.send(message).await + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/code/RustCargoToml.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/code/RustCargoToml.cs new file mode 100644 index 0000000000..066d17d428 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/code/RustCargoToml.cs @@ -0,0 +1,35 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustCargoToml : IEnvoyTemplateTransform + { + internal static readonly Dictionary> serializerPackageVersions = new() + { + { SerializationFormat.Json, new List<(string, string)> { ("serde_json", "1.0.105") } }, + { SerializationFormat.Raw, new List<(string, string)> { } }, + { SerializationFormat.Custom, new List<(string, string)> { } }, + }; + + private readonly bool generateProject; + private readonly string projectName; + private readonly string? sdkPath; + private readonly List<(string, string)> packageVersions; + private readonly string srcSubdir; + + public RustCargoToml(string projectName, List genFormats, string? sdkPath, bool generateProject, string srcSubdir) + { + this.generateProject = generateProject; + this.projectName = projectName; + this.sdkPath = sdkPath?.Replace('\\', '/'); + this.srcSubdir = srcSubdir; + packageVersions = genFormats.Select(f => serializerPackageVersions[f]).SelectMany(pv => pv).Distinct().ToList(); + } + + public string FileName { get => generateProject ? "Cargo.toml" : "dependencies.md"; } + + public string FolderPath { get => this.generateProject ? string.Empty : this.srcSubdir; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/code/RustIndex.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/code/RustIndex.cs new file mode 100644 index 0000000000..104589a669 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/code/RustIndex.cs @@ -0,0 +1,34 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustIndex : IEnvoyTemplateTransform + { + private readonly CodeName genNamespace; + private readonly bool generateClient; + private readonly bool generateServer; + private readonly List modules; + private readonly string srcSubdir; + + public RustIndex( + CodeName genNamespace, + List envoyFilenames, + bool generateClient, + bool generateServer, + string srcSubdir) + { + this.genNamespace = genNamespace; + this.generateClient = generateClient; + this.generateServer = generateServer; + this.srcSubdir = srcSubdir; + this.modules = envoyFilenames.Select(f => Path.GetFileNameWithoutExtension(f)).Order().ToList(); + } + + public string FileName { get => $"{this.genNamespace.GetFolderName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => this.srcSubdir; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/code/RustLib.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/code/RustLib.cs new file mode 100644 index 0000000000..571e31dabc --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/code/RustLib.cs @@ -0,0 +1,25 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustLib : IEnvoyTemplateTransform + { + private readonly bool generateProject; + private readonly List modules; + private readonly string srcSubdir; + + public RustLib(CodeName genNamespace, bool generateProject, string srcSubdir) + { + this.generateProject = generateProject; + this.srcSubdir = srcSubdir; + this.modules = new List { "common_types", genNamespace.GetFolderName(TargetLanguage.Rust) }; + this.modules.Sort(); + } + + public string FileName { get => this.generateProject ? "lib.rs" : "mod.rs"; } + + public string FolderPath { get => this.srcSubdir; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustCargoToml.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustCargoToml.cs new file mode 100644 index 0000000000..37157bb374 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustCargoToml.cs @@ -0,0 +1,348 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustCargoToml : RustCargoTomlBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + if (this.generateProject) { + this.Write("# Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT.\r\n\r\n[package]\r\nname = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("\"\r\nversion = \"0.1.0\"\r\nedition = \"2024\"\r\n\r\n"); + } else { + this.Write("# Dependencies for the Generated Libraries\r\n\r\nAdd these dependencies to your proj" + + "ect\'s `Cargo.toml` file:\r\n\r\n``` toml\r\n"); + } + this.Write("[dependencies]\r\nserde = { version = \"1.0\", features = [\"derive\"] }\r\nserde_bytes =" + + " \"0.11.15\"\r\nserde_repr = \"0.1\"\r\n"); + foreach (var packageVersion in this.packageVersions) { + this.Write(this.ToStringHelper.ToStringWithCulture(packageVersion.Item1)); + this.Write(" = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(packageVersion.Item2)); + this.Write("\"\r\n"); + } + this.Write(@"chrono = { version = ""0.4.31"", features = [""serde"", ""alloc""] } +iso8601-duration = { version = ""0.2"", features = [""serde"", ""chrono""] } +base64 = ""0.22.1"" +bigdecimal = ""0.4.5"" +time = { version = ""0.3"", features = [""serde"", ""formatting"", ""parsing""] } +uuid = { version = ""1.8.0"", features = [""serde"", ""v4""] } +derive_builder = ""0.20"" +"); + if (this.sdkPath != null) { + this.Write("azure_iot_operations_mqtt = { "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IfLocal("path", "git"))); + this.Write(" = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.sdkPath)); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IfLocal("/azure_iot_operations_mqtt"))); + this.Write("\" }\r\nazure_iot_operations_protocol = { "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IfLocal("path", "git"))); + this.Write(" = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.sdkPath)); + this.Write(this.ToStringHelper.ToStringWithCulture(this.IfLocal("/azure_iot_operations_protocol"))); + this.Write("\" }\r\n"); + } else { + this.Write("azure_iot_operations_mqtt = { version = \"1.0\", registry = \"aio-sdks\" }\r\nazure_iot" + + "_operations_protocol = { version = \"1.0\", registry = \"aio-sdks\" }\r\n"); + } + if (!this.generateProject) { + this.Write("```\r\n"); + } + return this.GenerationEnvironment.ToString(); + } + +string IfLocal(string ifText, string elseText = "") => !this.sdkPath.StartsWith("http://") && !this.sdkPath.StartsWith("https://") ? ifText : elseText; + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustCargoTomlBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustCargoToml.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustCargoToml.tt new file mode 100644 index 0000000000..b881006a44 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustCargoToml.tt @@ -0,0 +1,43 @@ +<#@ template language="C#" linePragmas="false" #> +<# if (this.generateProject) { #> +# Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. + +[package] +name = "<#=this.projectName#>" +version = "0.1.0" +edition = "2024" + +<# } else { #> +# Dependencies for the Generated Libraries + +Add these dependencies to your project's `Cargo.toml` file: + +``` toml +<# } #> +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_bytes = "0.11.15" +serde_repr = "0.1" +<# foreach (var packageVersion in this.packageVersions) { #> +<#=packageVersion.Item1#> = "<#=packageVersion.Item2#>" +<# } #> +chrono = { version = "0.4.31", features = ["serde", "alloc"] } +iso8601-duration = { version = "0.2", features = ["serde", "chrono"] } +base64 = "0.22.1" +bigdecimal = "0.4.5" +time = { version = "0.3", features = ["serde", "formatting", "parsing"] } +uuid = { version = "1.8.0", features = ["serde", "v4"] } +derive_builder = "0.20" +<# if (this.sdkPath != null) { #> +azure_iot_operations_mqtt = { <#=this.IfLocal("path", "git")#> = "<#=this.sdkPath#><#=this.IfLocal("/azure_iot_operations_mqtt")#>" } +azure_iot_operations_protocol = { <#=this.IfLocal("path", "git")#> = "<#=this.sdkPath#><#=this.IfLocal("/azure_iot_operations_protocol")#>" } +<# } else { #> +azure_iot_operations_mqtt = { version = "1.0", registry = "aio-sdks" } +azure_iot_operations_protocol = { version = "1.0", registry = "aio-sdks" } +<# } #> +<# if (!this.generateProject) { #> +``` +<# } #> +<#+ +string IfLocal(string ifText, string elseText = "") => !this.sdkPath.StartsWith("http://") && !this.sdkPath.StartsWith("https://") ? ifText : elseText; +#> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustIndex.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustIndex.cs new file mode 100644 index 0000000000..f4292ec94c --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustIndex.cs @@ -0,0 +1,338 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustIndex : RustIndexBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n"); + foreach (string module in this.modules) { + this.Write("mod "); + this.Write(this.ToStringHelper.ToStringWithCulture(Path.GetFileNameWithoutExtension(module))); + this.Write(";\r\n"); + } + this.Write("\r\npub use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolE" + + "rror;\r\n\r\n"); + if (this.generateServer) { + this.Write("pub use super::common_types::options::{CommandExecutorOptions, TelemetrySenderOpt" + + "ions};\r\n"); + } + if (this.generateClient) { + this.Write("pub use super::common_types::options::{CommandInvokerOptions, TelemetryReceiverOp" + + "tions};\r\n"); + } + if (this.generateClient) { + this.Write("\r\npub mod client {\r\n"); + foreach (string module in this.modules) { if (!module.EndsWith("_executor") && !module.EndsWith("_maintainer") && !module.EndsWith("_sender") && !module.EndsWith("_serialization") && !module.EndsWith("_headers")) { + this.Write(" pub use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(module)); + this.Write("::*;\r\n"); + } } + this.Write("}\r\n"); + } + if (this.generateServer) { + this.Write("\r\npub mod service {\r\n"); + foreach (string module in this.modules) { if (!module.EndsWith("_invoker") && !module.EndsWith("_consumer") && !module.EndsWith("_receiver") && !module.EndsWith("_serialization") && !module.EndsWith("_headers")) { + this.Write(" pub use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(module)); + this.Write("::*;\r\n"); + } } + this.Write("}\r\n"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustIndexBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustIndex.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustIndex.tt new file mode 100644 index 0000000000..de3c8f848f --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustIndex.tt @@ -0,0 +1,33 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.IO" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +<# foreach (string module in this.modules) { #> +mod <#=Path.GetFileNameWithoutExtension(module)#>; +<# } #> + +pub use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; + +<# if (this.generateServer) { #> +pub use super::common_types::options::{CommandExecutorOptions, TelemetrySenderOptions}; +<# } #> +<# if (this.generateClient) { #> +pub use super::common_types::options::{CommandInvokerOptions, TelemetryReceiverOptions}; +<# } #> +<# if (this.generateClient) { #> + +pub mod client { +<# foreach (string module in this.modules) { if (!module.EndsWith("_executor") && !module.EndsWith("_maintainer") && !module.EndsWith("_sender") && !module.EndsWith("_serialization") && !module.EndsWith("_headers")) { #> + pub use super::<#=module#>::*; +<# } } #> +} +<# } #> +<# if (this.generateServer) { #> + +pub mod service { +<# foreach (string module in this.modules) { if (!module.EndsWith("_invoker") && !module.EndsWith("_consumer") && !module.EndsWith("_receiver") && !module.EndsWith("_serialization") && !module.EndsWith("_headers")) { #> + pub use super::<#=module#>::*; +<# } } #> +} +<# } #> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustLib.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustLib.cs new file mode 100644 index 0000000000..044fbda3e4 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustLib.cs @@ -0,0 +1,310 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustLib : RustLibBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#![allow(missing_docs)]\r\n#![allow(unused_imports)]\r\n#![allow" + + "(dead_code)]\r\n#![allow(clippy::result_large_err)]\r\n#![allow(clippy::struct_field" + + "_names)]\r\n"); + foreach (string module in this.modules) { + this.Write("pub mod "); + this.Write(this.ToStringHelper.ToStringWithCulture(module)); + this.Write(";\r\n"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustLibBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustLib.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustLib.tt new file mode 100644 index 0000000000..832b1749a8 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Project/t4/RustLib.tt @@ -0,0 +1,11 @@ +<#@ template language="C#" linePragmas="false" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#![allow(missing_docs)] +#![allow(unused_imports)] +#![allow(dead_code)] +#![allow(clippy::result_large_err)] +#![allow(clippy::struct_field_names)] +<# foreach (string module in this.modules) { #> +pub mod <#=module#>; +<# } #> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/code/RustPropertyConsumer.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/code/RustPropertyConsumer.cs new file mode 100644 index 0000000000..16d6bf816a --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/code/RustPropertyConsumer.cs @@ -0,0 +1,83 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustPropertyConsumer : IEnvoyTemplateTransform + { + private readonly CodeName? propertyName; + private readonly CodeName propSchema; + private readonly CodeName componentName; + private readonly string readCommandName; + private readonly string writeCommandName; + private readonly CodeName genNamespace; + private readonly EmptyTypeName readSerializerEmptyType; + private readonly EmptyTypeName writeSerializerEmptyType; + private readonly CodeName? readRespSchema; + private readonly CodeName? writeReqSchema; + private readonly CodeName? writeRespSchema; + private readonly CodeName? propValueName; + private readonly CodeName? readErrorName; + private readonly CodeName? readErrorSchema; + private readonly CodeName? writeErrorName; + private readonly CodeName? writeErrorSchema; + private readonly string readTopicPattern; + private readonly string writeTopicPattern; + private readonly string srcSubdir; + private readonly bool doesPropertyTargetReadMaintainer; + private readonly bool doesPropertyTargetWriteMaintainer; + private readonly bool separateProperties; + + public RustPropertyConsumer( + string propertyName, + CodeName propSchema, + string compnentName, + string readCommandName, + string writeCommandName, + CodeName genNamespace, + EmptyTypeName readSerializerEmptyType, + EmptyTypeName writeSerializerEmptyType, + string? readRespSchema, + string? writeReqSchema, + string? writeRespSchema, + string? propValueName, + string? readErrorName, + string? readErrorSchema, + string? writeErrorName, + string? writeErrorSchema, + string readTopicPattern, + string writeTopicPattern, + string srcSubdir, + bool doesPropertyTargetReadMaintainer, + bool doesPropertyTargetWriteMaintainer, + bool separateProperties) + { + this.propertyName = new CodeName(propertyName); + this.propSchema = propSchema; + this.componentName = new CodeName(compnentName); + this.readCommandName = readCommandName; + this.writeCommandName = writeCommandName; + this.genNamespace = genNamespace; + this.readSerializerEmptyType = readSerializerEmptyType; + this.writeSerializerEmptyType = writeSerializerEmptyType; + this.readRespSchema = readRespSchema != null ? new CodeName(readRespSchema) : null; + this.writeReqSchema = writeReqSchema != null ? new CodeName(writeReqSchema) : null; + this.writeRespSchema = writeRespSchema != null ? new CodeName(writeRespSchema) : null; + this.propValueName = propValueName != null ? new CodeName(propValueName) : null; + this.readErrorName = readErrorName != null ? new CodeName(readErrorName) : null; + this.readErrorSchema = readErrorSchema != null ? new CodeName(readErrorSchema) : null; + this.writeErrorName = writeErrorName != null ? new CodeName(writeErrorName) : null; + this.writeErrorSchema = writeErrorSchema != null ? new CodeName(writeErrorSchema) : null; + this.readTopicPattern = readTopicPattern; + this.writeTopicPattern = writeTopicPattern; + this.srcSubdir = srcSubdir; + this.doesPropertyTargetReadMaintainer = doesPropertyTargetReadMaintainer; + this.doesPropertyTargetWriteMaintainer = doesPropertyTargetWriteMaintainer; + this.separateProperties = separateProperties; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/code/RustPropertyMaintainer.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/code/RustPropertyMaintainer.cs new file mode 100644 index 0000000000..02f2762be6 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/code/RustPropertyMaintainer.cs @@ -0,0 +1,77 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustPropertyMaintainer : IEnvoyTemplateTransform + { + private readonly CodeName? propertyName; + private readonly CodeName propSchema; + private readonly CodeName componentName; + private readonly string readCommandName; + private readonly string writeCommandName; + private readonly CodeName genNamespace; + private readonly EmptyTypeName readSerializerEmptyType; + private readonly EmptyTypeName writeSerializerEmptyType; + private readonly CodeName? readRespSchema; + private readonly CodeName? writeReqSchema; + private readonly CodeName? writeRespSchema; + private readonly CodeName? propValueName; + private readonly CodeName? readErrorName; + private readonly CodeName? readErrorSchema; + private readonly CodeName? writeErrorName; + private readonly CodeName? writeErrorSchema; + private readonly string readTopicPattern; + private readonly string writeTopicPattern; + private readonly string srcSubdir; + private readonly bool separateProperties; + + public RustPropertyMaintainer( + string propertyName, + CodeName propSchema, + string compnentName, + string readCommandName, + string writeCommandName, + CodeName genNamespace, + EmptyTypeName readSerializerEmptyType, + EmptyTypeName writeSerializerEmptyType, + string? readRespSchema, + string? writeReqSchema, + string? writeRespSchema, + string? propValueName, + string? readErrorName, + string? readErrorSchema, + string? writeErrorName, + string? writeErrorSchema, + string readTopicPattern, + string writeTopicPattern, + string srcSubdir, + bool separateProperties) + { + this.propertyName = new CodeName(propertyName); + this.propSchema = propSchema; + this.componentName = new CodeName(compnentName); + this.readCommandName = readCommandName; + this.writeCommandName = writeCommandName; + this.genNamespace = genNamespace; + this.readSerializerEmptyType = readSerializerEmptyType; + this.writeSerializerEmptyType = writeSerializerEmptyType; + this.readRespSchema = readRespSchema != null ? new CodeName(readRespSchema) : null; + this.writeReqSchema = writeReqSchema != null ? new CodeName(writeReqSchema) : null; + this.writeRespSchema = writeRespSchema != null ? new CodeName(writeRespSchema) : null; + this.propValueName = propValueName != null ? new CodeName(propValueName) : null; + this.readErrorName = readErrorName != null ? new CodeName(readErrorName) : null; + this.readErrorSchema = readErrorSchema != null ? new CodeName(readErrorSchema) : null; + this.writeErrorName = writeErrorName != null ? new CodeName(writeErrorName) : null; + this.writeErrorSchema = writeErrorSchema != null ? new CodeName(writeErrorSchema) : null; + this.readTopicPattern = readTopicPattern; + this.writeTopicPattern = writeTopicPattern; + this.srcSubdir = srcSubdir; + this.separateProperties = separateProperties; + } + + public string FileName { get => $"{this.componentName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyConsumer.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyConsumer.cs new file mode 100644 index 0000000000..509c4ab711 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyConsumer.cs @@ -0,0 +1,830 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustPropertyConsumer : RustPropertyConsumerBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\nuse std::collections::HashMap;\r\n"); + if (this.readErrorName != null) { + this.Write("use std::error::Error;\r\n"); + } + this.Write("use std::time::Duration;\r\n\r\nuse azure_iot_operations_mqtt::session::SessionManage" + + "dClient;\r\nuse azure_iot_operations_protocol::common::aio_protocol_error::{\r\n " + + "AIOProtocolError,\r\n"); + if (this.readErrorName != null) { + this.Write(" AIOProtocolErrorKind,\r\n"); + } + this.Write("};\r\n"); + if (this.writeReqSchema != null) { + this.Write("use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize;\r\n" + + ""); + } + this.Write("use azure_iot_operations_protocol::rpc_command;\r\nuse azure_iot_operations_protoco" + + "l::application::ApplicationContext;\r\n\r\nuse super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + if (this.readRespSchema is CodeName && !this.readRespSchema.Equals(this.propSchema)) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.writeReqSchema is CodeName && !this.writeReqSchema.Equals(this.readRespSchema) && !this.writeReqSchema.Equals(this.propSchema)) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.writeRespSchema is CodeName) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeRespSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.readErrorName != null) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.writeErrorName != null && !this.writeErrorSchema.Equals(this.readErrorSchema)) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("use super::super::common_types::options::CommandInvokerOptions;\r\nuse super::super" + + "::common_types::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + if (!this.readSerializerEmptyType.Equals(this.writeSerializerEmptyType)) { + this.Write("use super::super::common_types::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeSerializerEmptyType.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("\r\n"); + if (this.readRespSchema != null) { + this.Write("pub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request"))); + this.Write(" = rpc_command::invoker::Request<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response"))); + this.Write(" = rpc_command::invoker::Response<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder", "error"))); + this.Write(" = rpc_command::invoker::RequestBuilderError;\r\n"); + if (this.readErrorSchema != null) { + this.Write("pub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "error"))); + this.Write(" = rpc_command::invoker::Response<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\n"); + } + } + if (this.writeReqSchema != null) { + this.Write("pub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request"))); + this.Write(" = rpc_command::invoker::Request<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response"))); + this.Write(" = rpc_command::invoker::Response<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder", "error"))); + this.Write(" = rpc_command::invoker::RequestBuilderError;\r\n"); + if (this.writeErrorSchema != null) { + this.Write("pub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "error"))); + this.Write(" = rpc_command::invoker::Response<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\n"); + } + } + if (this.readRespSchema != null) { + this.Write("\r\n#[derive(Default)]\r\n/// Builder for [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request"))); + this.Write("`]\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder"))); + this.Write(" {\r\n inner_builder: rpc_command::invoker::RequestBuilder<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n"); + if (this.doesPropertyTargetReadMaintainer) { + this.Write(" set_maintainer_id: bool,\r\n"); + } + this.Write(" topic_tokens: HashMap,\r\n}\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder"))); + this.Write(@" { + /// Custom user data to set on the read request + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Topic token keys/values to be replaced into the publish topic of the request message. + /// A prefix of ""ex:"" will be prepended to each key before scanning the topic pattern. + /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced. + pub fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self { + for (k, v) in topic_tokens { + self.topic_tokens.insert(format!(""ex:{k}""), v); + } + self + } + + /// Timeout for the request + pub fn timeout(&mut self, timeout: Duration) -> &mut Self { + self.inner_builder.timeout(timeout); + self + } + +"); + if (this.doesPropertyTargetReadMaintainer) { + this.Write(@" /// Target maintainer ID + pub fn maintainer_id(&mut self, maintainer_id: &str) -> &mut Self { + self.topic_tokens + .insert(""maintainerId"".to_string(), maintainer_id.to_string()); + self.set_maintainer_id = true; + self + } + +"); + } + this.Write(" /// Builds a new `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request"))); + this.Write("`\r\n ///\r\n /// # Errors\r\n /// If a required field has not been initialize" + + "d\r\n #[allow(clippy::missing_panics_doc)] // The panic is not possible\r\n pu" + + "b fn build(&mut self) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request"))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder", "error"))); + this.Write("> {\r\n"); + if (this.doesPropertyTargetReadMaintainer) { + this.Write(" if !self.set_maintainer_id {\r\n return Err("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder", "error"))); + this.Write("::UninitializedField(\r\n \"maintainer_id\",\r\n ));\r\n " + + " }\r\n\r\n"); + } + this.Write(" self.inner_builder.payload("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetAllocator(TargetLanguage.Rust))); + this.Write(").unwrap();\r\n\r\n self.inner_builder.topic_tokens(self.topic_tokens.clone())" + + ";\r\n\r\n self.inner_builder.build()\r\n }\r\n}\r\n"); + } + if (this.writeReqSchema != null) { + this.Write("\r\n#[derive(Default)]\r\n/// Builder for [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request"))); + this.Write("`]\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder"))); + this.Write(" {\r\n inner_builder: rpc_command::invoker::RequestBuilder<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n"); + if (this.doesPropertyTargetWriteMaintainer) { + this.Write(" set_maintainer_id: bool,\r\n"); + } + this.Write(" topic_tokens: HashMap,\r\n}\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder"))); + this.Write(@" { + /// Custom user data to set on the write request + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Topic token keys/values to be replaced into the publish topic of the request message. + /// A prefix of ""ex:"" will be prepended to each key before scanning the topic pattern. + /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced. + pub fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self { + for (k, v) in topic_tokens { + self.topic_tokens.insert(format!(""ex:{k}""), v); + } + self + } + + /// Timeout for the request + pub fn timeout(&mut self, timeout: Duration) -> &mut Self { + self.inner_builder.timeout(timeout); + self + } + +"); + if (this.doesPropertyTargetWriteMaintainer) { + this.Write(@" /// Target maintainer ID + pub fn maintainer_id(&mut self, maintainer_id: &str) -> &mut Self { + self.topic_tokens + .insert(""maintainerId"".to_string(), maintainer_id.to_string()); + self.set_maintainer_id = true; + self + } + +"); + } + this.Write(" /// Payload of the write request\r\n ///\r\n /// # Errors\r\n /// If the p" + + "ayload cannot be serialized\r\n pub fn payload(\r\n &mut self,\r\n pa" + + "yload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(",\r\n ) -> Result<&mut Self, AIOProtocolError> {\r\n self.inner_builder.pay" + + "load(payload)?;\r\n Ok(self)\r\n }\r\n\r\n /// Builds a new `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request"))); + this.Write("`\r\n ///\r\n /// # Errors\r\n /// If a required field has not been initialize" + + "d\r\n #[allow(clippy::missing_panics_doc)] // The panic is not possible\r\n pu" + + "b fn build(&mut self) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request"))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder", "error"))); + this.Write("> {\r\n"); + if (this.doesPropertyTargetWriteMaintainer) { + this.Write(" if !self.set_maintainer_id {\r\n return Err("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder", "error"))); + this.Write("::UninitializedField(\r\n \"maintainer_id\",\r\n ));\r\n " + + " }\r\n\r\n"); + } + this.Write(" self.inner_builder.topic_tokens(self.topic_tokens.clone());\r\n\r\n se" + + "lf.inner_builder.build()\r\n }\r\n}\r\n"); + } + if (this.readRespSchema != null) { + this.Write("\r\n"); + if (!this.propertyName.IsEmpty) { + this.Write("/// Read requester for `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("`\r\n"); + } else { + this.Write("/// Read requester for a Property collection\r\n"); + } + this.Write("pub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "requester"))); + this.Write("(\r\n rpc_command::Invoker<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n);\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "requester"))); + this.Write("\r\n{\r\n /// Creates a new [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "requester"))); + this.Write(@"`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandInvokerOptions) -> Self { + let mut invoker_options_builder = rpc_command::invoker::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + invoker_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!(""ex:{k}""), v)) + .collect(); + + topic_token_map.insert("""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyAction)); + this.Write("\".to_string(), \""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyActionValues.Read)); + this.Write("\".to_string());\r\n topic_token_map.insert(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyConsumerId)); + this.Write("\".to_string(), client.client_id().to_string());\r\n\r\n let invoker_options = " + + "invoker_options_builder\r\n .request_topic_pattern(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readTopicPattern)); + this.Write("\")\r\n .command_name(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readCommandName)); + this.Write(@""") + .topic_token_map(topic_token_map) + .response_topic_prefix(options.response_topic_prefix.clone()) + .response_topic_suffix(options.response_topic_suffix.clone()) + .build() + .expect(""DTDL schema generated invalid arguments""); + + Self( + rpc_command::Invoker::new(application_context, client, invoker_options) + .expect(""DTDL schema generated invalid arguments""), + ) + } + + /// Invokes the [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request"))); + this.Write("`]\r\n ///\r\n /// # Errors\r\n /// [`AIOProtocolError`] if there is a failure" + + " invoking the request\r\n pub async fn invoke(\r\n &self,\r\n request" + + ": "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request"))); + this.Write(",\r\n"); + if (this.readErrorName != null) { + this.Write(" ) -> Result, AIOProtocolError> {\r\n let response = self.0.invoke(request).await;\r\n " + + " match response {\r\n Ok(response) => {\r\n if let Som" + + "e("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorName.GetVariableName(TargetLanguage.Rust))); + this.Write(") = response.payload."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorName.GetFieldName(TargetLanguage.Rust))); + this.Write(" {\r\n Ok(Err("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "error"))); + this.Write(" {\r\n payload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorName.GetVariableName(TargetLanguage.Rust))); + this.Write(@", + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } else if let Some("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propValueName.GetVariableName(TargetLanguage.Rust))); + this.Write(") = response.payload."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propValueName.GetFieldName(TargetLanguage.Rust))); + this.Write(" {\r\n Ok(Ok("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response"))); + this.Write(" {\r\n"); + if (this.separateProperties) { + this.Write(" payload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(" { "); + this.Write(this.ToStringHelper.ToStringWithCulture((this.propValueName ?? this.propertyName).GetFieldName(TargetLanguage.Rust))); + this.Write(": "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propValueName.GetVariableName(TargetLanguage.Rust))); + this.Write(" },\r\n"); + } else { + this.Write(" payload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propValueName.GetVariableName(TargetLanguage.Rust))); + this.Write(",\r\n"); + } + this.Write(@" content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } else { + Err(AIOProtocolError { + message: Some( + ""Command response has neither normal nor error payload content"" + .to_string(), + ), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: true, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readCommandName)); + this.Write("\".to_string()),\r\n protocol_version: None,\r\n " + + " supported_protocol_major_versions: None,\r\n })\r\n " + + " }\r\n }\r\n Err(err) => Err(err),\r\n }\r\n"); + } else { + this.Write(" ) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response"))); + this.Write(", AIOProtocolError> {\r\n self.0.invoke(request).await\r\n"); + } + this.Write(" }\r\n\r\n /// Shutdown the [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "requester"))); + this.Write(@"`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +"); + } + if (this.writeReqSchema != null) { + this.Write("\r\n"); + if (!this.propertyName.IsEmpty) { + this.Write("/// Write requester for `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("`\r\n"); + } else { + this.Write("/// Write requester for a Property collection\r\n"); + } + this.Write("pub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "requester"))); + this.Write("(\r\n rpc_command::Invoker<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(((ITypeName)this.writeRespSchema ?? this.writeSerializerEmptyType).GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n);\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "requester"))); + this.Write("\r\n{\r\n /// Creates a new [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "requester"))); + this.Write(@"`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandInvokerOptions) -> Self { + let mut invoker_options_builder = rpc_command::invoker::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + invoker_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!(""ex:{k}""), v)) + .collect(); + + topic_token_map.insert("""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyAction)); + this.Write("\".to_string(), \""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyActionValues.Write)); + this.Write("\".to_string());\r\n topic_token_map.insert(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyConsumerId)); + this.Write("\".to_string(), client.client_id().to_string());\r\n\r\n let invoker_options = " + + "invoker_options_builder\r\n .request_topic_pattern(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readTopicPattern)); + this.Write("\")\r\n .command_name(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeCommandName)); + this.Write(@""") + .topic_token_map(topic_token_map) + .response_topic_prefix(options.response_topic_prefix.clone()) + .response_topic_suffix(options.response_topic_suffix.clone()) + .build() + .expect(""DTDL schema generated invalid arguments""); + + Self( + rpc_command::Invoker::new(application_context, client, invoker_options) + .expect(""DTDL schema generated invalid arguments""), + ) + } + + /// Invokes the [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request"))); + this.Write("`]\r\n ///\r\n /// # Errors\r\n /// [`AIOProtocolError`] if there is a failure" + + " invoking the request\r\n pub async fn invoke(\r\n &self,\r\n request" + + ": "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request"))); + this.Write(",\r\n"); + if (this.writeErrorName != null) { + this.Write(" ) -> Result, AIOProtocolError> {\r\n let response = self.0.invoke(request).await;\r\n " + + " match response {\r\n Ok(response) => {\r\n if let Som" + + "e("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorName.GetVariableName(TargetLanguage.Rust))); + this.Write(") = response.payload."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorName.GetFieldName(TargetLanguage.Rust))); + this.Write(" {\r\n Ok(Err("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "error"))); + this.Write(" {\r\n payload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorName.GetVariableName(TargetLanguage.Rust))); + this.Write(@", + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } else { + Ok(Ok("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response"))); + this.Write(" {\r\n payload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeSerializerEmptyType.GetAllocator(TargetLanguage.Rust))); + this.Write(@", + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } + } + Err(err) => Err(err), + } +"); + } else { + this.Write(" ) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response"))); + this.Write(", AIOProtocolError> {\r\n self.0.invoke(request).await\r\n"); + } + this.Write(" }\r\n\r\n /// Shutdown the [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "requester"))); + this.Write(@"`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustPropertyConsumerBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyConsumer.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyConsumer.tt new file mode 100644 index 0000000000..78147e3fb5 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyConsumer.tt @@ -0,0 +1,425 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use std::collections::HashMap; +<# if (this.readErrorName != null) { #> +use std::error::Error; +<# } #> +use std::time::Duration; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::common::aio_protocol_error::{ + AIOProtocolError, +<# if (this.readErrorName != null) { #> + AIOProtocolErrorKind, +<# } #> +}; +<# if (this.writeReqSchema != null) { #> +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +<# } #> +use azure_iot_operations_protocol::rpc_command; +use azure_iot_operations_protocol::application::ApplicationContext; + +use super::<#=this.propSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.propSchema.GetTypeName(TargetLanguage.Rust)#>; +<# if (this.readRespSchema is CodeName && !this.readRespSchema.Equals(this.propSchema)) { #> +use super::<#=this.readRespSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.readRespSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.writeReqSchema is CodeName && !this.writeReqSchema.Equals(this.readRespSchema) && !this.writeReqSchema.Equals(this.propSchema)) { #> +use super::<#=this.writeReqSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.writeReqSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.writeRespSchema is CodeName) { #> +use super::<#=this.writeRespSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.writeRespSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.readErrorName != null) { #> +use super::<#=this.readErrorSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.readErrorSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.writeErrorName != null && !this.writeErrorSchema.Equals(this.readErrorSchema)) { #> +use super::<#=this.writeErrorSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.writeErrorSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +use super::super::common_types::options::CommandInvokerOptions; +use super::super::common_types::<#=this.readSerializerEmptyType.GetFileName(TargetLanguage.Rust)#>::<#=this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>; +<# if (!this.readSerializerEmptyType.Equals(this.writeSerializerEmptyType)) { #> +use super::super::common_types::<#=this.writeSerializerEmptyType.GetFileName(TargetLanguage.Rust)#>::<#=this.writeSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>; +<# } #> + +<# if (this.readRespSchema != null) { #> +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request")#> = rpc_command::invoker::Request<<#=this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>>; +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response")#> = rpc_command::invoker::Response<<#=this.propSchema.GetTypeName(TargetLanguage.Rust)#>>; +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder", "error")#> = rpc_command::invoker::RequestBuilderError; +<# if (this.readErrorSchema != null) { #> +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "error")#> = rpc_command::invoker::Response<<#=this.readErrorSchema.GetTypeName(TargetLanguage.Rust)#>>; +<# } #> +<# } #> +<# if (this.writeReqSchema != null) { #> +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request")#> = rpc_command::invoker::Request<<#=this.writeReqSchema.GetTypeName(TargetLanguage.Rust)#>>; +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response")#> = rpc_command::invoker::Response<<#=this.writeSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>>; +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder", "error")#> = rpc_command::invoker::RequestBuilderError; +<# if (this.writeErrorSchema != null) { #> +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "error")#> = rpc_command::invoker::Response<<#=this.writeErrorSchema.GetTypeName(TargetLanguage.Rust)#>>; +<# } #> +<# } #> +<# if (this.readRespSchema != null) { #> + +#[derive(Default)] +/// Builder for [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request")#>`] +pub struct <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder")#> { + inner_builder: rpc_command::invoker::RequestBuilder<<#=this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>>, +<# if (this.doesPropertyTargetReadMaintainer) { #> + set_maintainer_id: bool, +<# } #> + topic_tokens: HashMap, +} + +impl <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder")#> { + /// Custom user data to set on the read request + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Topic token keys/values to be replaced into the publish topic of the request message. + /// A prefix of "ex:" will be prepended to each key before scanning the topic pattern. + /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced. + pub fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self { + for (k, v) in topic_tokens { + self.topic_tokens.insert(format!("ex:{k}"), v); + } + self + } + + /// Timeout for the request + pub fn timeout(&mut self, timeout: Duration) -> &mut Self { + self.inner_builder.timeout(timeout); + self + } + +<# if (this.doesPropertyTargetReadMaintainer) { #> + /// Target maintainer ID + pub fn maintainer_id(&mut self, maintainer_id: &str) -> &mut Self { + self.topic_tokens + .insert("maintainerId".to_string(), maintainer_id.to_string()); + self.set_maintainer_id = true; + self + } + +<# } #> + /// Builds a new `<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request")#>` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build(&mut self) -> Result<<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request")#>, <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder", "error")#>> { +<# if (this.doesPropertyTargetReadMaintainer) { #> + if !self.set_maintainer_id { + return Err(<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request", "builder", "error")#>::UninitializedField( + "maintainer_id", + )); + } + +<# } #> + self.inner_builder.payload(<#=this.readSerializerEmptyType.GetAllocator(TargetLanguage.Rust)#>).unwrap(); + + self.inner_builder.topic_tokens(self.topic_tokens.clone()); + + self.inner_builder.build() + } +} +<# } #> +<# if (this.writeReqSchema != null) { #> + +#[derive(Default)] +/// Builder for [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request")#>`] +pub struct <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder")#> { + inner_builder: rpc_command::invoker::RequestBuilder<<#=this.writeReqSchema.GetTypeName(TargetLanguage.Rust)#>>, +<# if (this.doesPropertyTargetWriteMaintainer) { #> + set_maintainer_id: bool, +<# } #> + topic_tokens: HashMap, +} + +impl <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder")#> { + /// Custom user data to set on the write request + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Topic token keys/values to be replaced into the publish topic of the request message. + /// A prefix of "ex:" will be prepended to each key before scanning the topic pattern. + /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced. + pub fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self { + for (k, v) in topic_tokens { + self.topic_tokens.insert(format!("ex:{k}"), v); + } + self + } + + /// Timeout for the request + pub fn timeout(&mut self, timeout: Duration) -> &mut Self { + self.inner_builder.timeout(timeout); + self + } + +<# if (this.doesPropertyTargetWriteMaintainer) { #> + /// Target maintainer ID + pub fn maintainer_id(&mut self, maintainer_id: &str) -> &mut Self { + self.topic_tokens + .insert("maintainerId".to_string(), maintainer_id.to_string()); + self.set_maintainer_id = true; + self + } + +<# } #> + /// Payload of the write request + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: <#=this.writeReqSchema.GetTypeName(TargetLanguage.Rust)#>, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(payload)?; + Ok(self) + } + + /// Builds a new `<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request")#>` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build(&mut self) -> Result<<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request")#>, <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder", "error")#>> { +<# if (this.doesPropertyTargetWriteMaintainer) { #> + if !self.set_maintainer_id { + return Err(<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request", "builder", "error")#>::UninitializedField( + "maintainer_id", + )); + } + +<# } #> + self.inner_builder.topic_tokens(self.topic_tokens.clone()); + + self.inner_builder.build() + } +} +<# } #> +<# if (this.readRespSchema != null) { #> + +<# if (!this.propertyName.IsEmpty) { #> +/// Read requester for `<#=this.propertyName.AsGiven#>` +<# } else { #> +/// Read requester for a Property collection +<# } #> +pub struct <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "requester")#>( + rpc_command::Invoker<<#=this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>, <#=this.readRespSchema.GetTypeName(TargetLanguage.Rust)#>>, +); + +impl <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "requester")#> +{ + /// Creates a new [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "requester")#>`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandInvokerOptions) -> Self { + let mut invoker_options_builder = rpc_command::invoker::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + invoker_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("<#=MqttTopicTokens.PropertyAction#>".to_string(), "<#=MqttTopicTokens.PropertyActionValues.Read#>".to_string()); + topic_token_map.insert("<#=MqttTopicTokens.PropertyConsumerId#>".to_string(), client.client_id().to_string()); + + let invoker_options = invoker_options_builder + .request_topic_pattern("<#=this.readTopicPattern#>") + .command_name("<#=this.readCommandName#>") + .topic_token_map(topic_token_map) + .response_topic_prefix(options.response_topic_prefix.clone()) + .response_topic_suffix(options.response_topic_suffix.clone()) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Invoker::new(application_context, client, invoker_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Invokes the [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request")#>`] + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure invoking the request + pub async fn invoke( + &self, + request: <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request")#>, +<# if (this.readErrorName != null) { #> + ) -> Result, <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "error")#>>, AIOProtocolError> { + let response = self.0.invoke(request).await; + match response { + Ok(response) => { + if let Some(<#=this.readErrorName.GetVariableName(TargetLanguage.Rust)#>) = response.payload.<#=this.readErrorName.GetFieldName(TargetLanguage.Rust)#> { + Ok(Err(<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "error")#> { + payload: <#=this.readErrorName.GetVariableName(TargetLanguage.Rust)#>, + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } else if let Some(<#=this.propValueName.GetVariableName(TargetLanguage.Rust)#>) = response.payload.<#=this.propValueName.GetFieldName(TargetLanguage.Rust)#> { + Ok(Ok(<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response")#> { +<# if (this.separateProperties) { #> + payload: <#=this.propSchema.GetTypeName(TargetLanguage.Rust)#> { <#=(this.propValueName ?? this.propertyName).GetFieldName(TargetLanguage.Rust)#>: <#=this.propValueName.GetVariableName(TargetLanguage.Rust)#> }, +<# } else { #> + payload: <#=this.propValueName.GetVariableName(TargetLanguage.Rust)#>, +<# } #> + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } else { + Err(AIOProtocolError { + message: Some( + "Command response has neither normal nor error payload content" + .to_string(), + ), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: true, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("<#=this.readCommandName#>".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + }) + } + } + Err(err) => Err(err), + } +<# } else { #> + ) -> Result<<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response")#>, AIOProtocolError> { + self.0.invoke(request).await +<# } #> + } + + /// Shutdown the [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "requester")#>`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +<# } #> +<# if (this.writeReqSchema != null) { #> + +<# if (!this.propertyName.IsEmpty) { #> +/// Write requester for `<#=this.propertyName.AsGiven#>` +<# } else { #> +/// Write requester for a Property collection +<# } #> +pub struct <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "requester")#>( + rpc_command::Invoker<<#=this.writeReqSchema.GetTypeName(TargetLanguage.Rust)#>, <#=((ITypeName)this.writeRespSchema ?? this.writeSerializerEmptyType).GetTypeName(TargetLanguage.Rust)#>>, +); + +impl <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "requester")#> +{ + /// Creates a new [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "requester")#>`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandInvokerOptions) -> Self { + let mut invoker_options_builder = rpc_command::invoker::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + invoker_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("<#=MqttTopicTokens.PropertyAction#>".to_string(), "<#=MqttTopicTokens.PropertyActionValues.Write#>".to_string()); + topic_token_map.insert("<#=MqttTopicTokens.PropertyConsumerId#>".to_string(), client.client_id().to_string()); + + let invoker_options = invoker_options_builder + .request_topic_pattern("<#=this.readTopicPattern#>") + .command_name("<#=this.writeCommandName#>") + .topic_token_map(topic_token_map) + .response_topic_prefix(options.response_topic_prefix.clone()) + .response_topic_suffix(options.response_topic_suffix.clone()) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Invoker::new(application_context, client, invoker_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Invokes the [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request")#>`] + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure invoking the request + pub async fn invoke( + &self, + request: <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request")#>, +<# if (this.writeErrorName != null) { #> + ) -> Result, <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "error")#>>, AIOProtocolError> { + let response = self.0.invoke(request).await; + match response { + Ok(response) => { + if let Some(<#=this.writeErrorName.GetVariableName(TargetLanguage.Rust)#>) = response.payload.<#=this.writeErrorName.GetFieldName(TargetLanguage.Rust)#> { + Ok(Err(<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "error")#> { + payload: <#=this.writeErrorName.GetVariableName(TargetLanguage.Rust)#>, + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } else { + Ok(Ok(<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response")#> { + payload: <#=this.writeSerializerEmptyType.GetAllocator(TargetLanguage.Rust)#>, + content_type: response.content_type, + format_indicator: response.format_indicator, + custom_user_data: response.custom_user_data, + timestamp: response.timestamp, + executor_id: response.executor_id, + })) + } + } + Err(err) => Err(err), + } +<# } else { #> + ) -> Result<<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response")#>, AIOProtocolError> { + self.0.invoke(request).await +<# } #> + } + + /// Shutdown the [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "requester")#>`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +<# } #> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyMaintainer.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyMaintainer.cs new file mode 100644 index 0000000000..56a3fe8c87 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyMaintainer.cs @@ -0,0 +1,641 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustPropertyMaintainer : RustPropertyMaintainerBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\nuse std::collections::HashMap;\r\n\r\nuse azure_iot_operations_m" + + "qtt::session::SessionManagedClient;\r\nuse azure_iot_operations_protocol::common::" + + "aio_protocol_error::AIOProtocolError;\r\n"); + if (this.readRespSchema != null || this.writeRespSchema != null) { + this.Write("use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize;\r\n" + + ""); + } + this.Write("use azure_iot_operations_protocol::rpc_command;\r\nuse azure_iot_operations_protoco" + + "l::application::ApplicationContext;\r\n\r\nuse super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + if (this.readRespSchema is CodeName && !this.readRespSchema.Equals(this.propSchema)) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.writeReqSchema is CodeName && !this.writeReqSchema.Equals(this.readRespSchema) && !this.writeReqSchema.Equals(this.propSchema)) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.writeRespSchema is CodeName) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeRespSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.readErrorName != null) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + if (this.writeErrorName != null && !this.writeErrorSchema.Equals(this.readErrorSchema)) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorSchema.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("use super::super::common_types::options::CommandExecutorOptions;\r\nuse super::supe" + + "r::common_types::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + if (!this.readSerializerEmptyType.Equals(this.writeSerializerEmptyType)) { + this.Write("use super::super::common_types::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeSerializerEmptyType.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + this.Write("\r\n"); + if (this.readRespSchema != null) { + this.Write("pub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request"))); + this.Write(" = rpc_command::executor::Request<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response"))); + this.Write(" = rpc_command::executor::Response<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "builder", "error"))); + this.Write(" = rpc_command::executor::ResponseBuilderError;\r\n"); + } + if (this.writeReqSchema != null) { + this.Write("pub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request"))); + this.Write(" = rpc_command::executor::Request<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(((ITypeName)this.writeRespSchema ?? this.writeSerializerEmptyType).GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response"))); + this.Write(" = rpc_command::executor::Response<"); + this.Write(this.ToStringHelper.ToStringWithCulture(((ITypeName)this.writeRespSchema ?? this.writeSerializerEmptyType).GetTypeName(TargetLanguage.Rust))); + this.Write(">;\r\npub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "builder", "error"))); + this.Write(" = rpc_command::executor::ResponseBuilderError;\r\n"); + } + this.Write("\r\n#[derive(Default)]\r\n/// Builder for [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response"))); + this.Write("`]\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "builder"))); + this.Write(" {\r\n inner_builder: rpc_command::executor::ResponseBuilder<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n}\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "builder"))); + this.Write(@" { + /// Custom user data to set on the read response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Payload of the read response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(",\r\n ) -> Result<&mut Self, AIOProtocolError> {\r\n"); + if (this.readErrorName != null) { + this.Write(" self.inner_builder.payload("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propValueName.GetFieldName(TargetLanguage.Rust))); + this.Write(": Some(payload"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.separateProperties ? $".{this.propValueName.GetFieldName(TargetLanguage.Rust)}" : "")); + this.Write("),\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorName.GetFieldName(TargetLanguage.Rust))); + this.Write(": None,\r\n })?;\r\n"); + } else { + this.Write(" self.inner_builder.payload(payload)?;\r\n"); + } + this.Write(" Ok(self)\r\n }\r\n\r\n"); + if (this.readErrorName != null) { + this.Write(" pub fn error(&mut self, error: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(") -> Result<&mut Self, AIOProtocolError> {\r\n self.inner_builder.payload("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propValueName.GetFieldName(TargetLanguage.Rust))); + this.Write(": None,\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorName.GetFieldName(TargetLanguage.Rust))); + this.Write(": Some(error),\r\n })?;\r\n Ok(self)\r\n }\r\n\r\n"); + } + this.Write(" /// Builds a new `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response"))); + this.Write("`\r\n ///\r\n /// # Errors\r\n /// If a required field has not been initialize" + + "d\r\n #[allow(clippy::missing_panics_doc)] // The panic is not possible\r\n pu" + + "b fn build(&mut self) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response"))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "builder", "error"))); + this.Write("> {\r\n self.inner_builder.build()\r\n }\r\n}\r\n"); + if (this.writeRespSchema != null) { + this.Write("\r\n#[derive(Default)]\r\n/// Builder for [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response"))); + this.Write("`]\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "builder"))); + this.Write(" {\r\n inner_builder: rpc_command::executor::ResponseBuilder<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n}\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "builder"))); + this.Write(" {\r\n /// Custom user data to set on the write response\r\n pub fn custom_user" + + "_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self {\r\n " + + " self.inner_builder.custom_user_data(custom_user_data);\r\n self\r\n }\r\n\r" + + "\n"); + if (this.writeErrorName != null) { + this.Write(" pub fn error(&mut self, error: "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(") -> Result<&mut Self, AIOProtocolError> {\r\n self.inner_builder.payload("); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorName.GetFieldName(TargetLanguage.Rust))); + this.Write(": Some(error),\r\n })?;\r\n Ok(self)\r\n }\r\n\r\n"); + } + this.Write(" /// Builds a new `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response"))); + this.Write("`\r\n ///\r\n /// # Errors\r\n /// If a required field has not been initialize" + + "d\r\n #[allow(clippy::missing_panics_doc)] // The panic is not possible\r\n pu" + + "b fn build(&mut self) -> Result<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response"))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "builder", "error"))); + this.Write("> {\r\n self.inner_builder.build()\r\n }\r\n}\r\n"); + } + if (this.readRespSchema != null) { + this.Write("\r\n"); + if (!this.propertyName.IsEmpty) { + this.Write("/// Read responder for `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("`\r\n"); + } else { + this.Write("/// Read responder for a Property collection\r\n"); + } + this.Write("pub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "responder"))); + this.Write("(\r\n rpc_command::Executor<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readRespSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n);\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "responder"))); + this.Write("\r\n{\r\n /// Creates a new [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "responder"))); + this.Write(@"`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandExecutorOptions) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!(""ex:{k}""), v)) + .collect(); + + topic_token_map.insert("""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyAction)); + this.Write("\".to_string(), \""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyActionValues.Read)); + this.Write("\".to_string());\r\n topic_token_map.insert(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyMaintainerId)); + this.Write("\".to_string(), client.client_id().to_string());\r\n\r\n let executor_options =" + + " executor_options_builder\r\n .request_topic_pattern(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readTopicPattern)); + this.Write("\")\r\n .command_name(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readCommandName)); + this.Write(@""") + .topic_token_map(topic_token_map) + .build() + .expect(""DTDL schema generated invalid arguments""); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect(""DTDL schema generated invalid arguments""), + ) + } + + /// Receive the next [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request"))); + this.Write("`] or [`None`] if there will be no more requests\r\n ///\r\n /// # Errors\r\n " + + "/// [`AIOProtocolError`] if there is a failure receiving a request\r\n pub asyn" + + "c fn recv(&mut self) -> Option> {\r\n self.0.recv().await\r\n }\r\n\r\n /// Shutdown th" + + "e [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "responder"))); + this.Write(@"`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +"); + } + if (this.writeReqSchema != null) { + this.Write("\r\n"); + if (!this.propertyName.IsEmpty) { + this.Write("/// Write responder for `"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.AsGiven)); + this.Write("`\r\n"); + } else { + this.Write("/// Write responder for a Property collection\r\n"); + } + this.Write("pub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "responder"))); + this.Write("(\r\n rpc_command::Executor<"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeReqSchema.GetTypeName(TargetLanguage.Rust))); + this.Write(", "); + this.Write(this.ToStringHelper.ToStringWithCulture(((ITypeName)this.writeRespSchema ?? this.writeSerializerEmptyType).GetTypeName(TargetLanguage.Rust))); + this.Write(">,\r\n);\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "responder"))); + this.Write("\r\n{\r\n /// Creates a new [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "responder"))); + this.Write(@"`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandExecutorOptions) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!(""ex:{k}""), v)) + .collect(); + + topic_token_map.insert("""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyAction)); + this.Write("\".to_string(), \""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyActionValues.Write)); + this.Write("\".to_string());\r\n topic_token_map.insert(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(MqttTopicTokens.PropertyMaintainerId)); + this.Write("\".to_string(), client.client_id().to_string());\r\n\r\n let executor_options =" + + " executor_options_builder\r\n .request_topic_pattern(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeTopicPattern)); + this.Write("\")\r\n .command_name(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeCommandName)); + this.Write(@""") + .topic_token_map(topic_token_map) + .build() + .expect(""DTDL schema generated invalid arguments""); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect(""DTDL schema generated invalid arguments""), + ) + } + + /// Receive the next [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request"))); + this.Write("`] or [`None`] if there will be no more requests\r\n ///\r\n /// # Errors\r\n " + + "/// [`AIOProtocolError`] if there is a failure receiving a request\r\n pub asyn" + + "c fn recv(&mut self) -> Option> {\r\n self.0.recv().await\r\n }\r\n\r\n /// Shutdown th" + + "e [`"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "responder"))); + this.Write(@"`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +"); + } + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustPropertyMaintainerBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyMaintainer.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyMaintainer.tt new file mode 100644 index 0000000000..32e0cc6e99 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Properties/t4/RustPropertyMaintainer.tt @@ -0,0 +1,260 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +<# if (this.readRespSchema != null || this.writeRespSchema != null) { #> +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +<# } #> +use azure_iot_operations_protocol::rpc_command; +use azure_iot_operations_protocol::application::ApplicationContext; + +use super::<#=this.propSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.propSchema.GetTypeName(TargetLanguage.Rust)#>; +<# if (this.readRespSchema is CodeName && !this.readRespSchema.Equals(this.propSchema)) { #> +use super::<#=this.readRespSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.readRespSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.writeReqSchema is CodeName && !this.writeReqSchema.Equals(this.readRespSchema) && !this.writeReqSchema.Equals(this.propSchema)) { #> +use super::<#=this.writeReqSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.writeReqSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.writeRespSchema is CodeName) { #> +use super::<#=this.writeRespSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.writeRespSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.readErrorName != null) { #> +use super::<#=this.readErrorSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.readErrorSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# if (this.writeErrorName != null && !this.writeErrorSchema.Equals(this.readErrorSchema)) { #> +use super::<#=this.writeErrorSchema.GetFileName(TargetLanguage.Rust)#>::<#=this.writeErrorSchema.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +use super::super::common_types::options::CommandExecutorOptions; +use super::super::common_types::<#=this.readSerializerEmptyType.GetFileName(TargetLanguage.Rust)#>::<#=this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>; +<# if (!this.readSerializerEmptyType.Equals(this.writeSerializerEmptyType)) { #> +use super::super::common_types::<#=this.writeSerializerEmptyType.GetFileName(TargetLanguage.Rust)#>::<#=this.writeSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>; +<# } #> + +<# if (this.readRespSchema != null) { #> +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request")#> = rpc_command::executor::Request<<#=this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>, <#=this.readRespSchema.GetTypeName(TargetLanguage.Rust)#>>; +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response")#> = rpc_command::executor::Response<<#=this.readRespSchema.GetTypeName(TargetLanguage.Rust)#>>; +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "builder", "error")#> = rpc_command::executor::ResponseBuilderError; +<# } #> +<# if (this.writeReqSchema != null) { #> +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request")#> = rpc_command::executor::Request<<#=this.writeReqSchema.GetTypeName(TargetLanguage.Rust)#>, <#=((ITypeName)this.writeRespSchema ?? this.writeSerializerEmptyType).GetTypeName(TargetLanguage.Rust)#>>; +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response")#> = rpc_command::executor::Response<<#=((ITypeName)this.writeRespSchema ?? this.writeSerializerEmptyType).GetTypeName(TargetLanguage.Rust)#>>; +pub type <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "builder", "error")#> = rpc_command::executor::ResponseBuilderError; +<# } #> + +#[derive(Default)] +/// Builder for [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response")#>`] +pub struct <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "builder")#> { + inner_builder: rpc_command::executor::ResponseBuilder<<#=this.readRespSchema.GetTypeName(TargetLanguage.Rust)#>>, +} + +impl <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "builder")#> { + /// Custom user data to set on the read response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Payload of the read response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: <#=this.propSchema.GetTypeName(TargetLanguage.Rust)#>, + ) -> Result<&mut Self, AIOProtocolError> { +<# if (this.readErrorName != null) { #> + self.inner_builder.payload(<#=this.readRespSchema.GetTypeName(TargetLanguage.Rust)#> { + <#=this.propValueName.GetFieldName(TargetLanguage.Rust)#>: Some(payload<#=this.separateProperties ? $".{this.propValueName.GetFieldName(TargetLanguage.Rust)}" : "" #>), + <#=this.readErrorName.GetFieldName(TargetLanguage.Rust)#>: None, + })?; +<# } else { #> + self.inner_builder.payload(payload)?; +<# } #> + Ok(self) + } + +<# if (this.readErrorName != null) { #> + pub fn error(&mut self, error: <#=this.readErrorSchema.GetTypeName(TargetLanguage.Rust)#>) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(<#=this.readRespSchema.GetTypeName(TargetLanguage.Rust)#> { + <#=this.propValueName.GetFieldName(TargetLanguage.Rust)#>: None, + <#=this.readErrorName.GetFieldName(TargetLanguage.Rust)#>: Some(error), + })?; + Ok(self) + } + +<# } #> + /// Builds a new `<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response")#>` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build(&mut self) -> Result<<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response")#>, <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "response", "builder", "error")#>> { + self.inner_builder.build() + } +} +<# if (this.writeRespSchema != null) { #> + +#[derive(Default)] +/// Builder for [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response")#>`] +pub struct <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "builder")#> { + inner_builder: rpc_command::executor::ResponseBuilder<<#=this.writeRespSchema.GetTypeName(TargetLanguage.Rust)#>>, +} + +impl <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "builder")#> { + /// Custom user data to set on the write response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + +<# if (this.writeErrorName != null) { #> + pub fn error(&mut self, error: <#=this.writeErrorSchema.GetTypeName(TargetLanguage.Rust)#>) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(<#=this.writeRespSchema.GetTypeName(TargetLanguage.Rust)#> { + <#=this.writeErrorName.GetFieldName(TargetLanguage.Rust)#>: Some(error), + })?; + Ok(self) + } + +<# } #> + /// Builds a new `<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response")#>` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build(&mut self) -> Result<<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response")#>, <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "response", "builder", "error")#>> { + self.inner_builder.build() + } +} +<# } #> +<# if (this.readRespSchema != null) { #> + +<# if (!this.propertyName.IsEmpty) { #> +/// Read responder for `<#=this.propertyName.AsGiven#>` +<# } else { #> +/// Read responder for a Property collection +<# } #> +pub struct <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "responder")#>( + rpc_command::Executor<<#=this.readSerializerEmptyType.GetTypeName(TargetLanguage.Rust)#>, <#=this.readRespSchema.GetTypeName(TargetLanguage.Rust)#>>, +); + +impl <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "responder")#> +{ + /// Creates a new [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "responder")#>`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandExecutorOptions) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("<#=MqttTopicTokens.PropertyAction#>".to_string(), "<#=MqttTopicTokens.PropertyActionValues.Read#>".to_string()); + topic_token_map.insert("<#=MqttTopicTokens.PropertyMaintainerId#>".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("<#=this.readTopicPattern#>") + .command_name("<#=this.readCommandName#>") + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "request")#>`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv(&mut self) -> Option, AIOProtocolError>> { + self.0.recv().await + } + + /// Shutdown the [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "read", "responder")#>`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +<# } #> +<# if (this.writeReqSchema != null) { #> + +<# if (!this.propertyName.IsEmpty) { #> +/// Write responder for `<#=this.propertyName.AsGiven#>` +<# } else { #> +/// Write responder for a Property collection +<# } #> +pub struct <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "responder")#>( + rpc_command::Executor<<#=this.writeReqSchema.GetTypeName(TargetLanguage.Rust)#>, <#=((ITypeName)this.writeRespSchema ?? this.writeSerializerEmptyType).GetTypeName(TargetLanguage.Rust)#>>, +); + +impl <#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "responder")#> +{ + /// Creates a new [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "responder")#>`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new(application_context: ApplicationContext, client: SessionManagedClient, options: &CommandExecutorOptions) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("<#=MqttTopicTokens.PropertyAction#>".to_string(), "<#=MqttTopicTokens.PropertyActionValues.Write#>".to_string()); + topic_token_map.insert("<#=MqttTopicTokens.PropertyMaintainerId#>".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("<#=this.writeTopicPattern#>") + .command_name("<#=this.writeCommandName#>") + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "request")#>`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv(&mut self) -> Option, AIOProtocolError>> { + self.0.recv().await + } + + /// Shutdown the [`<#=this.propertyName.GetTypeName(TargetLanguage.Rust, "write", "responder")#>`]. Unsubscribes from the response topic and cancels the receiver loop to drop the receiver and to prevent the task from looping indefinitely. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} +<# } #> diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Serialization/code/RustSerialization.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Serialization/code/RustSerialization.cs new file mode 100644 index 0000000000..c53b35c6f4 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Serialization/code/RustSerialization.cs @@ -0,0 +1,74 @@ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustSerialization : IEnvoyTemplateTransform + { + private static readonly Dictionary serdeLibs = new() + { + { SerializationFormat.Json, "serde_json" }, + }; + + private static readonly Dictionary> formatStdHeaders = new() + { + { SerializationFormat.Json, new List { } }, + }; + + private static readonly Dictionary> formatExtHeaders = new() + { + { SerializationFormat.Json, new List { "use serde_json;" } }, + }; + + private static readonly Dictionary formatContentType = new() + { + { SerializationFormat.Json, "application/json" }, + }; + + private static readonly Dictionary formatFormatIndicator = new() + { + { SerializationFormat.Json, "Utf8EncodedCharacterData" }, + }; + + private static readonly Dictionary> formatSerializeCode = new() + { + { SerializationFormat.Json, new List { "serde_json::to_vec(&self)" } }, + }; + + private static readonly Dictionary> formatDeserializeCode = new() + { + { SerializationFormat.Json, new List { "serde_json::from_slice(payload)" } }, + }; + + private readonly CodeName genNamespace; + private readonly CodeName schemaClassName; + private readonly string? serdeLib; + private readonly List stdHeaders; + private readonly List extHeaders; + private readonly string? contentType; + private readonly string? formatIndicator; + private readonly List serializeCode; + private readonly List deserializeCode; + private readonly string srcSubdir; + + public RustSerialization(CodeName genNamespace, SerializationFormat genFormat, CodeName schemaClassName, string srcSubdir) + { + this.genNamespace = genNamespace; + this.schemaClassName = schemaClassName; + this.srcSubdir = srcSubdir; + + this.serdeLib = serdeLibs.GetValueOrDefault(genFormat); + this.stdHeaders = formatStdHeaders.GetValueOrDefault(genFormat) ?? new List(); + this.extHeaders = formatExtHeaders.GetValueOrDefault(genFormat) ?? new List(); + this.contentType = formatContentType.GetValueOrDefault(genFormat); + this.formatIndicator = formatFormatIndicator.GetValueOrDefault(genFormat); + this.serializeCode = formatSerializeCode.GetValueOrDefault(genFormat) ?? new List(); + this.deserializeCode = formatDeserializeCode.GetValueOrDefault(genFormat) ?? new List(); + } + + public string FileName { get => $"{this.schemaClassName.GetFileName(TargetLanguage.Rust, "serialization")}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Serialization/t4/RustSerialization.cs b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Serialization/t4/RustSerialization.cs new file mode 100644 index 0000000000..30abdf7a32 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Serialization/t4/RustSerialization.cs @@ -0,0 +1,390 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.EnvoyGenerator +{ + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustSerialization : RustSerializationBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n"); + if (this.stdHeaders.Any()) { + foreach (string header in this.stdHeaders) { + this.Write(this.ToStringHelper.ToStringWithCulture(header)); + this.Write("\r\n"); + } + this.Write("\r\n"); + } + if (this.extHeaders.Any()) { + this.Write("use azure_iot_operations_protocol::common::payload_serialize::{\r\n Deserializat" + + "ionError, FormatIndicator, PayloadSerialize, SerializedPayload,\r\n};\r\n"); + foreach (string header in this.extHeaders) { + this.Write(this.ToStringHelper.ToStringWithCulture(header)); + this.Write("\r\n"); + } + this.Write("\r\n"); + } + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaClassName.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaClassName.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n\r\n"); + + var contentTypeConstName = this.schemaClassName.GetConstantName(TargetLanguage.Rust, "content", "type"); + + this.Write("const "); + this.Write(this.ToStringHelper.ToStringWithCulture(contentTypeConstName)); + this.Write(": &str = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\";\r\n\r\nimpl "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaClassName.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n fn is_content_type(content_type: &str) -> bool {\r\n content_type.st" + + "arts_with("); + this.Write(this.ToStringHelper.ToStringWithCulture(contentTypeConstName)); + this.Write(")\r\n && matches!(\r\n content_type\r\n .c" + + "hars()\r\n .nth("); + this.Write(this.ToStringHelper.ToStringWithCulture(contentTypeConstName)); + this.Write(".len()),\r\n None | Some(\'+\' | \';\')\r\n )\r\n }\r\n}\r\n\r\nimpl" + + " PayloadSerialize for "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaClassName.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n"); + if (this.serdeLib != null) { + this.Write(" type Error = "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serdeLib)); + this.Write("::Error;\r\n\r\n"); + } + this.Write(" fn serialize(self) -> Result {\r\n let p" + + "ayload = "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializeCode.First())); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serializeCode.Count == 1 ? ";" : "")); + this.Write("\r\n"); + int ix = 2; foreach (string serializeLine in this.serializeCode.Skip(1)) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(serializeLine)); + this.Write(this.ToStringHelper.ToStringWithCulture(ix == this.serializeCode.Count ? ";" : "")); + this.Write("\r\n"); + ix++; } + this.Write(" Ok(SerializedPayload {\r\n payload: payload?,\r\n conte" + + "nt_type: \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\".to_string(),\r\n format_indicator: FormatIndicator::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.formatIndicator)); + this.Write(@", + }) + } + + fn deserialize( + payload: &[u8], + content_type: Option<&String>, + _format_indicator: &FormatIndicator, + ) -> Result> { + if let Some(content_type) = content_type + && !"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaClassName.GetTypeName(TargetLanguage.Rust))); + this.Write("::is_content_type(content_type)\r\n {\r\n return Err(Deserializatio" + + "nError::UnsupportedContentType(format!(\r\n \"Invalid content type: " + + "\'{content_type}\'. Must be \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\'\"\r\n )));\r\n }\r\n"); + ix = 1; foreach (string deserializeLine in this.deserializeCode) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(deserializeLine)); + this.Write(this.ToStringHelper.ToStringWithCulture(ix == this.deserializeCode.Count ? ".map_err(DeserializationError::InvalidPayload)" : "")); + this.Write("\r\n"); + ix++; } + this.Write(" }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustSerializationBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Serialization/t4/RustSerialization.tt b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Serialization/t4/RustSerialization.tt new file mode 100644 index 0000000000..b78095082d --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.EnvoyGenerator/rust/Serialization/t4/RustSerialization.tt @@ -0,0 +1,73 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +<# if (this.stdHeaders.Any()) { #> +<# foreach (string header in this.stdHeaders) { #> +<#=header#> +<# } #> + +<# } #> +<# if (this.extHeaders.Any()) { #> +use azure_iot_operations_protocol::common::payload_serialize::{ + DeserializationError, FormatIndicator, PayloadSerialize, SerializedPayload, +}; +<# foreach (string header in this.extHeaders) { #> +<#=header#> +<# } #> + +<# } #> +use super::<#=this.schemaClassName.GetFileName(TargetLanguage.Rust)#>::<#=this.schemaClassName.GetTypeName(TargetLanguage.Rust)#>; + +<# + var contentTypeConstName = this.schemaClassName.GetConstantName(TargetLanguage.Rust, "content", "type"); +#> +const <#=contentTypeConstName#>: &str = "<#=this.contentType#>"; + +impl <#=this.schemaClassName.GetTypeName(TargetLanguage.Rust)#> { + fn is_content_type(content_type: &str) -> bool { + content_type.starts_with(<#=contentTypeConstName#>) + && matches!( + content_type + .chars() + .nth(<#=contentTypeConstName#>.len()), + None | Some('+' | ';') + ) + } +} + +impl PayloadSerialize for <#=this.schemaClassName.GetTypeName(TargetLanguage.Rust)#> { +<# if (this.serdeLib != null) { #> + type Error = <#=this.serdeLib#>::Error; + +<# } #> + fn serialize(self) -> Result { + let payload = <#=this.serializeCode.First()#><#=this.serializeCode.Count == 1 ? ";" : ""#> +<# int ix = 2; foreach (string serializeLine in this.serializeCode.Skip(1)) { #> + <#=serializeLine#><#=ix == this.serializeCode.Count ? ";" : ""#> +<# ix++; } #> + Ok(SerializedPayload { + payload: payload?, + content_type: "<#=this.contentType#>".to_string(), + format_indicator: FormatIndicator::<#=this.formatIndicator#>, + }) + } + + fn deserialize( + payload: &[u8], + content_type: Option<&String>, + _format_indicator: &FormatIndicator, + ) -> Result> { + if let Some(content_type) = content_type + && !<#=this.schemaClassName.GetTypeName(TargetLanguage.Rust)#>::is_content_type(content_type) + { + return Err(DeserializationError::UnsupportedContentType(format!( + "Invalid content type: '{content_type}'. Must be '<#=this.contentType#>'" + ))); + } +<# ix = 1; foreach (string deserializeLine in this.deserializeCode) { #> + <#=deserializeLine#><#=ix == this.deserializeCode.Count ? ".map_err(DeserializationError::InvalidPayload)" : ""#> +<# ix++; } #> + } +} diff --git a/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/ArgBinder.cs b/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/ArgBinder.cs new file mode 100644 index 0000000000..fdc0137341 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/ArgBinder.cs @@ -0,0 +1,91 @@ +namespace Azure.Iot.Operations.ProtocolCompiler +{ + using System.CommandLine; + using System.CommandLine.Binding; + using System.IO; + using Azure.Iot.Operations.ProtocolCompilerLib; + + /// + /// Custom arguemnt binder for CLI. + /// + public class ArgBinder : BinderBase + { + private readonly Option thingFiles; + private readonly Option schemaFiles; + private readonly Option typeNamerFile; + private readonly Option outputDir; + private readonly Option workingDir; + private readonly Option genNamespace; + private readonly Option sdkPath; + private readonly Option language; + private readonly Option clientOnly; + private readonly Option serverOnly; + private readonly Option noProj; + private readonly Option defaultImpl; + + /// + /// Initializes a new instance of the class. + /// + /// File(s) containing WoT Thing Description(s) to process. + /// Filespec(s) of files containing schema definitions. + /// Directory for receiving generated code. + /// Directory for storing temporary files (relative to outDir unless path is rooted). + /// Namespace for generated code; null for default. + /// Local path or feed URL for Azure.Iot.Operations.Protocol SDK. + /// Programming language for generated code. + /// Generate only client-side code. + /// Generate only server-side code. + /// Suppress generation of a project. + /// Generate default (empty) implementations of callbacks. + public ArgBinder( + Option thingFiles, + Option schemaFiles, + Option typeNamerFile, + Option outputDir, + Option workingDir, + Option genNamespace, + Option sdkPath, + Option language, + Option clientOnly, + Option serverOnly, + Option noProj, + Option defaultImpl) + { + this.thingFiles = thingFiles; + this.schemaFiles = schemaFiles; + this.typeNamerFile = typeNamerFile; + this.outputDir = outputDir; + this.workingDir = workingDir; + this.genNamespace = genNamespace; + this.sdkPath = sdkPath; + this.language = language; + this.clientOnly = clientOnly; + this.serverOnly = serverOnly; + this.noProj = noProj; + this.defaultImpl = defaultImpl; + } + + /// + protected override OptionContainer GetBoundValue(BindingContext bindingContext) + { + DirectoryInfo outputDir = bindingContext.ParseResult.GetValueForOption(this.outputDir)!; + string workingDir = bindingContext.ParseResult.GetValueForOption(this.workingDir)!; + + return new OptionContainer() + { + ThingFiles = bindingContext.ParseResult.GetValueForOption(this.thingFiles)!, + SchemaFiles = bindingContext.ParseResult.GetValueForOption(this.schemaFiles)!, + TypeNamerFile = bindingContext.ParseResult.GetValueForOption(this.typeNamerFile), + OutputDir = outputDir, + WorkingDir = Path.IsPathRooted(workingDir) ? new DirectoryInfo(workingDir) : new DirectoryInfo(Path.Combine(outputDir.FullName, workingDir)), + GenNamespace = bindingContext.ParseResult.GetValueForOption(this.genNamespace)!, + SdkPath = bindingContext.ParseResult.GetValueForOption(this.sdkPath), + Language = bindingContext.ParseResult.GetValueForOption(this.language)!, + ClientOnly = bindingContext.ParseResult.GetValueForOption(this.clientOnly), + ServerOnly = bindingContext.ParseResult.GetValueForOption(this.serverOnly), + NoProj = bindingContext.ParseResult.GetValueForOption(this.noProj), + DefaultImpl = bindingContext.ParseResult.GetValueForOption(this.defaultImpl), + }; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/Azure.Iot.Operations.ProtocolCompiler.csproj b/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/Azure.Iot.Operations.ProtocolCompiler.csproj new file mode 100644 index 0000000000..c0a2810ae8 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/Azure.Iot.Operations.ProtocolCompiler.csproj @@ -0,0 +1,18 @@ + + + + Exe + net9.0 + enable + + + + + + + + + + + + diff --git a/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/CommandHandler.cs b/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/CommandHandler.cs new file mode 100644 index 0000000000..70deec1b8c --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/CommandHandler.cs @@ -0,0 +1,91 @@ +namespace Azure.Iot.Operations.ProtocolCompiler +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.ProtocolCompilerLib; + + internal class CommandHandler + { + private const ConsoleColor ErrorColor = ConsoleColor.Red; + private const ConsoleColor WarningColor = ConsoleColor.Yellow; + + private static readonly Dictionary LanguageMap = new() + { + { "csharp", (TargetLanguage.CSharp, "") }, + { "rust", (TargetLanguage.Rust, "src") }, + { "none", (TargetLanguage.None, "") }, + }; + + public static readonly string[] SupportedLanguages = LanguageMap.Keys.ToArray(); + + public static int GenerateCode(OptionContainer options) + { + ErrorLog errorLog = CommandPerformer.GenerateCode(options, (string msg, bool noNewline) => + { + if (noNewline) + { + Console.Write(msg); + } + else + { + Console.WriteLine(msg); + } + }); + + if (errorLog.HasErrors) + { + DisplayErrors(errorLog); + DisplayWarnings(errorLog); + return 1; + } + else + { + DisplayWarnings(errorLog); + return 0; + } + } + + private static void DisplayErrors(ErrorLog errorLog) + { + if (errorLog.Errors.Count > 0 || errorLog.FatalError != null) + { + Console.ForegroundColor = ErrorColor; + Console.WriteLine(); + Console.WriteLine($"{errorLog.Phase} FAILED with the following errors:"); + if (errorLog.FatalError != null) + { + Console.WriteLine($" FATAL: {FormatErrorRecord(errorLog.FatalError)}"); + } + foreach (ErrorRecord error in errorLog.Errors.OrderBy(e => (e.Filename, e.LineNumber))) + { + Console.WriteLine($" ERROR: {FormatErrorRecord(error)}"); + } + Console.ResetColor(); + } + } + + private static void DisplayWarnings(ErrorLog errorLog) + { + if (errorLog.Warnings.Count > 0) + { + Console.ForegroundColor = WarningColor; + Console.WriteLine(); + foreach (ErrorRecord error in errorLog.Warnings.OrderBy(e => (e.CrossRef, e.Filename, e.LineNumber))) + { + Console.WriteLine($" WARNING: {FormatErrorRecord(error)}"); + } + Console.ResetColor(); + } + } + + private static string FormatErrorRecord(ErrorRecord error) + { + string cfLineInfo = error.CfLineNumber > 0 ? $", cf. Line: {error.CfLineNumber}" : string.Empty; + string lineInfo = error.LineNumber > 0 ? $", Line: {error.LineNumber}" : string.Empty; + string fileInfo = error.Filename != string.Empty ? $" (File: {error.Filename}{lineInfo}{cfLineInfo})" : string.Empty; + return $"{error.Message}{fileInfo}"; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/Program.cs b/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/Program.cs new file mode 100644 index 0000000000..0923c80714 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.ProtocolCompiler/Program.cs @@ -0,0 +1,108 @@ +namespace Azure.Iot.Operations.ProtocolCompiler +{ + using System; + using System.CommandLine; + using System.IO; + using Azure.Iot.Operations.ProtocolCompilerLib; + + internal class Program + { + static void Main(string[] args) + { + var thingFilesOption = new Option( + name: "--thingFiles", + description: "File(s) containing WoT Thing Description(s) to process") + { ArgumentHelpName = "FILEPATH ...", AllowMultipleArgumentsPerToken = true }; + + var schemasOption = new Option( + name: "--schemas", + description: "Filespec(s) of files containing schema definitions (each may include wildcards).") + { ArgumentHelpName = "FILESPEC ...", AllowMultipleArgumentsPerToken = true }; + + var typeNamerOption = new Option( + name: "--typeNamer", + description: "File containing JSON config for deriving type names from JSON Schema names") + { ArgumentHelpName = "FILEPATH" }; + + var outDirOption = new Option( + name: "--outDir", + getDefaultValue: () => new DirectoryInfo(CommandPerformer.DefaultOutDir), + description: "Directory for receiving generated code") + { ArgumentHelpName = "DIRPATH" }; + + var workingDirOption = new Option( + name: "--workingDir", + getDefaultValue: () => CommandPerformer.DefaultWorkingDir, + description: "Directory for storing temporary files (relative to outDir unless path is rooted)") + { ArgumentHelpName = "DIRPATH" }; + + var namespaceOption = new Option( + name: "--namespace", + getDefaultValue: () => CommandPerformer.DefaultNamespace, + description: "Namespace for generated code") + { ArgumentHelpName = "NAMESPACE" }; + + var sdkPathOption = new Option( + name: "--sdkPath", + description: "Local path or feed URL for Azure.Iot.Operations.Protocol SDK") + { ArgumentHelpName = "FILEPATH | URL" }; + + var langOption = new Option( + name: "--lang", + description: "Programming language for generated code") + { ArgumentHelpName = string.Join('|', CommandHandler.SupportedLanguages) }; + + var clientOnlyOption = new Option( + name: "--clientOnly", + description: "Generate only client-side code"); + + var serverOnlyOption = new Option( + name: "--serverOnly", + description: "Generate only server-side code"); + + var noProjOption = new Option( + name: "--noProj", + description: "Do not generate code in a project"); + + var defaultImplOption = new Option( + name: "--defaultImpl", + description: "Generate default implementations of user-level callbacks"); + + var rootCommand = new RootCommand("Akri MQTT code generation tool for WoT Thing Descriptions") + { + thingFilesOption, + schemasOption, + typeNamerOption, + outDirOption, + workingDirOption, + namespaceOption, + sdkPathOption, + langOption, + clientOnlyOption, + serverOnlyOption, + noProjOption, + defaultImplOption, + }; + + ArgBinder argBinder = new ArgBinder( + thingFilesOption, + schemasOption, + typeNamerOption, + outDirOption, + workingDirOption, + namespaceOption, + sdkPathOption, + langOption, + clientOnlyOption, + serverOnlyOption, + noProjOption, + defaultImplOption); + + rootCommand.SetHandler( + (OptionContainer options) => { Environment.ExitCode = CommandHandler.GenerateCode(options); }, + argBinder); + + rootCommand.Invoke(args); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.ProtocolCompilerLib/Azure.Iot.Operations.ProtocolCompilerLib.csproj b/codegen2/src/Azure.Iot.Operations.ProtocolCompilerLib/Azure.Iot.Operations.ProtocolCompilerLib.csproj new file mode 100644 index 0000000000..d849e95466 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.ProtocolCompilerLib/Azure.Iot.Operations.ProtocolCompilerLib.csproj @@ -0,0 +1,16 @@ + + + + net9.0 + enable + + + + + + + + + + + diff --git a/codegen2/src/Azure.Iot.Operations.ProtocolCompilerLib/CommandPerformer.cs b/codegen2/src/Azure.Iot.Operations.ProtocolCompilerLib/CommandPerformer.cs new file mode 100644 index 0000000000..e7a41df9a9 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.ProtocolCompilerLib/CommandPerformer.cs @@ -0,0 +1,398 @@ +namespace Azure.Iot.Operations.ProtocolCompilerLib +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.EnvoyGenerator; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + using Azure.Iot.Operations.TypeGenerator; + using Azure.Iot.Operations.SchemaGenerator; + + public static class CommandPerformer + { + public const string DefaultOutDir = "."; + public const string DefaultWorkingDir = "schemas"; + public const string DefaultNamespace = "Generated"; + + private static readonly Dictionary LanguageMap = new() + { + { "csharp", (TargetLanguage.CSharp, "") }, + { "rust", (TargetLanguage.Rust, "src") }, + { "none", (TargetLanguage.None, "") }, + }; + + public static readonly string[] SupportedLanguages = LanguageMap.Keys.ToArray(); + + public static ErrorLog GenerateCode(OptionContainer options, Action statusReceiver, bool suppressExternalTools = false) + { + ErrorLog errorLog = new(options.WorkingDir.FullName); + + try + { + WarnOnSuspiciousOptions(options, errorLog); + ValidateOptions(options, errorLog); + if (errorLog.HasErrors) + { + return errorLog; + } + + string projectName = LegalizeProjectName(options.OutputDir.Name); + (TargetLanguage targetLanguage, string srcSubdir) = LanguageMap[options.Language.ToLowerInvariant()]; + + if (options.NoProj) + { + srcSubdir = string.Empty; + } + + errorLog.Phase = "Parsing"; + List parsedThings = ParseThings(options.ThingFiles, errorLog, out HashSet serializationFormats, statusReceiver); + + if (errorLog.HasErrors) + { + return errorLog; + } + + errorLog.Phase = "Schema generation"; + Dictionary> generatedSchemas = SchemaGenerator.GenerateSchemas(parsedThings, projectName, options.WorkingDir); + + errorLog.CheckForDuplicatesInThings(); + if (errorLog.HasErrors) + { + return errorLog; + } + + foreach (List schemas in generatedSchemas.Values) + { + WriteItems(schemas, options.WorkingDir, statusReceiver); + } + + if (targetLanguage == TargetLanguage.None) + { + statusReceiver?.Invoke("No code generation requested; exiting after schema generation.", false); + return errorLog; + } + + FileInfo[] schemaFiles = options.SchemaFiles.SelectMany(fs => Directory.GetFiles(Path.GetDirectoryName(fs) ?? string.Empty, Path.GetFileName(fs)), (_, f) => new FileInfo(f)).ToArray(); + if (schemaFiles.Length > 0) + { + ImportSchemas(schemaFiles, generatedSchemas); + } + + string? typeNameInfoText = options.TypeNamerFile?.OpenText()?.ReadToEnd(); + TypeNamer typeNamer = new TypeNamer(typeNameInfoText); + + errorLog.Phase = "Type generation"; + List generatedTypes = new(); + foreach (KeyValuePair> schemaSet in generatedSchemas) + { + Dictionary schemaTextsByName = schemaSet.Value.ToDictionary(s => Path.GetFullPath(Path.Combine(options.WorkingDir.FullName, s.FolderPath, s.FileName)).Replace('\\', '/'), s => s.Content); + TypeGenerator typeGenerator = new TypeGenerator(schemaSet.Key, targetLanguage, typeNamer, errorLog); + generatedTypes.AddRange(typeGenerator.GenerateTypes(schemaTextsByName, new CodeName(options.GenNamespace), projectName, srcSubdir)); + } + + errorLog.CheckForDuplicatesInSchemas(); + if (errorLog.HasErrors) + { + return errorLog; + } + + WriteItems(generatedTypes, options.OutputDir, statusReceiver); + + List typeNames = generatedTypes.Select(gt => Path.GetFileNameWithoutExtension(gt.FileName)).ToList(); + + string? sdkPath = options.SdkPath != null ? Path.GetRelativePath(options.OutputDir.FullName, options.SdkPath) : null; + + serializationFormats.UnionWith(generatedSchemas.Keys); + + errorLog.Phase = "Envoy generation"; + List generatedEnvoys = EnvoyGenerator.GenerateEnvoys( + parsedThings, + serializationFormats.ToList(), + targetLanguage, + options.GenNamespace, + projectName, + sdkPath, + typeNames, + srcSubdir, + generateClient: !options.ServerOnly, + generateServer: !options.ClientOnly, + generateProject: !options.NoProj, + defaultImpl: options.DefaultImpl); + WriteItems(generatedEnvoys, options.OutputDir, statusReceiver); + + if (targetLanguage == TargetLanguage.Rust && !suppressExternalTools) + { + GeneratedItem? cargoInfo = generatedEnvoys.FirstOrDefault(e => e.FileName.Equals("Cargo.toml", StringComparison.OrdinalIgnoreCase)); + if (cargoInfo != null) + { + string projectFolder = Path.Combine(options.OutputDir.FullName, cargoInfo.FolderPath); + try + { + RunCargo($"fmt --manifest-path {Path.Combine(projectFolder, "Cargo.toml")}", display: true); + RunCargo("install --locked cargo-machete@0.7.0", display: false); + RunCargo($"machete --fix {projectFolder}", display: true); + } + catch (Win32Exception) + { + Console.WriteLine("cargo tool not found; install per instructions: https://doc.rust-lang.org/cargo/getting-started/installation.html"); + } + } + } + } + catch (Exception ex) + { + AddUnlocatableError(ErrorCondition.JsonInvalid, $"Exception: {ex.Message}", errorLog); + return errorLog; + } + + return errorLog; + } + + private static void RunCargo(string args, bool display) + { + if (display) + { + Console.WriteLine($"cargo {args}"); + } + + using (Process cargo = new Process()) + { + cargo.StartInfo.FileName = "cargo"; + cargo.StartInfo.Arguments = args; + cargo.StartInfo.UseShellExecute = false; + cargo.StartInfo.RedirectStandardOutput = true; + cargo.Start(); + cargo.WaitForExit(); + } + } + + private static List ParseThings(FileInfo[] thingFiles, ErrorLog errorLog, out HashSet serializationFormats, Action statusReceiver) + { + List parsedThings = new(); + serializationFormats = new HashSet(); + + foreach (FileInfo thingFile in thingFiles) + { + statusReceiver.Invoke($"Parsing thing description file: {thingFile.Name} ...", true); + + using (StreamReader thingReader = thingFile.OpenText()) + { + string thingText = thingReader.ReadToEnd(); + byte[] thingBytes = Encoding.UTF8.GetBytes(thingText); + ErrorReporter errorReporter = new ErrorReporter(errorLog, thingFile.FullName, thingBytes); + ThingValidator thingValidator = new ThingValidator(errorReporter); + + if (TryGetThings(errorReporter, thingBytes, out List? things)) + { + int thingCount = 0; + foreach (TDThing thing in things) + { + if (thingValidator.TryValidateThng(thing, serializationFormats)) + { + ValueTracker? schemaNamesFilename = thing.Links?.Elements?.FirstOrDefault(l => l.Value.Rel?.Value.Value == TDValues.RelationSchemaNaming)?.Value.Href; + if (TryGetSchemaNamer(errorReporter, thingFile.DirectoryName!, schemaNamesFilename, out SchemaNamer? schemaNamer)) + { + thingCount++; + parsedThings.Add(new ParsedThing(thing, thingFile.Name, thingFile.DirectoryName!, schemaNamer, errorReporter)); + errorReporter.RegisterNameOfThing(thing.Title!.Value.Value, thing.Title!.TokenIndex); + } + } + } + + statusReceiver.Invoke($" {thingCount} {(thingCount == 1 ? "TD" : "TDs")} validly parsed", false); + } + } + } + + return parsedThings; + } + + private static bool TryGetSchemaNamer(ErrorReporter errorReporter, string folderPath, ValueTracker? namerFilename, [NotNullWhen(true)] out SchemaNamer? schemaNamer) + { + if (namerFilename == null) + { + schemaNamer = new SchemaNamer(); + return true; + } + + FileInfo namerFile = new FileInfo(Path.Combine(folderPath, namerFilename.Value.Value)); + if (!namerFile.Exists) + { + errorReporter.ReportError(ErrorCondition.ItemNotFound, $"Could not find schema naming file '{namerFilename.Value.Value}'.", namerFilename.TokenIndex); + + schemaNamer = null; + return false; + } + + string schemaNameInfoText = namerFile.OpenText().ReadToEnd(); + + try + { + schemaNamer = new SchemaNamer(schemaNameInfoText); + return true; + } + catch (Exception ex) + { + errorReporter.ReportError(ErrorCondition.JsonInvalid, $"Failed to parse schema naming file '{namerFilename.Value.Value}': {ex.Message}", namerFilename.TokenIndex); + schemaNamer = null; + return false; + } + } + + private static bool TryGetThings(ErrorReporter errorReporter, byte[] thingBytes, [NotNullWhen(true)] out List? things) + { + bool hasError = false; + + try + { + things = TDParser.Parse(thingBytes); + } + catch (Exception ex) + { + errorReporter.ReportJsonException(ex); + things = null; + return false; + } + + foreach (TDThing thing in things) + { + foreach (ITraversable item in thing.Traverse()) + { + if (item is ISourceTracker tracker && tracker.DeserializingFailed) + { + errorReporter.ReportError(ErrorCondition.JsonInvalid, $"TD deserialization error: {tracker.DeserializationError ?? string.Empty}.", tracker.TokenIndex); + hasError = true; + } + + if (item is ValueTracker dataSchema && dataSchema.Value?.Ref != null) + { + errorReporter.RegisterReferenceFromThing(dataSchema.Value.Ref.TokenIndex, dataSchema.Value.Ref.Value.Value); + } + else if (item is ValueTracker property && property.Value?.Ref != null) + { + errorReporter.RegisterReferenceFromThing(property.Value.Ref.TokenIndex, property.Value.Ref.Value.Value); + } + } + } + + return !hasError; + } + + private static void WriteItems(List generatedItems, DirectoryInfo destDir, Action statusReceiver) + { + foreach (GeneratedItem genItem in generatedItems) + { + DirectoryInfo folderPath = new DirectoryInfo(Path.Combine(destDir.FullName, genItem.FolderPath)); + if (!folderPath.Exists) + { + folderPath.Create(); + } + + string filePath = Path.Combine(folderPath.FullName, genItem.FileName); + File.WriteAllText(filePath, genItem.Content); + statusReceiver.Invoke($" Generated {filePath}", false); + } + } + + private static void ImportSchemas(FileInfo[] extSchemaFiles, Dictionary> generatedSchemas) + { + foreach (FileInfo schemaFile in extSchemaFiles) + { + SerializationFormat format = schemaFile.Name switch + { + string n when n.EndsWith(".json", StringComparison.OrdinalIgnoreCase) => SerializationFormat.Json, + _ => SerializationFormat.None, + }; + + if (!generatedSchemas.TryGetValue(format, out List? schemas)) + { + schemas = new(); + generatedSchemas[format] = schemas; + } + + schemas.Add(new GeneratedItem(schemaFile.OpenText().ReadToEnd(), schemaFile.Name, schemaFile.Directory!.FullName)); + } + } + + private static string LegalizeProjectName(string fsName) + { + return string.Join('.', fsName.Split('.', StringSplitOptions.RemoveEmptyEntries).Select(s => (char.IsNumber(s[0]) ? "_" : "") + Regex.Replace(s, "[^a-zA-Z0-9]+", "_", RegexOptions.CultureInvariant))); + } + + private static void ValidateOptions(OptionContainer options, ErrorLog errorLog) + { + bool anyThingFiles = options.ThingFiles.Length > 0; + bool anySchemaFiles = options.SchemaFiles.Any(fs => Directory.GetFiles(Path.GetDirectoryName(fs) ?? string.Empty, Path.GetFileName(fs)).Any()); + + if (!anyThingFiles && !anySchemaFiles) + { + AddUnlocatableError(ErrorCondition.ElementMissing, $"No Thing Description files specified, and no schema files {(options.SchemaFiles.Length > 0 ? "found" : "specified")}. Use option --help for CLI usage and options.", errorLog); + return; + } + + if (!SupportedLanguages.Contains(options.Language)) + { + string langCondition = string.IsNullOrEmpty(options.Language) ? "language not specified" : $"language '{options.Language}' not recognized"; + AddUnlocatableError(ErrorCondition.PropertyUnsupportedValue, $"{langCondition}; language must be {string.Join(" or ", SupportedLanguages.Select(l => $"'{l}'"))} (use 'none' for no code generation)", errorLog); + return; + } + + if (options.ClientOnly && options.ServerOnly) + { + AddUnlocatableError(ErrorCondition.ValuesInconsistent, "options --clientOnly and --serverOnly are mutually exclusive", errorLog); + return; + } + + if (options.ThingFiles.Any(mf => !mf.Exists)) + { + foreach (FileInfo f in options.ThingFiles.Where(tf => !tf.Exists)) + { + AddUnlocatableError(ErrorCondition.ItemNotFound, $"Non-existent Thing Description file: {f.FullName}", errorLog); + } + return; + } + } + + private static void WarnOnSuspiciousOptions(OptionContainer options, ErrorLog errorLog) + { + if (!options.WorkingDir.Exists) + { + WarnOnSuspiciousOption("workingDir", options.WorkingDir.Name, errorLog); + } + + if (!options.OutputDir.Exists) + { + WarnOnSuspiciousOption("outDir", options.OutputDir.Name, errorLog); + } + + WarnOnSuspiciousOption("namespace", options.GenNamespace, errorLog); + WarnOnSuspiciousOption("sdkPath", options.SdkPath, errorLog); + } + + private static void WarnOnSuspiciousOption(string optionName, string? pathName, ErrorLog errorLog) + { + if (pathName != null && pathName.StartsWith("--")) + { + AddUnlocatableWarning($"{optionName} \"{pathName}\" looks like a flag. Did you forget to specify a value?", errorLog); + } + } + + private static void AddUnlocatableError(ErrorCondition condition, string message, ErrorLog errorLog) + { + errorLog.AddError(ErrorLevel.Error, condition, message, string.Empty, 0); + } + + private static void AddUnlocatableWarning(string message, ErrorLog errorLog) + { + errorLog.AddError(ErrorLevel.Warning, ErrorCondition.None, message, string.Empty, 0); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.ProtocolCompilerLib/OptionContainer.cs b/codegen2/src/Azure.Iot.Operations.ProtocolCompilerLib/OptionContainer.cs new file mode 100644 index 0000000000..220367226a --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.ProtocolCompilerLib/OptionContainer.cs @@ -0,0 +1,46 @@ +namespace Azure.Iot.Operations.ProtocolCompilerLib +{ + using System.IO; + + /// + /// Custom container for holding CLI options. + /// + public class OptionContainer + { + /// Gets or sets the file(s) containing WoT Thing Description(s) to process. + public required FileInfo[] ThingFiles { get; set; } + + /// Gets or sets the filespec(s) of files containing schema definitions. + public required string[] SchemaFiles { get; set; } + + /// Gets or sets the file containing JSON config for deriving type names from JSON Schema names. + public required FileInfo? TypeNamerFile { get; set; } + + /// Gets or sets the directory for storing temporary files. + public required DirectoryInfo WorkingDir { get; set; } + + /// Gets or sets the directory for receiving generated code. + public required DirectoryInfo OutputDir { get; set; } + + /// Gets or sets a namespace for generated code. + public required string GenNamespace { get; set; } + + /// Gets or sets a local path or feed URL for Azure.Iot.Operations.Protocol SDK. + public string? SdkPath { get; set; } + + /// Gets or sets the programming language for generated code. + public required string Language { get; set; } + + /// Gets or sets an indication of whether to generate only client-side code. + public bool ClientOnly { get; set; } + + /// Gets or sets an indication of whether to generate only server-side code. + public bool ServerOnly { get; set; } + + /// Gets or sets an indication of whether to suppress generation of a project. + public bool NoProj { get; set; } + + /// Gets or sets an indication of whether to substitute virtual methods for abstract methods. + public bool DefaultImpl { get; set; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/ActionSchemaGenerator.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/ActionSchemaGenerator.cs new file mode 100644 index 0000000000..59544d819d --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/ActionSchemaGenerator.cs @@ -0,0 +1,138 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal static class ActionSchemaGenerator + { + private const string InputOutputType = "object"; + + internal static void GenerateActionSchemas(ErrorReporter errorReporter, TDThing tdThing, string dirName, SchemaNamer schemaNamer, string projectName, Dictionary> schemaSpecs, Dictionary> referencedSchemas) + { + foreach (KeyValuePair> actionKvp in tdThing.Actions?.Entries ?? new()) + { + TDAction? action = actionKvp.Value.Value; + if (action != null) + { + ProcessAction( + errorReporter, + schemaNamer, + actionKvp.Key, + action, + projectName, + dirName, + tdThing.SchemaDefinitions?.Entries, + schemaSpecs, + referencedSchemas); + } + } + } + + private static void ProcessAction( + ErrorReporter errorReporter, + SchemaNamer schemaNamer, + string actionName, + TDAction tdAction, + string projectName, + string dirName, + Dictionary>? schemaDefinitions, + Dictionary> schemaSpecs, + Dictionary> referencedSchemas) + { + FormInfo? actionForm = FormInfo.CreateFromForm(errorReporter, tdAction.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpInvokeAction) ?? false)?.Value, schemaDefinitions); + actionForm ??= FormInfo.CreateFromForm(errorReporter, tdAction.Forms?.Elements?.FirstOrDefault(f => f.Value.Op == null)?.Value, schemaDefinitions); + + if (actionForm?.TopicPattern != null) + { + ValueTracker? inputRef = tdAction.Input?.Value?.Ref; + if (inputRef != null) + { + string inputSchemaName = schemaNamer.GetActionInSchema(null, actionName); + if (!schemaSpecs.TryGetValue(inputSchemaName, out List? inputSpecs)) + { + inputSpecs = new List(); + schemaSpecs[inputSchemaName] = inputSpecs; + } + inputSpecs.Add(new AliasSpec(null, InputOutputType, inputRef.Value.Value, actionForm.Format, inputSchemaName, dirName, TokenIndex: -1)); + errorReporter.RegisterTypedReferenceFromThing(inputRef.TokenIndex, InputOutputType, inputRef.Value.Value); + } + else if (tdAction.Input?.Value != null && tdAction.Input.Value.Type?.Value.Value != TDValues.TypeNull) + { + string inputSchemaName = schemaNamer.GetActionInSchema(tdAction.Input.Value, actionName); + ObjectSpec inputObjectSpec = ObjectSpec.CreateFromDataSchema(errorReporter, schemaNamer, tdAction.Input, actionForm.Format, inputSchemaName, tdAction.Input.Value.Description?.Value.Value ?? $"Input arguments for action '{actionName}'"); + if (!schemaSpecs.TryGetValue(inputSchemaName, out List? inputSpecs)) + { + inputSpecs = new List(); + schemaSpecs[inputSchemaName] = inputSpecs; + } + inputSpecs.Add(inputObjectSpec); + } + + Dictionary responseFields = new(); + ValueTracker? outputRef = tdAction.Output?.Value?.Ref; + if (outputRef != null) + { + string outputSchemaName = schemaNamer.GetActionOutSchema(null, actionName); + if (!schemaSpecs.TryGetValue(outputSchemaName, out List? outputSpecs)) + { + outputSpecs = new List(); + schemaSpecs[outputSchemaName] = outputSpecs; + } + outputSpecs.Add(new AliasSpec(null, InputOutputType, outputRef.Value.Value, actionForm.Format, outputSchemaName, dirName, TokenIndex: -1)); + errorReporter.RegisterTypedReferenceFromThing(outputRef.TokenIndex, InputOutputType, outputRef.Value.Value); + } + else if (tdAction.Output?.Value != null && tdAction.Output.Value.Type?.Value.Value != TDValues.TypeNull) + { + string outputSchemaName = schemaNamer.GetActionOutSchema(tdAction.Output.Value, actionName); + ObjectSpec outputObjectSpec = ObjectSpec.CreateFromDataSchema(errorReporter, schemaNamer, tdAction.Output, actionForm.Format, outputSchemaName, tdAction.Output.Value.Description?.Value.Value ?? $"Output arguments for action '{actionName}'"); + if (!schemaSpecs.TryGetValue(outputSchemaName, out List? outputSpecs)) + { + outputSpecs = new List(); + schemaSpecs[outputSchemaName] = outputSpecs; + } + outputSpecs.Add(outputObjectSpec); + responseFields = outputObjectSpec.Fields.ToDictionary(f => f.Key, f => f.Value with { Require = false }); + } + + if (actionForm?.ErrorRespSchema != null) + { + responseFields[schemaNamer.GetActionRespErrorField(actionName, actionForm.ErrorRespName!)] = new FieldSpec( + $"Read error for the '{actionName}' Action.", + actionForm.ErrorRespSchema, + Require: false, + BackupSchemaName: actionForm.ErrorRespName!, + Base: string.Empty); + + string respSchemaName = schemaNamer.GetActionRespSchema(actionName); + ObjectSpec propReadRespObjectSpec = new( + tdAction.Output?.Value.Description?.Value.Value ?? $"Response to a '{actionName}' Action.", + responseFields, + actionForm.Format, + respSchemaName, + TokenIndex: -1); + if (!schemaSpecs.TryGetValue(respSchemaName, out List? respSpecs)) + { + respSpecs = new List(); + schemaSpecs[respSchemaName] = respSpecs; + } + respSpecs.Add(propReadRespObjectSpec); + + SchemaGenerationSupport.AddSchemaReference(actionForm.ErrorRespName!, actionForm.ErrorRespFormat, referencedSchemas); + } + + if (actionForm?.HeaderInfoSchema != null) + { + SchemaGenerationSupport.AddSchemaReference(actionForm.HeaderInfoName!, actionForm.HeaderInfoFormat, referencedSchemas); + } + + if (actionForm?.HeaderCodeSchema != null) + { + SchemaGenerationSupport.AddSchemaReference(actionForm.HeaderCodeName!, SerializationFormat.Json, referencedSchemas); + } + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/AliasSpec.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/AliasSpec.cs new file mode 100644 index 0000000000..2fb8dbe16b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/AliasSpec.cs @@ -0,0 +1,6 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + internal record AliasSpec(string? Description, string? Type, string Ref, SerializationFormat Format, string SchemaName, string Base, long TokenIndex) : SchemaSpec(Format, TokenIndex); +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/Azure.Iot.Operations.SchemaGenerator.csproj b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/Azure.Iot.Operations.SchemaGenerator.csproj new file mode 100644 index 0000000000..f3b821762b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/Azure.Iot.Operations.SchemaGenerator.csproj @@ -0,0 +1,67 @@ + + + + net9.0 + enable + + + + + + + + + + + + + + TextTemplatingFilePreprocessor + ConstSchema.cs + Azure.Iot.Operations.SchemaGenerator + + + TextTemplatingFilePreprocessor + AliasJsonSchema.cs + Azure.Iot.Operations.SchemaGenerator + + + TextTemplatingFilePreprocessor + EnumJsonSchema.cs + Azure.Iot.Operations.SchemaGenerator + + + TextTemplatingFilePreprocessor + ObjectJsonSchema.cs + Azure.Iot.Operations.SchemaGenerator + + + + + + + + + + True + True + ConstSchema.tt + + + True + True + AliasJsonSchema.tt + + + True + True + EnumJsonSchema.tt + + + True + True + ObjectJsonSchema.tt + + + + diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/EnumSpec.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/EnumSpec.cs new file mode 100644 index 0000000000..30714a0c1b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/EnumSpec.cs @@ -0,0 +1,30 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Linq; + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal record EnumSpec(string? Description, List Values, SerializationFormat Format, string SchemaName, long TokenIndex) : SchemaSpec(Format, TokenIndex) + { + internal static EnumSpec CreateFromDataSchema(ErrorReporter errorReporter, SchemaNamer schemaNamer, ValueTracker dataSchema, SerializationFormat format, string backupName, string? defaultDescription = null) + { + string schemaName = schemaNamer.ApplyBackupSchemaName(dataSchema.Value.Title?.Value.Value, backupName); + + if (dataSchema.Value.Type?.Value.Value != TDValues.TypeString) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"Enum schema '{schemaName}' must have type 'string'.", dataSchema.TokenIndex); + } + if (dataSchema.Value.Enum?.Elements == null) + { + errorReporter.ReportError(ErrorCondition.ElementMissing, $"Enum schema '{schemaName}' must have at least one defined value.", dataSchema.TokenIndex); + } + + string? description = dataSchema.Value.Description?.Value.Value ?? defaultDescription; + List values = dataSchema.Value.Enum?.Elements != null ? dataSchema.Value.Enum.Elements.Select(e => e.Value.Value).ToList() : new(); + + return new EnumSpec(description, values, format, schemaName, dataSchema.TokenIndex); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/EventSchemaGenerator.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/EventSchemaGenerator.cs new file mode 100644 index 0000000000..3ff9621a84 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/EventSchemaGenerator.cs @@ -0,0 +1,107 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal static class EventSchemaGenerator + { + internal static void GenerateEventSchemas(ErrorReporter errorReporter, TDThing tdThing, string dirName, SchemaNamer schemaNamer, string projectName, Dictionary> schemaSpecs, Dictionary> referencedSchemas) + { + FormInfo? subAllEventsForm = FormInfo.CreateFromForm(errorReporter, tdThing.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpSubAllEvents) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + + Dictionary valueFields = new(); + + if (tdThing.Events?.Entries != null) + { + foreach (KeyValuePair> eventKvp in tdThing.Events.Entries.Where(e => e.Value.Value.Data != null)) + { + ProcessEvent( + errorReporter, + schemaNamer, + eventKvp.Key, + eventKvp.Value.Value!, + projectName, + dirName, + tdThing.SchemaDefinitions?.Entries, + schemaSpecs, + valueFields); + } + } + + GenerateCollectiveEventObject( + schemaNamer, + subAllEventsForm, + valueFields, + schemaSpecs); + } + + private static void ProcessEvent( + ErrorReporter errorReporter, + SchemaNamer schemaNamer, + string eventName, + TDEvent tdEvent, + string projectName, + string dirName, + Dictionary>? schemaDefinitions, + Dictionary> schemaSpecs, + Dictionary valueFields) + { + FormInfo? subEventForm = FormInfo.CreateFromForm(errorReporter, tdEvent.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpSubEvent) ?? false)?.Value, schemaDefinitions); + subEventForm ??= FormInfo.CreateFromForm(errorReporter, tdEvent.Forms?.Elements?.FirstOrDefault(f => f.Value.Op == null)?.Value, schemaDefinitions); + + FieldSpec dataFieldSpec = new( + tdEvent.Description?.Value.Value ?? $"The '{eventName}' Event data value.", + tdEvent.Data!, + BackupSchemaName: schemaNamer.GetEventValueSchema(eventName), + Require: true, + Base: dirName, + Fragment: tdEvent.Placeholder?.Value.Value ?? false); + valueFields[eventName] = dataFieldSpec with { Require = false }; + + if (subEventForm?.TopicPattern != null) + { + string eventSchemaName = schemaNamer.GetEventSchema(eventName); + ObjectSpec eventObjectSpec = new( + tdEvent.Description?.Value.Value ?? $"Container for the '{eventName}' Event data.", + new Dictionary { { eventName, dataFieldSpec } }, + subEventForm.Format, + eventSchemaName, + TokenIndex: -1); + if (!schemaSpecs.TryGetValue(eventSchemaName, out List? eventSpecs)) + { + eventSpecs = new List(); + schemaSpecs[eventSchemaName] = eventSpecs; + } + eventSpecs.Add(eventObjectSpec); + } + } + + private static void GenerateCollectiveEventObject( + SchemaNamer schemaNamer, + FormInfo? topLevelEventsForm, + Dictionary valueFields, + Dictionary> schemaSpecs) + { + if (topLevelEventsForm?.TopicPattern != null) + { + if (valueFields.Any()) + { + if (!schemaSpecs.TryGetValue(schemaNamer.AggregateEventSchema, out List? aggEventSpecs)) + { + aggEventSpecs = new List(); + schemaSpecs[schemaNamer.AggregateEventSchema] = aggEventSpecs; + } + aggEventSpecs.Add(new ObjectSpec( + $"Data values of Events.", + valueFields, + topLevelEventsForm.Format, + schemaNamer.AggregateEventSchema, + TokenIndex: -1)); + } + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/FieldSpec.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/FieldSpec.cs new file mode 100644 index 0000000000..35b9c2a083 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/FieldSpec.cs @@ -0,0 +1,46 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal record FieldSpec(string Description, ValueTracker Schema, bool Require, string BackupSchemaName, string Base, bool Fragment = false, bool ForceOption = false) + { + internal static FieldSpec CreateFixed(string title, string description, string backupSchemaName) + { + return new FieldSpec( + description, + new ValueTracker + { + PropertyName = string.Empty, + Value = new TDDataSchema + { + Title = new ValueTracker { PropertyName = string.Empty, Value = new StringHolder { Value = title } }, + Type = new ValueTracker { PropertyName = string.Empty, Value = new StringHolder { Value = TDValues.TypeObject } }, + }, + }, + Require: false, + backupSchemaName, + string.Empty); + } + + public override int GetHashCode() + { + return (Description, Schema, Require, BackupSchemaName, Fragment).GetHashCode(); + } + + public virtual bool Equals(FieldSpec? other) + { + if (other == null) + { + return false; + } + + return Description == other.Description && + Schema == other.Schema && + Require == other.Require && + BackupSchemaName == other.BackupSchemaName && + Fragment == other.Fragment; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/ISchemaTemplateTransform.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/ISchemaTemplateTransform.cs new file mode 100644 index 0000000000..91448a8519 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/ISchemaTemplateTransform.cs @@ -0,0 +1,13 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + internal interface ISchemaTemplateTransform + { + SerializationFormat Format { get; } + + string FileName { get; } + + string TransformText(); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/ObjectSpec.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/ObjectSpec.cs new file mode 100644 index 0000000000..dc8ddf02bc --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/ObjectSpec.cs @@ -0,0 +1,41 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Linq; + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal record ObjectSpec(string? Description, Dictionary Fields, SerializationFormat Format, string SchemaName, long TokenIndex) : SchemaSpec(Format, TokenIndex) + { + internal static ObjectSpec CreateFromDataSchema(ErrorReporter errorReporter, SchemaNamer schemaNamer, ValueTracker dataSchema, SerializationFormat format, string backupName, string? defaultDescription = null) + { + string schemaName = schemaNamer.ApplyBackupSchemaName(dataSchema.Value.Title?.Value.Value, backupName); + + if (dataSchema.Value.Type?.Value.Value != TDValues.TypeObject) + { + errorReporter.ReportError(ErrorCondition.TypeMismatch, $"Object schema '{schemaName}' must have type 'object'.", dataSchema.TokenIndex); + } + + Dictionary fieldSpecs = new(); + foreach (KeyValuePair> property in dataSchema.Value.Properties?.Entries ?? new Dictionary>()) + { + fieldSpecs[property.Key] = new FieldSpec(property.Value.Value.Description?.Value.Value ?? $"The '{property.Key}' Field.", property.Value, Require: dataSchema.Value.Required?.Elements?.Any(e => e.Value.Value == property.Key) ?? false, schemaNamer.GetBackupSchemaName(schemaName, property.Key), string.Empty); + } + + string? description = dataSchema.Value.Description?.Value.Value ?? defaultDescription; + + return new ObjectSpec(description, fieldSpecs, format, schemaName, dataSchema.TokenIndex); + } + + internal static ObjectSpec CreateFixed(SchemaNamer schemaNamer, string description, Dictionary fieldSketches, SerializationFormat format, string schemaName) + { + return new ObjectSpec( + description, + fieldSketches.ToDictionary(f => f.Key, f => FieldSpec.CreateFixed(f.Value.Item1, f.Value.Item2, schemaNamer.GetBackupSchemaName(schemaName, f.Value.Item1))), + format, + schemaName, + TokenIndex: -1); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/PropertySchemaGenerator.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/PropertySchemaGenerator.cs new file mode 100644 index 0000000000..6c0f573f36 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/PropertySchemaGenerator.cs @@ -0,0 +1,262 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal static class PropertySchemaGenerator + { + internal static void GeneratePropertySchemas(ErrorReporter errorReporter, TDThing tdThing, string dirName, SchemaNamer schemaNamer, string projectName, Dictionary> schemaSpecs, Dictionary> referencedSchemas) + { + FormInfo? readAllPropsForm = FormInfo.CreateFromForm(errorReporter, tdThing.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpReadAllProps) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + FormInfo? writeMultPropsForm = FormInfo.CreateFromForm(errorReporter, tdThing.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == TDValues.OpWriteMultProps) ?? false)?.Value, tdThing.SchemaDefinitions?.Entries); + + Dictionary readValueFields = new(); + Dictionary writeValueFields = new(); + Dictionary readErrorFields = new(); + Dictionary writeErrorFields = new(); + HashSet readErrorSchemaNames = new(); + HashSet writeErrorSchemaNames = new(); + + foreach (KeyValuePair> propKvp in tdThing.Properties?.Entries ?? new()) + { + ValueTracker property = propKvp.Value; + if (property != null) + { + ProcessProperty( + errorReporter, + schemaNamer, + propKvp.Key, + property, + projectName, + dirName, + tdThing.SchemaDefinitions?.Entries, + schemaSpecs, + readValueFields, + readErrorFields, + referencedSchemas, + readErrorSchemaNames, + isRead: true); + + ProcessProperty( + errorReporter, + schemaNamer, + propKvp.Key, + property, + projectName, + dirName, + tdThing.SchemaDefinitions?.Entries, + schemaSpecs, + writeValueFields, + writeErrorFields, + referencedSchemas, + writeErrorSchemaNames, + isRead: false); + } + } + + GenerateCollectiveResponseObject( + schemaNamer, + readAllPropsForm, + readValueFields, + readErrorFields, + schemaNamer.AggregatePropSchema, + schemaNamer.AggregatePropReadErrSchema, + schemaNamer.AggregatePropReadRespSchema, + "read", + "of", + "all", + readErrorSchemaNames, + schemaSpecs, + referencedSchemas, + responseIncludesProps: true); + GenerateCollectiveResponseObject( + schemaNamer, + writeMultPropsForm, + writeValueFields, + writeErrorFields, + schemaNamer.AggregatePropWriteSchema, + schemaNamer.AggregatePropWriteErrSchema, + schemaNamer.AggregatePropWriteRespSchema, + "write", + "for", + "multiple", + writeErrorSchemaNames, + schemaSpecs, + referencedSchemas, + responseIncludesProps: false); + } + + private static void ProcessProperty( + ErrorReporter errorReporter, + SchemaNamer schemaNamer, + string propName, + ValueTracker tdProperty, + string projectName, + string dirName, + Dictionary>? schemaDefinitions, + Dictionary> schemaSpecs, + Dictionary valueFields, + Dictionary errorFields, + Dictionary> referencedSchemas, + HashSet errorSchemaNames, + bool isRead) + { + if ((tdProperty.Value.ReadOnly?.Value.Value ?? false) && !isRead) + { + return; + } + + string operation = isRead ? TDValues.OpReadProp : TDValues.OpWriteProp; + FormInfo? propForm = FormInfo.CreateFromForm(errorReporter, tdProperty.Value.Forms?.Elements?.FirstOrDefault(f => f.Value.Op?.Elements?.Any(e => e.Value.Value == operation) ?? false)?.Value, schemaDefinitions); + propForm ??= FormInfo.CreateFromForm(errorReporter, tdProperty.Value.Forms?.Elements?.FirstOrDefault(f => f.Value.Op == null)?.Value, schemaDefinitions); + + FieldSpec propFieldSpec = new( + tdProperty.Value.Description?.Value.Value ?? (isRead ? $"The '{propName}' Property value." : $"Value for the '{propName}' Property."), + new ValueTracker { PropertyName = string.Empty, Value = tdProperty.Value as TDDataSchema, TokenIndex = tdProperty.TokenIndex }, + BackupSchemaName: schemaNamer.GetPropValueSchema(propName), + Require: isRead, + Base: dirName, + Fragment: tdProperty.Value.Placeholder?.Value.Value ?? false); + valueFields[propName] = propFieldSpec; + + if (propForm?.TopicPattern != null && (isRead || (tdProperty.Value.Placeholder?.Value.Value ?? false))) + { + string propSchemaName = isRead ? schemaNamer.GetPropSchema(propName) : schemaNamer.GetWritablePropSchema(propName); + ObjectSpec propObjectSpec = new( + tdProperty.Value.Description?.Value.Value ?? $"Container for{(isRead ? "" : " writing to")} the '{propName}' Property.", + new Dictionary { { propName, propFieldSpec } }, + propForm.Format, + propSchemaName, + TokenIndex: -1); + + if (!schemaSpecs.TryGetValue(propSchemaName, out List? propSpecs)) + { + propSpecs = new List(); + schemaSpecs[propSchemaName] = propSpecs; + } + propSpecs.Add(propObjectSpec); + } + + if (propForm?.ErrorRespSchema != null) + { + FieldSpec respFieldSpec = new( + tdProperty.Value.Description?.Value.Value ?? $"{(isRead ? "Read" : "Write")} error for the '{propName}' Property.", + propForm.ErrorRespSchema, + BackupSchemaName: propForm.ErrorRespName!, + Require: false, + Base: dirName); + errorFields[propName] = respFieldSpec; + + errorSchemaNames.Add(propForm.ErrorRespName!); + + if (propForm?.TopicPattern != null) + { + Dictionary responseFields = new(); + if (isRead) + { + responseFields[propName] = propFieldSpec with { ForceOption = true }; + responseFields[schemaNamer.GetPropReadRespErrorField(propName, propForm.ErrorRespName!)] = respFieldSpec; + } + else + { + responseFields[schemaNamer.GetPropWriteRespErrorField(propName, propForm.ErrorRespName!)] = respFieldSpec; + } + + string respSchemaName = isRead ? schemaNamer.GetPropReadRespSchema(propName) : schemaNamer.GetPropWriteRespSchema(propName); + ObjectSpec respObjectSpec = new( + tdProperty.Value.Description?.Value.Value ?? $"Response to a '{propName}' Property {(isRead ? "read" : "write")}.", + responseFields, + propForm.Format, + respSchemaName, + TokenIndex: -1); + + if (!schemaSpecs.TryGetValue(respSchemaName, out List? respSpecs)) + { + respSpecs = new List(); + schemaSpecs[respSchemaName] = respSpecs; + } + respSpecs.Add(respObjectSpec); + + SchemaGenerationSupport.AddSchemaReference(propForm.ErrorRespName!, propForm.ErrorRespFormat, referencedSchemas); + } + } + } + + private static void GenerateCollectiveResponseObject( + SchemaNamer schemaNamer, + FormInfo? topLevelPropsForm, + Dictionary valueFields, + Dictionary errorFields, + string propsSchema, + string errorSchema, + string responseSchema, + string operation, + string preposition, + string quantifier, + HashSet errorSchemaNames, + Dictionary> schemaSpecs, + Dictionary> referencedSchemas, + bool responseIncludesProps) + { + if (topLevelPropsForm?.TopicPattern != null) + { + if (valueFields.Any()) + { + if (!schemaSpecs.TryGetValue(propsSchema, out List? propSpecs)) + { + propSpecs = new List(); + schemaSpecs[propsSchema] = propSpecs; + } + propSpecs.Add(new ObjectSpec( + $"Values {preposition} {quantifier} Properties.", + valueFields, + topLevelPropsForm.Format, + propsSchema, + TokenIndex: -1)); + } + + if (topLevelPropsForm.HasErrorResponse) + { + if (!schemaSpecs.TryGetValue(errorSchema, out List? errorSpecs)) + { + errorSpecs = new List(); + schemaSpecs[errorSchema] = errorSpecs; + } + errorSpecs.Add(new ObjectSpec( + $"Errors from any Property {operation}.", + errorFields, + topLevelPropsForm.Format, + errorSchema, + TokenIndex: -1)); + + Dictionary fieldSketches = new(); + fieldSketches[schemaNamer.AggregateRespErrorField] = (errorSchema, "Errors when operation fails."); + if (responseIncludesProps) + { + fieldSketches[schemaNamer.AggregateReadRespValueField] = (propsSchema, "Properties when operation succeeds."); + } + + if (!schemaSpecs.TryGetValue(responseSchema, out List? responseSpecs)) + { + responseSpecs = new List(); + schemaSpecs[propsSchema] = responseSpecs; + } + responseSpecs.Add(ObjectSpec.CreateFixed( + schemaNamer, + $"Response to {operation} of {quantifier} Properties", + fieldSketches, + topLevelPropsForm.Format, + responseSchema)); + + foreach (string errSchemaName in errorSchemaNames) + { + SchemaGenerationSupport.AddSchemaReference(errSchemaName, topLevelPropsForm.Format, referencedSchemas); + } + } + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaGenerationSupport.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaGenerationSupport.cs new file mode 100644 index 0000000000..85968fd0ae --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaGenerationSupport.cs @@ -0,0 +1,19 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + + internal static class SchemaGenerationSupport + { + internal static void AddSchemaReference(string schemaName, SerializationFormat format, Dictionary> referencedSchemas) + { + if (!referencedSchemas.TryGetValue(schemaName, out HashSet? formats) || formats == null) + { + formats = new HashSet(); + referencedSchemas[schemaName] = formats; + } + + formats.Add(format); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaGenerator.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaGenerator.cs new file mode 100644 index 0000000000..0ba789fd45 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaGenerator.cs @@ -0,0 +1,210 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + public static class SchemaGenerator + { + public static Dictionary> GenerateSchemas(List parsedThings, string projectName, DirectoryInfo workingDir) + { + Dictionary transforms = new(); + + foreach (ParsedThing parsedThing in parsedThings) + { + Dictionary> schemaSpecs = new(); + Dictionary> referencedSchemas = new(); + + PropertySchemaGenerator.GeneratePropertySchemas(parsedThing.ErrorReporter, parsedThing.Thing, parsedThing.DirectoryName, parsedThing.SchemaNamer, projectName, schemaSpecs, referencedSchemas); + ActionSchemaGenerator.GenerateActionSchemas(parsedThing.ErrorReporter, parsedThing.Thing, parsedThing.DirectoryName, parsedThing.SchemaNamer, projectName, schemaSpecs, referencedSchemas); + EventSchemaGenerator.GenerateEventSchemas(parsedThing.ErrorReporter, parsedThing.Thing, parsedThing.DirectoryName, parsedThing.SchemaNamer, projectName, schemaSpecs, referencedSchemas); + + Dictionary closedSchemaSpecs = ComputeClosedSchemaSpecs(parsedThing.ErrorReporter, parsedThing.Thing, parsedThing.SchemaNamer, schemaSpecs, referencedSchemas); + + SchemaTransformFactory transformFactory = new(parsedThing.SchemaNamer, workingDir); + + foreach (KeyValuePair schemaSpec in closedSchemaSpecs) + { + if (transformFactory.TryGetSchemaTransform(schemaSpec.Key, schemaSpec.Value, out ISchemaTemplateTransform? transform)) + { + transforms[transform.FileName] = transform; + } + } + } + + Dictionary> generatedSchemas = new(); + + foreach (KeyValuePair transform in transforms) + { + if (!generatedSchemas.TryGetValue(transform.Value.Format, out List? schemas)) + { + schemas = new(); + generatedSchemas[transform.Value.Format] = schemas; + } + + schemas.Add(new GeneratedItem(transform.Value.TransformText(), transform.Key)); + } + + return generatedSchemas; + } + + private static Dictionary ComputeClosedSchemaSpecs(ErrorReporter errorReporter, TDThing thing, SchemaNamer schemaNamer, Dictionary> schemaSpecs, Dictionary> referencedSchemas) + { + Dictionary closedSchemaSpecs = new(); + + foreach (KeyValuePair> referencedSchema in referencedSchemas) + { + foreach (SerializationFormat format in referencedSchema.Value) + { + if (thing.SchemaDefinitions?.Entries?.TryGetValue(referencedSchema.Key, out ValueTracker? dataSchema) ?? false) + { + ComputeClosureOfDataSchema(errorReporter, schemaNamer, thing.Title!.Value.Value, referencedSchema.Key, dataSchema, format, closedSchemaSpecs); + } + } + } + + foreach (KeyValuePair> schemaSpec in schemaSpecs) + { + foreach (SchemaSpec spec in schemaSpec.Value) + { + ComputeClosureOfSchemaSpec(errorReporter, schemaNamer, thing.Title!.Value.Value, schemaSpec.Key, spec, closedSchemaSpecs); + } + } + + return closedSchemaSpecs; + } + + private static void ComputeClosureOfSchemaSpec(ErrorReporter errorReporter, SchemaNamer schemaNamer, string thingName, string schemaName, SchemaSpec schemaSpec, Dictionary closedSchemaSpecs) + { + if (IsLocalDuplicate(errorReporter, schemaName, schemaSpec, closedSchemaSpecs)) + { + return; + } + + closedSchemaSpecs[schemaName] = schemaSpec; + errorReporter.RegisterNameInThing(schemaName, thingName, schemaSpec.TokenIndex); + + if (schemaSpec is ObjectSpec objectSpec) + { + foreach (KeyValuePair field in objectSpec.Fields) + { + ComputeClosureOfDataSchema(errorReporter, schemaNamer, thingName, field.Value.BackupSchemaName, field.Value.Schema, schemaSpec.Format, closedSchemaSpecs); + } + } + } + + private static void ComputeClosureOfDataSchema(ErrorReporter errorReporter, SchemaNamer schemaNamer, string thingName, string backupName, ValueTracker dataSchema, SerializationFormat format, Dictionary closedSchemaSpecs) + { + if (IsProxy(dataSchema.Value)) + { + return; + } + + string schemaName = schemaNamer.ApplyBackupSchemaName(dataSchema.Value.Title?.Value.Value, backupName); + + if (SchemaSpec.TryCreateFromDataSchema(errorReporter, schemaNamer, dataSchema, format, backupName, out SchemaSpec? schemaSpec)) + { + if (IsLocalDuplicate(errorReporter, schemaName, schemaSpec, closedSchemaSpecs)) + { + return; + } + + closedSchemaSpecs[schemaName] = schemaSpec; + errorReporter.RegisterNameInThing(schemaName, thingName, schemaSpec.TokenIndex); + } + + if (dataSchema.Value.Properties?.Entries != null) + { + foreach (KeyValuePair> property in dataSchema.Value.Properties.Entries) + { + ComputeClosureOfDataSchema(errorReporter, schemaNamer, thingName, schemaNamer.GetBackupSchemaName(schemaName, property.Key), property.Value, format, closedSchemaSpecs); + } + } + else if (dataSchema.Value.Items?.Value != null) + { + ComputeClosureOfDataSchema(errorReporter, schemaNamer, thingName, backupName, dataSchema.Value.Items, format, closedSchemaSpecs); + } + else if (dataSchema.Value.AdditionalProperties?.Value != null) + { + ComputeClosureOfDataSchema(errorReporter, schemaNamer, thingName, backupName, dataSchema.Value.AdditionalProperties, format, closedSchemaSpecs); + } + } + + private static bool IsProxy(TDDataSchema dataSchema) + { + return dataSchema.Type?.Value.Value == TDValues.TypeObject && dataSchema.AdditionalProperties == null && dataSchema.Properties == null; + } + + private static bool IsLocalDuplicate(ErrorReporter errorReporter, string schemaName, SchemaSpec schemaSpec, Dictionary closedSchemaSpecs) + { + if (!closedSchemaSpecs.TryGetValue(schemaName, out SchemaSpec? existingSpec)) + { + return false; + } + + if (existingSpec.GetType() != schemaSpec.GetType()) + { + errorReporter.ReportError(ErrorCondition.Duplication, $"Schema name {schemaName} is duplicated on schema with different type.", schemaSpec.TokenIndex, existingSpec.TokenIndex); + return false; + } + else if (existingSpec is ObjectSpec existingObjectSpec && schemaSpec is ObjectSpec newObjectSpec) + { + foreach (KeyValuePair field in existingObjectSpec.Fields) + { + if (!newObjectSpec.Fields.TryGetValue(field.Key, out FieldSpec? newField)) + { + errorReporter.ReportError(ErrorCondition.Duplication, $"Schema name {schemaName} is duplicated but schema has field '{field.Key}' not present in other schema.", field.Value.Schema.TokenIndex, newObjectSpec.TokenIndex); + return false; + } + } + + foreach (KeyValuePair field in newObjectSpec.Fields) + { + if (!existingObjectSpec.Fields.TryGetValue(field.Key, out FieldSpec? extantField)) + { + errorReporter.ReportError(ErrorCondition.Duplication, $"Schema name {schemaName} is duplicated but schema has field '{field.Key}' not present in other schema.", field.Value.Schema.TokenIndex, existingObjectSpec.TokenIndex); + return false; + } + } + + foreach (KeyValuePair field in newObjectSpec.Fields) + { + FieldSpec existingFieldValue = existingObjectSpec.Fields[field.Key]; + if (!field.Value.Equals(existingFieldValue)) + { + errorReporter.ReportError(ErrorCondition.Duplication, $"Schema name {schemaName} is duplicated but field '{field.Key}' has different value.", field.Value.Schema.TokenIndex, existingFieldValue.Schema.TokenIndex); + return false; + } + } + + return true; + } + else if (existingSpec is EnumSpec existingEnumSpec && schemaSpec is EnumSpec newEnumSpec) + { + foreach (string value in existingEnumSpec.Values) + { + if (!newEnumSpec.Values.Contains(value)) + { + errorReporter.ReportError(ErrorCondition.Duplication, $"Schema name {schemaName} is duplicated but schema has enum value '{value}' not present in other schema.", existingEnumSpec.TokenIndex, newEnumSpec.TokenIndex); + return false; + } + } + + foreach (string value in newEnumSpec.Values) + { + if (!existingEnumSpec.Values.Contains(value)) + { + errorReporter.ReportError(ErrorCondition.Duplication, $"Schema name {schemaName} is duplicated but schema has enum value '{value}' not present in other schema.", newEnumSpec.TokenIndex, existingEnumSpec.TokenIndex); + return false; + } + } + + return true; + } + + return false; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaSpec.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaSpec.cs new file mode 100644 index 0000000000..a565f6c99c --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaSpec.cs @@ -0,0 +1,29 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Diagnostics.CodeAnalysis; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal record SchemaSpec(SerializationFormat Format, long TokenIndex) + { + internal static bool TryCreateFromDataSchema(ErrorReporter errorReporter, SchemaNamer schemaNamer, ValueTracker dataSchema, SerializationFormat format, string backupName, [NotNullWhen(true)] out SchemaSpec? schemaSpec) + { + if (dataSchema.Value.Type?.Value.Value == TDValues.TypeObject && dataSchema.Value.AdditionalProperties == null) + { + schemaSpec = ObjectSpec.CreateFromDataSchema(errorReporter, schemaNamer, dataSchema, format, backupName); + return true; + } + else if (dataSchema.Value.Enum != null) + { + schemaSpec = EnumSpec.CreateFromDataSchema(errorReporter, schemaNamer, dataSchema, format, backupName); + return true; + } + else + { + schemaSpec = null; + return false; + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaTransformFactory.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaTransformFactory.cs new file mode 100644 index 0000000000..e1fcaaa589 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/SchemaTransformFactory.cs @@ -0,0 +1,71 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Diagnostics.CodeAnalysis; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + internal class SchemaTransformFactory + { + private readonly JsonSchemaSupport jsonSchemaSupport; + + internal SchemaTransformFactory(SchemaNamer schemaNamer, DirectoryInfo workingDir) + { + this.jsonSchemaSupport = new JsonSchemaSupport(schemaNamer, workingDir); + } + + internal bool TryGetSchemaTransform(string schemaName, SchemaSpec schemaSpec, [NotNullWhen(true)] out ISchemaTemplateTransform? transform) + { + switch (schemaSpec) + { + case ObjectSpec objectSpec: + return TryGetObjectSchemaTransform(schemaName, objectSpec, out transform); + case EnumSpec enumSpec: + return TryGetEnumSchemaTransform(schemaName, enumSpec, out transform); + case AliasSpec aliasSpec: + return GetAliasSchemaTransform(schemaName, aliasSpec, out transform); + default: + transform = null; + return false; + } + } + + internal bool TryGetObjectSchemaTransform(string schemaName, ObjectSpec objectSpec, [NotNullWhen(true)] out ISchemaTemplateTransform? transform) + { + switch (objectSpec.Format) + { + case SerializationFormat.Json: + transform = new ObjectJsonSchema(this.jsonSchemaSupport, schemaName, objectSpec); + return true; + default: + transform = null; + return false; + } + } + + internal bool TryGetEnumSchemaTransform(string schemaName, EnumSpec enumSpec, [NotNullWhen(true)] out ISchemaTemplateTransform? transform) + { + switch (enumSpec.Format) + { + case SerializationFormat.Json: + transform = new EnumJsonSchema(schemaName, enumSpec); + return true; + default: + transform = null; + return false; + } + } + + internal bool GetAliasSchemaTransform(string schemaName, AliasSpec aliasSpec, [NotNullWhen(true)] out ISchemaTemplateTransform? transform) + { + switch (aliasSpec.Format) + { + case SerializationFormat.Json: + transform = new AliasJsonSchema(this.jsonSchemaSupport, schemaName, aliasSpec); + return true; + default: + transform = null; + return false; + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/JsonSchemaSupport.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/JsonSchemaSupport.cs new file mode 100644 index 0000000000..290adc6183 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/JsonSchemaSupport.cs @@ -0,0 +1,79 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.IO; + using System.Text.RegularExpressions; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal class JsonSchemaSupport + { + internal const string JsonSchemaSuffix = "schema.json"; + + private const string Iso8601DurationExample = "P3Y6M4DT12H30M5S"; + private const string DecimalExample = "1234567890.0987654321"; + private const string AnArbitraryString = "Pretty12345Tricky67890"; + private const string DecimalPattern = @"^(?:\\+|-)?(?:[1-9][0-9]*|0)(?:\\.[0-9]*)?$"; + + private readonly SchemaNamer schemaNamer; + private readonly DirectoryInfo workingDir; + + internal JsonSchemaSupport(SchemaNamer schemaNamer, DirectoryInfo workingDir) + { + this.schemaNamer = schemaNamer; + this.workingDir = workingDir; + } + + internal string GetFragmented(string typeAndAddenda, bool require) + { + string addProps = require ? typeAndAddenda : $"\"anyOf\": [ {{ \"type\": \"null\" }}, {{ {typeAndAddenda} }} ]"; + return $"\"type\": \"object\", \"additionalProperties\": {{ {addProps} }}"; + } + + internal string GetReferencePath(string reference, string refBase) + { + return reference.Contains('/') ? Path.GetRelativePath(this.workingDir.FullName, Path.Combine(refBase, reference)).Replace('\\', '/') : $"./{reference}"; + } + + internal string GetTypeAndAddenda(ValueTracker tdSchema, string backupSchemaName, string refBase) + { + if (tdSchema.Value.Ref?.Value != null) + { + return $"\"$ref\": \"{GetReferencePath(tdSchema.Value.Ref.Value.Value, refBase)}\""; + } + + if ((tdSchema.Value.Type?.Value.Value == TDValues.TypeObject && tdSchema.Value.AdditionalProperties?.Value == null) || + (tdSchema.Value.Type?.Value.Value == TDValues.TypeString && tdSchema.Value.Enum != null)) + { + return $"\"$ref\": \"{this.schemaNamer.ApplyBackupSchemaName(tdSchema.Value.Title?.Value.Value, backupSchemaName)}.{JsonSchemaSuffix}\""; + } + + switch (tdSchema.Value.Type?.Value.Value ?? string.Empty) + { + case TDValues.TypeObject: + return $"\"type\": \"object\", \"additionalProperties\": {{ {GetTypeAndAddenda(tdSchema.Value.AdditionalProperties!, backupSchemaName, refBase)} }}"; + case TDValues.TypeArray: + string itemsProp = tdSchema.Value.Items?.Value != null ? $", \"items\": {{ {GetTypeAndAddenda(tdSchema.Value.Items, backupSchemaName, refBase)} }}" : string.Empty; + return $"\"type\": \"array\"{itemsProp}"; + case TDValues.TypeString: + string formatProp = TDValues.FormatValues.Contains(tdSchema.Value.Format?.Value.Value ?? string.Empty) ? $", \"format\": \"{tdSchema.Value.Format!.Value.Value}\"" : + tdSchema.Value.Pattern?.Value != null && Regex.IsMatch(Iso8601DurationExample, tdSchema.Value.Pattern.Value.Value) && !Regex.IsMatch(AnArbitraryString, tdSchema.Value.Pattern.Value.Value) ? @", ""format"": ""duration""" : string.Empty; + string patternProp = tdSchema.Value.Pattern?.Value != null && Regex.IsMatch(DecimalExample, tdSchema.Value.Pattern.Value.Value) && !Regex.IsMatch(AnArbitraryString, tdSchema.Value.Pattern.Value.Value) ? $", \"pattern\": \"{DecimalPattern}\"" : string.Empty; + string encodingProp = tdSchema.Value.ContentEncoding?.Value.Value == TDValues.ContentEncodingBase64 ? @", ""contentEncoding"": ""base64""" : string.Empty; + string enumProp = tdSchema.Value.Enum != null ? $", \"enum\": [ {string.Join(", ", $"\"{tdSchema.Value.Enum}\"")} ]" : string.Empty; + return $"\"type\": \"string\"{formatProp}{patternProp}{encodingProp}{enumProp}"; + case TDValues.TypeNumber: + string numberFormat = tdSchema.Value.Minimum?.Value.Value >= -3.40e+38 && tdSchema.Value.Maximum?.Value.Value <= 3.40e+38 ? "float" : "double"; + return $"\"type\": \"number\", \"format\": \"{numberFormat}\""; + case TDValues.TypeInteger: + string minProp = tdSchema.Value.Minimum?.Value != null ? $", \"minimum\": {(long)tdSchema.Value.Minimum.Value.Value}" : string.Empty; + string maxProp = tdSchema.Value.Maximum?.Value != null ? $", \"maximum\": {(long)tdSchema.Value.Maximum.Value.Value}" : string.Empty; + return $"\"type\": \"integer\"{minProp}{maxProp}"; + case TDValues.TypeBoolean: + return @"""type"": ""boolean"""; + } + + return @"""type"": ""null"""; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/code/AliasJsonSchema.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/code/AliasJsonSchema.cs new file mode 100644 index 0000000000..21135194e4 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/code/AliasJsonSchema.cs @@ -0,0 +1,23 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class AliasJsonSchema : ISchemaTemplateTransform + { + private readonly JsonSchemaSupport schemaSupport; + private readonly string schemaName; + private readonly AliasSpec aliasSpec; + + internal AliasJsonSchema(JsonSchemaSupport schemaSupport, string schemaName, AliasSpec aliasSpec) + { + this.schemaSupport = schemaSupport; + this.schemaName = schemaName; + this.aliasSpec = aliasSpec; + } + + public SerializationFormat Format { get => SerializationFormat.Json; } + + public string FileName { get => $"{this.schemaName}.{JsonSchemaSupport.JsonSchemaSuffix}"; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/code/EnumJsonSchema.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/code/EnumJsonSchema.cs new file mode 100644 index 0000000000..e53aa7a555 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/code/EnumJsonSchema.cs @@ -0,0 +1,20 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class EnumJsonSchema : ISchemaTemplateTransform + { + string schemaName; + EnumSpec enumSpec; + + internal EnumJsonSchema(string schemaName, EnumSpec enumSpec) + { + this.schemaName = schemaName; + this.enumSpec = enumSpec; + } + + public SerializationFormat Format { get => SerializationFormat.Json; } + + public string FileName { get => $"{this.schemaName}.{JsonSchemaSupport.JsonSchemaSuffix}"; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/code/ObjectJsonSchema.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/code/ObjectJsonSchema.cs new file mode 100644 index 0000000000..92259e861d --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/code/ObjectJsonSchema.cs @@ -0,0 +1,22 @@ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class ObjectJsonSchema : ISchemaTemplateTransform + { + private readonly JsonSchemaSupport schemaSupport; + private readonly string schemaName; + private readonly ObjectSpec objectSpec; + + internal ObjectJsonSchema(JsonSchemaSupport schemaSupport, string schemaName, ObjectSpec objectSpec) + { + this.schemaSupport = schemaSupport; + this.schemaName = schemaName; + this.objectSpec = objectSpec; + } + + public SerializationFormat Format { get => SerializationFormat.Json; } + + public string FileName { get => $"{this.schemaName}.{JsonSchemaSupport.JsonSchemaSuffix}"; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/AliasJsonSchema.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/AliasJsonSchema.cs new file mode 100644 index 0000000000..7dd6e68048 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/AliasJsonSchema.cs @@ -0,0 +1,316 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class AliasJsonSchema : AliasJsonSchemaBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("{\r\n \"$schema\": \"https://json-schema.org/draft-07/schema\",\r\n \"title\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName)); + this.Write("\",\r\n"); + if (this.aliasSpec.Type != null) { + this.Write(" \"type\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasSpec.Type)); + this.Write("\",\r\n"); + } + if (this.aliasSpec.Description != null) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasSpec.Description)); + this.Write("\",\r\n"); + } + this.Write(" \"$ref\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaSupport.GetReferencePath(this.aliasSpec.Ref, this.aliasSpec.Base))); + this.Write("\"\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class AliasJsonSchemaBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/AliasJsonSchema.tt b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/AliasJsonSchema.tt new file mode 100644 index 0000000000..241562c955 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/AliasJsonSchema.tt @@ -0,0 +1,12 @@ +<#@ template language="C#" linePragmas="false" #> +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "<#=this.schemaName#>", +<# if (this.aliasSpec.Type != null) { #> + "type": "<#=this.aliasSpec.Type#>", +<# } #> +<# if (this.aliasSpec.Description != null) { #> + "description": "<#=this.aliasSpec.Description#>", +<# } #> + "$ref": "<#=this.schemaSupport.GetReferencePath(this.aliasSpec.Ref, this.aliasSpec.Base)#>" +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/EnumJsonSchema.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/EnumJsonSchema.cs new file mode 100644 index 0000000000..64c434e810 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/EnumJsonSchema.cs @@ -0,0 +1,319 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Collections.Generic; + using System.Linq; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class EnumJsonSchema : EnumJsonSchemaBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("{\r\n \"$schema\": \"https://json-schema.org/draft-07/schema\",\r\n \"title\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName)); + this.Write("\",\r\n \"type\": \"string\",\r\n"); + if (this.enumSpec.Description != null) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.enumSpec.Description)); + this.Write("\",\r\n"); + } + this.Write(" \"enum\": [\r\n"); + int ix = 1; foreach (string value in this.enumSpec.Values) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(value)); + this.Write("\""); + this.Write(this.ToStringHelper.ToStringWithCulture(ix < this.enumSpec.Values.Count ? "," : "")); + this.Write("\r\n"); + ix++; } + this.Write(" ]\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class EnumJsonSchemaBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/EnumJsonSchema.tt b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/EnumJsonSchema.tt new file mode 100644 index 0000000000..526f23d179 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/EnumJsonSchema.tt @@ -0,0 +1,16 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "<#=this.schemaName#>", + "type": "string", +<# if (this.enumSpec.Description != null) { #> + "description": "<#=this.enumSpec.Description#>", +<# } #> + "enum": [ +<# int ix = 1; foreach (string value in this.enumSpec.Values) { #> + "<#=value#>"<#=ix < this.enumSpec.Values.Count ? "," : ""#> +<# ix++; } #> + ] +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/ObjectJsonSchema.cs b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/ObjectJsonSchema.cs new file mode 100644 index 0000000000..d54ab6b507 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/ObjectJsonSchema.cs @@ -0,0 +1,337 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.SchemaGenerator +{ + using System.Collections.Generic; + using System.Linq; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class ObjectJsonSchema : ObjectJsonSchemaBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("{\r\n \"$schema\": \"https://json-schema.org/draft-07/schema\",\r\n \"title\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaName)); + this.Write("\",\r\n"); + if (this.objectSpec.Description != null) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.objectSpec.Description)); + this.Write("\",\r\n"); + } + this.Write(" \"type\": \"object\",\r\n \"additionalProperties\": false,\r\n"); + if (this.objectSpec.Fields.Any(f => f.Value.Require || f.Value.Fragment)) { + this.Write(" \"required\": [ "); + this.Write(this.ToStringHelper.ToStringWithCulture(string.Join(", ", this.objectSpec.Fields.Where(f => (f.Value.Require || f.Value.Fragment) && !f.Value.ForceOption).Select(f => $"\"{f.Key}\"")))); + this.Write(" ],\r\n"); + } + this.Write(" \"properties\": {\r\n"); + int ix = 1; foreach (KeyValuePair field in this.objectSpec.Fields.OrderBy(field => field.Key)) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(field.Key)); + this.Write("\": {\r\n \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(field.Value.Description)); + this.Write("\",\r\n"); + if (field.Value.Fragment) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaSupport.GetFragmented(this.schemaSupport.GetTypeAndAddenda(field.Value.Schema, field.Value.BackupSchemaName, field.Value.Base), field.Value.Require))); + this.Write("\r\n"); + } else { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaSupport.GetTypeAndAddenda(field.Value.Schema, field.Value.BackupSchemaName, field.Value.Base))); + this.Write("\r\n"); + } + this.Write(" }"); + this.Write(this.ToStringHelper.ToStringWithCulture(ix < this.objectSpec.Fields.Count ? "," : "")); + this.Write("\r\n"); + ix++; } + this.Write(" }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class ObjectJsonSchemaBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/ObjectJsonSchema.tt b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/ObjectJsonSchema.tt new file mode 100644 index 0000000000..1fc3df3ce4 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.SchemaGenerator/json/t4/ObjectJsonSchema.tt @@ -0,0 +1,27 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "<#=this.schemaName#>", +<# if (this.objectSpec.Description != null) { #> + "description": "<#=this.objectSpec.Description#>", +<# } #> + "type": "object", + "additionalProperties": false, +<# if (this.objectSpec.Fields.Any(f => f.Value.Require || f.Value.Fragment)) { #> + "required": [ <#=string.Join(", ", this.objectSpec.Fields.Where(f => (f.Value.Require || f.Value.Fragment) && !f.Value.ForceOption).Select(f => $"\"{f.Key}\""))#> ], +<# } #> + "properties": { +<# int ix = 1; foreach (KeyValuePair field in this.objectSpec.Fields.OrderBy(field => field.Key)) { #> + "<#=field.Key#>": { + "description": "<#=field.Value.Description#>", +<# if (field.Value.Fragment) { #> + <#=this.schemaSupport.GetFragmented(this.schemaSupport.GetTypeAndAddenda(field.Value.Schema, field.Value.BackupSchemaName, field.Value.Base), field.Value.Require)#> +<# } else { #> + <#=this.schemaSupport.GetTypeAndAddenda(field.Value.Schema, field.Value.BackupSchemaName, field.Value.Base)#> +<# } #> + }<#=ix < this.objectSpec.Fields.Count ? "," : ""#> +<# ix++; } #> + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/ArrayTracker.cs b/codegen2/src/Azure.Iot.Operations.TDParser/ArrayTracker.cs new file mode 100644 index 0000000000..d4a65eb5c5 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/ArrayTracker.cs @@ -0,0 +1,135 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class ArrayTracker : IEquatable>, ISourceTracker + where T : IDeserializable + { + public List>? Elements { get; set; } + + public bool DeserializingFailed { get => false; } + + public string? DeserializationError { get => null; } + + public long TokenIndex { get; set; } + + public virtual bool Equals(ArrayTracker? other) + { + if (other == null) + { + return false; + } + if (Elements == null && other.Elements == null) + { + return true; + } + if (Elements == null || other.Elements == null) + { + return false; + } + if (Elements.Count != other.Elements.Count) + { + return false; + } + for (int i = 0; i < Elements.Count; i++) + { + if (Elements[i] != other.Elements[i]) + { + return false; + } + } + return true; + } + + public override int GetHashCode() + { + return Elements != null ? Elements.GetHashCode() : 0; + } + + public static bool operator ==(ArrayTracker? left, ArrayTracker? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(ArrayTracker? left, ArrayTracker? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not ArrayTracker other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + yield return this; + + if (Elements != null) + { + foreach (ValueTracker element in Elements) + { + foreach (ITraversable item in element.Traverse()) + { + yield return item; + } + } + } + } + + public static ArrayTracker Deserialize(ref Utf8JsonReader reader, string propertyName) + { + long tokenIndex = reader.TokenStartIndex; + + if (reader.TokenType != JsonTokenType.StartArray) + { + ValueTracker valueTracker = ValueTracker.Deserialize(ref reader, propertyName); + + return new ArrayTracker + { + Elements = new List> { valueTracker }, + TokenIndex = tokenIndex, + }; + } + + List> elements = new(); + + reader.Read(); + while (reader.TokenType != JsonTokenType.EndArray) + { + elements.Add(ValueTracker.Deserialize(ref reader, propertyName)); + reader.Read(); + } + + return new ArrayTracker + { + Elements = elements, + TokenIndex = tokenIndex, + }; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Azure.Iot.Operations.TDParser.csproj b/codegen2/src/Azure.Iot.Operations.TDParser/Azure.Iot.Operations.TDParser.csproj new file mode 100644 index 0000000000..0e0aa05263 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Azure.Iot.Operations.TDParser.csproj @@ -0,0 +1,8 @@ + + + + net9.0 + enable + + + diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/BaseHolder.cs b/codegen2/src/Azure.Iot.Operations.TDParser/BaseHolder.cs new file mode 100644 index 0000000000..b1672975a3 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/BaseHolder.cs @@ -0,0 +1,64 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System; + + public class BaseHolder : IEquatable> + where T : IEquatable + { + public required T Value { get; set; } + + public virtual bool Equals(BaseHolder? other) + { + if (other == null) + { + return false; + } + else + { + return Value.Equals(other.Value); + } + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + + public static bool operator ==(BaseHolder? left, BaseHolder? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(BaseHolder? left, BaseHolder? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not BaseHolder other) + { + return false; + } + else + { + return Equals(other); + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/BoolHolder.cs b/codegen2/src/Azure.Iot.Operations.TDParser/BoolHolder.cs new file mode 100644 index 0000000000..7ef69a8345 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/BoolHolder.cs @@ -0,0 +1,36 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class BoolHolder : BaseHolder, IEquatable, IDeserializable + { + public virtual bool Equals(BoolHolder? other) + { + if (other == null) + { + return false; + } + else + { + return Value == other.Value; + } + } + + public static BoolHolder Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.True && reader.TokenType != JsonTokenType.False) + { + throw new InvalidOperationException($"expected JSON bool but found {reader.TokenType}"); + } + + return new BoolHolder { Value = reader.GetBoolean()! }; + } + + public IEnumerable Traverse() + { + yield break; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/IDeserializable.cs b/codegen2/src/Azure.Iot.Operations.TDParser/IDeserializable.cs new file mode 100644 index 0000000000..ad1aa04ac9 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/IDeserializable.cs @@ -0,0 +1,10 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System.Text.Json; + + public interface IDeserializable : ITraversable + where T : IDeserializable + { + static abstract T Deserialize(ref Utf8JsonReader reader); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/ISourceTracker.cs b/codegen2/src/Azure.Iot.Operations.TDParser/ISourceTracker.cs new file mode 100644 index 0000000000..5d82f51539 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/ISourceTracker.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TDParser +{ + public interface ISourceTracker : ITraversable + { + bool DeserializingFailed { get; } + + string? DeserializationError { get; } + + long TokenIndex { get; } + } +} + \ No newline at end of file diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/ITraversable.cs b/codegen2/src/Azure.Iot.Operations.TDParser/ITraversable.cs new file mode 100644 index 0000000000..40ce4233f0 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/ITraversable.cs @@ -0,0 +1,9 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System.Collections.Generic; + + public interface ITraversable + { + IEnumerable Traverse(); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/MapTracker.cs b/codegen2/src/Azure.Iot.Operations.TDParser/MapTracker.cs new file mode 100644 index 0000000000..34177479f1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/MapTracker.cs @@ -0,0 +1,145 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class MapTracker : IEquatable>, ISourceTracker + where T : IDeserializable + { + public Dictionary>? Entries { get; set; } + + public bool DeserializingFailed { get; set; } + + public string? DeserializationError { get; set; } + + public long TokenIndex { get; set; } + + public virtual bool Equals(MapTracker? other) + { + if (other == null) + { + return false; + } + if (Entries == null && other.Entries == null) + { + return true; + } + if (Entries == null || other.Entries == null) + { + return false; + } + if (Entries.Count != other.Entries.Count) + { + return false; + } + foreach (var kvp in Entries) + { + if (!other.Entries.TryGetValue(kvp.Key, out var otherValue) || kvp.Value != otherValue) + { + return false; + } + } + return true; + } + + public override int GetHashCode() + { + return Entries != null ? Entries.GetHashCode() : 0; + } + + public static bool operator ==(MapTracker? left, MapTracker? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(MapTracker? left, MapTracker? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not MapTracker other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + yield return this; + + if (Entries != null) + { + foreach (ValueTracker entryValue in Entries.Values) + { + foreach (ITraversable item in entryValue.Traverse()) + { + yield return item; + } + } + } + } + + public static MapTracker Deserialize(ref Utf8JsonReader reader, string propertyName) + { + long tokenIndex = reader.TokenStartIndex; + string? deserializationError = null; + + if (reader.TokenType != JsonTokenType.StartObject) + { + return new MapTracker + { + DeserializingFailed = true, + DeserializationError = $"expected JSON object but found {reader.TokenType}", + TokenIndex = tokenIndex, + }; + } + + Dictionary> entries = new(); + + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string keyName = reader.GetString()!; + reader.Read(); + + if (entries.ContainsKey(keyName)) + { + deserializationError = $"duplicate property name '{keyName}' found in map"; + } + + entries[keyName] = ValueTracker.Deserialize(ref reader, propertyName); + reader.Read(); + } + + return new MapTracker + { + DeserializingFailed = deserializationError != null, + DeserializationError = deserializationError, + Entries = entries, + TokenIndex = tokenIndex, + }; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDAction.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDAction.cs new file mode 100644 index 0000000000..4e887be189 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDAction.cs @@ -0,0 +1,211 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class TDAction : IEquatable, IDeserializable + { + public const string DescriptionName = TDCommon.DescriptionName; + public const string InputName = "input"; + public const string OutputName = "output"; + public const string IdempotentName = "idempotent"; + public const string SafeName = "safe"; + public const string FormsName = TDCommon.FormsName; + public const string NamespaceName = TDCommon.NamespaceName; + public const string MemberOfName = "aov:memberOf"; + + public ValueTracker? Description { get; set; } + + public ValueTracker? Input { get; set; } + + public ValueTracker? Output { get; set; } + + public ValueTracker? Idempotent { get; set; } + + public ValueTracker? Safe { get; set; } + + public ArrayTracker? Forms { get; set; } + + public ValueTracker? Namespace { get; set; } + + public ValueTracker? MemberOf { get; set; } + + public Dictionary PropertyNames { get; set; } = new(); + + public virtual bool Equals(TDAction? other) + { + if (other == null) + { + return false; + } + else + { + return Description == other.Description && + Input == other.Input && + Output == other.Output && + Idempotent == other.Idempotent && + Safe == other.Safe && + Forms == other.Forms && + Namespace == other.Namespace && + MemberOf == other.MemberOf; + } + } + + public override int GetHashCode() + { + return (Description, Input, Output, Idempotent, Safe, Forms, Namespace, MemberOf).GetHashCode(); + } + + public static bool operator ==(TDAction? left, TDAction? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(TDAction? left, TDAction? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not TDAction other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + if (Description != null) + { + foreach (ITraversable item in Description.Traverse()) + { + yield return item; + } + } + if (Input != null) + { + foreach (ITraversable item in Input.Traverse()) + { + yield return item; + } + } + if (Output != null) + { + foreach (ITraversable item in Output.Traverse()) + { + yield return item; + } + } + if (Idempotent != null) + { + foreach (ITraversable item in Idempotent.Traverse()) + { + yield return item; + } + } + if (Safe != null) + { + foreach (ITraversable item in Safe.Traverse()) + { + yield return item; + } + } + if (Forms != null) + { + foreach (ITraversable item in Forms.Traverse()) + { + yield return item; + } + } + if (Namespace != null) + { + foreach (ITraversable item in Namespace.Traverse()) + { + yield return item; + } + } + if (MemberOf != null) + { + foreach (ITraversable item in MemberOf.Traverse()) + { + yield return item; + } + } + } + + public static TDAction Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new InvalidOperationException($"expected JSON object but found {reader.TokenType}"); + } + + TDAction action = new(); + + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + ParsingSupport.CheckForDuplicatePropertyName(ref reader, propertyName, action.PropertyNames, "action"); + action.PropertyNames[propertyName] = reader.TokenStartIndex; + reader.Read(); + + switch (propertyName) + { + case DescriptionName: + action.Description = ValueTracker.Deserialize(ref reader, DescriptionName); + break; + case InputName: + action.Input = ValueTracker.Deserialize(ref reader, InputName); + break; + case OutputName: + action.Output = ValueTracker.Deserialize(ref reader, OutputName); + break; + case IdempotentName: + action.Idempotent = ValueTracker.Deserialize(ref reader, IdempotentName); + break; + case SafeName: + action.Safe = ValueTracker.Deserialize(ref reader, SafeName); + break; + case FormsName: + action.Forms = ArrayTracker.Deserialize(ref reader, FormsName); + break; + case NamespaceName: + action.Namespace = ValueTracker.Deserialize(ref reader, NamespaceName); + break; + case MemberOfName: + action.MemberOf = ValueTracker.Deserialize(ref reader, MemberOfName); + break; + default: + reader.Skip(); + break; + } + + reader.Read(); + } + + return action; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDCommon.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDCommon.cs new file mode 100644 index 0000000000..ce472e7574 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDCommon.cs @@ -0,0 +1,13 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + public static class TDCommon + { + public const string ContainedInName = "aov:containedIn"; + public const string ContainsName = "aov:contains"; + public const string ContentTypeName = "contentType"; + public const string DescriptionName = "description"; + public const string FormsName = "forms"; + public const string NamespaceName = "aov:namespace"; + public const string TitleName = "title"; + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDContextSpecifier.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDContextSpecifier.cs new file mode 100644 index 0000000000..ad86d975f8 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDContextSpecifier.cs @@ -0,0 +1,109 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class TDContextSpecifier : IEquatable, IDeserializable + { + public ValueTracker? Remote { get; set; } + + public MapTracker? Local { get; set; } + + public virtual bool Equals(TDContextSpecifier? other) + { + if (other == null) + { + return false; + } + if (Remote != other.Remote) + { + return false; + } + if (Local != other.Local) + { + return false; + } + return true; + } + + public override int GetHashCode() + { + return (Remote, Local).GetHashCode(); + } + + public static bool operator ==(TDContextSpecifier? left, TDContextSpecifier? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(TDContextSpecifier? left, TDContextSpecifier? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not TDContextSpecifier other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + if (Remote != null) + { + foreach (ITraversable item in Remote.Traverse()) + { + yield return item; + } + } + if (Local != null) + { + foreach (ITraversable item in Local.Traverse()) + { + yield return item; + } + } + } + + public static TDContextSpecifier Deserialize(ref Utf8JsonReader reader) + { + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + return new TDContextSpecifier + { + Local = MapTracker.Deserialize(ref reader, string.Empty), + }; + case JsonTokenType.String: + return new TDContextSpecifier + { + Remote = ValueTracker.Deserialize(ref reader, string.Empty), + }; + default: + throw new InvalidOperationException($"expected string or JSON object but found {reader.TokenType}"); + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDDataSchema.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDDataSchema.cs new file mode 100644 index 0000000000..a3ec258ca0 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDDataSchema.cs @@ -0,0 +1,387 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.Json; + + public class TDDataSchema : IEquatable, IDeserializable + { + public const string RefName = "dtv:ref"; + public const string TitleName = TDCommon.TitleName; + public const string DescriptionName = TDCommon.DescriptionName; + public const string TypeName = "type"; + public const string ConstName = "const"; + public const string MinimumName = "minimum"; + public const string MaximumName = "maximum"; + public const string ScaleFactorName = "aov:scaleFactor"; + public const string DecimalPlacesName = "aov:decimalPlaces"; + public const string FormatName = "format"; + public const string PatternName = "pattern"; + public const string ContentEncodingName = "contentEncoding"; + public const string AdditionalPropertiesName = "dtv:additionalProperties"; + public const string EnumName = "enum"; + public const string RequiredName = "required"; + public const string ErrorMessageName = "dtv:errorMessage"; + public const string PropertiesName = "properties"; + public const string ItemsName = "items"; + public const string TypeRefName = "aov:typeRef"; + public const string NamespaceName = TDCommon.NamespaceName; + + public ValueTracker? Ref { get; set; } + + public ValueTracker? Title { get; set; } + + public ValueTracker? Description { get; set; } + + public ValueTracker? Type { get; set; } + + public ValueTracker? Const { get; set; } + + public ValueTracker? Minimum { get; set; } + + public ValueTracker? Maximum { get; set; } + + public ValueTracker? ScaleFactor { get; set; } + + public ValueTracker? DecimalPlaces { get; set; } + + public ValueTracker? Format { get; set; } + + public ValueTracker? Pattern { get; set; } + + public ValueTracker? ContentEncoding { get; set; } + + public ValueTracker? AdditionalProperties { get; set; } + + public ArrayTracker? Enum { get; set; } + + public ArrayTracker? Required { get; set; } + + public ValueTracker? ErrorMessage { get; set; } + + public MapTracker? Properties { get; set; } + + public ValueTracker? Items { get; set; } + + public ValueTracker? TypeRef { get; set; } + + public ValueTracker? Namespace { get; set; } + + public Dictionary PropertyNames { get; set; } = new(); + + public override int GetHashCode() + { + return (Title, Description, Type, Const, Minimum, Maximum, ScaleFactor, DecimalPlaces, Format, Pattern, ContentEncoding, AdditionalProperties, Enum, Required, ErrorMessage, Properties, Items, TypeRef, Namespace).GetHashCode(); + } + + public virtual bool Equals(TDDataSchema? other) + { + if (other == null) + { + return false; + } + else + { + return Title == other.Title && + Ref == other.Ref && + Description == other.Description && + Type == other.Type && + Const == other.Const && + Minimum == other.Minimum && + Maximum == other.Maximum && + ScaleFactor == other.ScaleFactor && + DecimalPlaces == other.DecimalPlaces && + Format == other.Format && + Pattern == other.Pattern && + ContentEncoding == other.ContentEncoding && + ((AdditionalProperties == null && other.AdditionalProperties == null) || (AdditionalProperties?.Equals(other.AdditionalProperties) ?? false)) && + ((Enum == null && other.Enum == null) || (Enum?.Elements != null && other.Enum?.Elements != null && Enum.Elements.SequenceEqual(other.Enum.Elements))) && + ((Required == null && other.Required == null) || (Required?.Elements != null && other.Required?.Elements != null && Required.Elements.SequenceEqual(other.Required.Elements))) && + ErrorMessage == other.ErrorMessage && + Properties?.Entries?.Count == other.Properties?.Entries?.Count && (Properties == null || Properties!.Entries!.OrderBy(kv => kv.Key).SequenceEqual(other.Properties!.Entries!.OrderBy(kv => kv.Key))) && + ((Items == null && other.Items == null) || (Items?.Equals(other.Items) ?? false)) && + TypeRef == other.TypeRef && + Namespace == other.Namespace; + } + } + + public static bool operator ==(TDDataSchema? left, TDDataSchema? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(TDDataSchema? left, TDDataSchema? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not TDDataSchema other) + { + return false; + } + else + { + return Equals(other); + } + } + + public static TDDataSchema Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new InvalidOperationException($"expected JSON object but found {reader.TokenType}"); + } + + TDDataSchema dataSchema = new(); + + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + ParsingSupport.CheckForDuplicatePropertyName(ref reader, propertyName, dataSchema.PropertyNames, "data schema"); + dataSchema.PropertyNames[propertyName] = reader.TokenStartIndex; + reader.Read(); + + if (!TryLoadPropertyValues(dataSchema, propertyName, ref reader)) + { + reader.Skip(); + } + + reader.Read(); + } + + return dataSchema; + } + + public virtual IEnumerable Traverse() + { + if (Ref != null) + { + foreach (ITraversable item in Ref.Traverse()) + { + yield return item; + } + } + if (Title != null) + { + foreach (ITraversable item in Title.Traverse()) + { + yield return item; + } + } + if (Description != null) + { + foreach (ITraversable item in Description.Traverse()) + { + yield return item; + } + } + if (Type != null) + { + foreach (ITraversable item in Type.Traverse()) + { + yield return item; + } + } + if (Const != null) + { + foreach (ITraversable item in Const.Traverse()) + { + yield return item; + } + } + if (Minimum != null) + { + foreach (ITraversable item in Minimum.Traverse()) + { + yield return item; + } + } + if (Maximum != null) + { + foreach (ITraversable item in Maximum.Traverse()) + { + yield return item; + } + } + if (ScaleFactor != null) + { + foreach (ITraversable item in ScaleFactor.Traverse()) + { + yield return item; + } + } + if (DecimalPlaces != null) + { + foreach (ITraversable item in DecimalPlaces.Traverse()) + { + yield return item; + } + } + if (Format != null) + { + foreach (ITraversable item in Format.Traverse()) + { + yield return item; + } + } + if (Pattern != null) + { + foreach (ITraversable item in Pattern.Traverse()) + { + yield return item; + } + } + if (ContentEncoding != null) + { + foreach (ITraversable item in ContentEncoding.Traverse()) + { + yield return item; + } + } + if (AdditionalProperties != null) + { + foreach (ITraversable item in AdditionalProperties.Traverse()) + { + yield return item; + } + } + if (Enum != null) + { + foreach (ITraversable item in Enum.Traverse()) + { + yield return item; + } + } + if (Required != null) + { + foreach (ITraversable item in Required.Traverse()) + { + yield return item; + } + } + if (ErrorMessage != null) + { + foreach (ITraversable item in ErrorMessage.Traverse()) + { + yield return item; + } + } + if (Properties != null) + { + foreach (ITraversable item in Properties.Traverse()) + { + yield return item; + } + } + if (Items != null) + { + foreach (ITraversable item in Items.Traverse()) + { + yield return item; + } + } + if (TypeRef != null) + { + foreach (ITraversable item in TypeRef.Traverse()) + { + yield return item; + } + } + if (Namespace != null) + { + foreach (ITraversable item in Namespace.Traverse()) + { + yield return item; + } + } + } + + protected static bool TryLoadPropertyValues(TDDataSchema dataSchema, string propertyName, ref Utf8JsonReader reader) + { + switch (propertyName) + { + case RefName: + dataSchema.Ref = ValueTracker.Deserialize(ref reader, RefName); + return true; + case TitleName: + dataSchema.Title = ValueTracker.Deserialize(ref reader, TitleName); + return true; + case DescriptionName: + dataSchema.Description = ValueTracker.Deserialize(ref reader, DescriptionName); + return true; + case TypeName: + dataSchema.Type = ValueTracker.Deserialize(ref reader, TypeName); + return true; + case ConstName: + dataSchema.Const = ValueTracker.Deserialize(ref reader, ConstName); + return true; + case MinimumName: + dataSchema.Minimum = ValueTracker.Deserialize(ref reader, MinimumName); + return true; + case MaximumName: + dataSchema.Maximum = ValueTracker.Deserialize(ref reader, MaximumName); + return true; + case ScaleFactorName: + dataSchema.ScaleFactor = ValueTracker.Deserialize(ref reader, ScaleFactorName); + return true; + case DecimalPlacesName: + dataSchema.DecimalPlaces = ValueTracker.Deserialize(ref reader, DecimalPlacesName); + return true; + case FormatName: + dataSchema.Format = ValueTracker.Deserialize(ref reader, FormatName); + return true; + case PatternName: + dataSchema.Pattern = ValueTracker.Deserialize(ref reader, PatternName); + return true; + case ContentEncodingName: + dataSchema.ContentEncoding = ValueTracker.Deserialize(ref reader, ContentEncodingName); + return true; + case AdditionalPropertiesName: + dataSchema.AdditionalProperties = ValueTracker.Deserialize(ref reader, AdditionalPropertiesName); + return true; + case EnumName: + dataSchema.Enum = ArrayTracker.Deserialize(ref reader, EnumName); + return true; + case RequiredName: + dataSchema.Required = ArrayTracker.Deserialize(ref reader, RequiredName); + return true; + case ErrorMessageName: + dataSchema.ErrorMessage = ValueTracker.Deserialize(ref reader, ErrorMessageName); + return true; + case PropertiesName: + dataSchema.Properties = MapTracker.Deserialize(ref reader, PropertiesName); + return true; + case ItemsName: + dataSchema.Items = ValueTracker.Deserialize(ref reader, ItemsName); + return true; + case TypeRefName: + dataSchema.TypeRef = ValueTracker.Deserialize(ref reader, TypeRefName); + return true; + case NamespaceName: + dataSchema.Namespace = ValueTracker.Deserialize(ref reader, NamespaceName); + return true; + default: + return false; + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDEvent.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDEvent.cs new file mode 100644 index 0000000000..0ec9e50a32 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDEvent.cs @@ -0,0 +1,197 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class TDEvent : IEquatable, IDeserializable + { + public const string DescriptionName = TDCommon.DescriptionName; + public const string DataName = "data"; + public const string PlaceholderName = "dtv:placeholder"; + public const string FormsName = TDCommon.FormsName; + public const string ContainsName = TDCommon.ContainsName; + public const string ContainedInName = TDCommon.ContainedInName; + public const string NamespaceName = TDCommon.NamespaceName; + + public ValueTracker? Description { get; set; } + + public ValueTracker? Data { get; set; } + + public ValueTracker? Placeholder { get; set; } + + public ArrayTracker? Forms { get; set; } + + public Dictionary PropertyNames { get; set; } = new(); + + public ArrayTracker? Contains { get; set; } + + public ValueTracker? ContainedIn { get; set; } + + public ValueTracker? Namespace { get; set; } + + public virtual bool Equals(TDEvent? other) + { + if (other == null) + { + return false; + } + else + { + return Description == other.Description && + Data == other.Data && + Placeholder == other.Placeholder && + Forms == other.Forms && + Contains == other.Contains && + ContainedIn == other.ContainedIn && + Namespace == other.Namespace; + } + } + + public override int GetHashCode() + { + return (Description, Data, Placeholder, Forms, Contains, ContainedIn, Namespace).GetHashCode(); + } + + public static bool operator ==(TDEvent? left, TDEvent? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(TDEvent? left, TDEvent? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not TDEvent other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + if (Description != null) + { + foreach (ITraversable item in Description.Traverse()) + { + yield return item; + } + } + if (Data != null) + { + foreach (ITraversable item in Data.Traverse()) + { + yield return item; + } + } + if (Placeholder != null) + { + foreach (ITraversable item in Placeholder.Traverse()) + { + yield return item; + } + } + if (Forms != null) + { + foreach (ITraversable item in Forms.Traverse()) + { + yield return item; + } + } + if (Contains != null) + { + foreach (ITraversable item in Contains.Traverse()) + { + yield return item; + } + } + if (ContainedIn != null) + { + foreach (ITraversable item in ContainedIn.Traverse()) + { + yield return item; + } + } + if (Namespace != null) + { + foreach (ITraversable item in Namespace.Traverse()) + { + yield return item; + } + } + } + + public static TDEvent Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new InvalidOperationException($"expected JSON object but found {reader.TokenType}"); + } + + TDEvent evt = new(); + + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + ParsingSupport.CheckForDuplicatePropertyName(ref reader, propertyName, evt.PropertyNames, "event"); + evt.PropertyNames[propertyName] = reader.TokenStartIndex; + reader.Read(); + + switch (propertyName) + { + case DescriptionName: + evt.Description = ValueTracker.Deserialize(ref reader, DescriptionName); + break; + case DataName: + evt.Data = ValueTracker.Deserialize(ref reader, DataName); + break; + case PlaceholderName: + evt.Placeholder = ValueTracker.Deserialize(ref reader, PlaceholderName); + break; + case FormsName: + evt.Forms = ArrayTracker.Deserialize(ref reader, FormsName); + break; + case ContainsName: + evt.Contains = ArrayTracker.Deserialize(ref reader, ContainsName); + break; + case ContainedInName: + evt.ContainedIn = ValueTracker.Deserialize(ref reader, ContainedInName); + break; + case NamespaceName: + evt.Namespace = ValueTracker.Deserialize(ref reader, NamespaceName); + break; + default: + reader.Skip(); + break; + } + + reader.Read(); + } + + return evt; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDForm.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDForm.cs new file mode 100644 index 0000000000..8f4b4466f2 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDForm.cs @@ -0,0 +1,197 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class TDForm : IEquatable, IDeserializable + { + public const string ContentTypeName = TDCommon.ContentTypeName; + public const string AdditionalResponsesName = "additionalResponses"; + public const string HeaderInfoName = "dtv:headerInfo"; + public const string HeaderCodeName = "dtv:headerCode"; + public const string ServiceGroupIdName = "dtv:serviceGroupId"; + public const string TopicName = "dtv:topic"; + public const string OpName = "op"; + + public ValueTracker? ContentType { get; set; } + + public ArrayTracker? AdditionalResponses { get; set; } + + public ArrayTracker? HeaderInfo { get; set; } + + public ValueTracker? HeaderCode { get; set; } + + public ValueTracker? ServiceGroupId { get; set; } + + public ValueTracker? Topic { get; set; } + + public ArrayTracker? Op { get; set; } + + public Dictionary PropertyNames { get; set; } = new(); + + public virtual bool Equals(TDForm? other) + { + if (other == null) + { + return false; + } + else + { + return ContentType == other.ContentType && + AdditionalResponses == other.AdditionalResponses && + HeaderInfo == other.HeaderInfo && + HeaderCode == other.HeaderCode && + ServiceGroupId == other.ServiceGroupId && + Topic == other.Topic && + Op == other.Op; + } + } + + public override int GetHashCode() + { + return (ContentType, AdditionalResponses, HeaderInfo, HeaderCode, ServiceGroupId, Topic, Op).GetHashCode(); + } + + public static bool operator ==(TDForm? left, TDForm? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(TDForm? left, TDForm? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not TDForm other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + if (ContentType != null) + { + foreach (ITraversable item in ContentType.Traverse()) + { + yield return item; + } + } + if (AdditionalResponses != null) + { + foreach (ITraversable item in AdditionalResponses.Traverse()) + { + yield return item; + } + } + if (HeaderInfo != null) + { + foreach (ITraversable item in HeaderInfo.Traverse()) + { + yield return item; + } + } + if (HeaderCode != null) + { + foreach (ITraversable item in HeaderCode.Traverse()) + { + yield return item; + } + } + if (ServiceGroupId != null) + { + foreach (ITraversable item in ServiceGroupId.Traverse()) + { + yield return item; + } + } + if (Topic != null) + { + foreach (ITraversable item in Topic.Traverse()) + { + yield return item; + } + } + if (Op != null) + { + foreach (ITraversable item in Op.Traverse()) + { + yield return item; + } + } + } + + public static TDForm Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new InvalidOperationException($"expected JSON object but found {reader.TokenType}"); + } + + TDForm form = new(); + + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + ParsingSupport.CheckForDuplicatePropertyName(ref reader, propertyName, form.PropertyNames, "form"); + form.PropertyNames[propertyName] = reader.TokenStartIndex; + reader.Read(); + + switch (propertyName) + { + case ContentTypeName: + form.ContentType = ValueTracker.Deserialize(ref reader, ContentTypeName); + break; + case AdditionalResponsesName: + form.AdditionalResponses = ArrayTracker.Deserialize(ref reader, AdditionalResponsesName); + break; + case HeaderInfoName: + form.HeaderInfo = ArrayTracker.Deserialize(ref reader, HeaderInfoName); + break; + case HeaderCodeName: + form.HeaderCode = ValueTracker.Deserialize(ref reader, HeaderCodeName); + break; + case ServiceGroupIdName: + form.ServiceGroupId = ValueTracker.Deserialize(ref reader, ServiceGroupIdName); + break; + case TopicName: + form.Topic = ValueTracker.Deserialize(ref reader, TopicName); + break; + case OpName: + form.Op = ArrayTracker.Deserialize(ref reader, OpName); + break; + default: + reader.Skip(); + break; + } + + reader.Read(); + } + + return form; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDLink.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDLink.cs new file mode 100644 index 0000000000..cf40d03d37 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDLink.cs @@ -0,0 +1,152 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class TDLink : IEquatable, IDeserializable + { + public const string HrefName = "href"; + public const string TypeName = "type"; + public const string RelName = "rel"; + public const string RefTypeName = "aov:refType"; + + public ValueTracker? Href { get; set; } + + public ValueTracker? Type { get; set; } + + public ValueTracker? Rel { get; set; } + + public ValueTracker? RefType { get; set; } + + public Dictionary PropertyNames { get; set; } = new(); + + public virtual bool Equals(TDLink? other) + { + if (other == null) + { + return false; + } + else + { + return Href == other.Href && Type == other.Type && Rel == other.Rel && RefType == other.RefType; + } + } + + public override int GetHashCode() + { + return (Href, Type, Rel, RefType).GetHashCode(); + } + + public static bool operator ==(TDLink? left, TDLink? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(TDLink? left, TDLink? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not TDLink other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + if (Href != null) + { + foreach (ITraversable item in Href.Traverse()) + { + yield return item; + } + } + if (Type != null) + { + foreach (ITraversable item in Type.Traverse()) + { + yield return item; + } + } + if (Rel != null) + { + foreach (ITraversable item in Rel.Traverse()) + { + yield return item; + } + } + if (RefType != null) + { + foreach (ITraversable item in RefType.Traverse()) + { + yield return item; + } + } + } + + public static TDLink Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new InvalidOperationException($"expected JSON object but found {reader.TokenType}"); + } + + TDLink link = new(); + + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + ParsingSupport.CheckForDuplicatePropertyName(ref reader, propertyName, link.PropertyNames, "link"); + link.PropertyNames[propertyName] = reader.TokenStartIndex; + reader.Read(); + + switch (propertyName) + { + case HrefName: + link.Href = ValueTracker.Deserialize(ref reader, HrefName); + break; + case TypeName: + link.Type = ValueTracker.Deserialize(ref reader, TypeName); + break; + case RelName: + link.Rel = ValueTracker.Deserialize(ref reader, RelName); + break; + case RefTypeName: + link.RefType = ValueTracker.Deserialize(ref reader, RefTypeName); + break; + default: + reader.Skip(); + break; + } + + reader.Read(); + } + + return link; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDProperty.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDProperty.cs new file mode 100644 index 0000000000..72257bbafc --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDProperty.cs @@ -0,0 +1,176 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class TDProperty : TDDataSchema, IEquatable, IDeserializable + { + public const string ReadOnlyName = "readOnly"; + public const string PlaceholderName = "dtv:placeholder"; + public const string FormsName = TDCommon.FormsName; + public const string ContainsName = TDCommon.ContainsName; + public const string ContainedInName = TDCommon.ContainedInName; + + public ValueTracker? ReadOnly { get; set; } + + public ValueTracker? Placeholder { get; set; } + + public ArrayTracker? Forms { get; set; } + + public ArrayTracker? Contains { get; set; } + + public ValueTracker? ContainedIn { get; set; } + + public virtual bool Equals(TDProperty? other) + { + if (other == null) + { + return false; + } + else + { + return base.Equals(other) && + ReadOnly == other.ReadOnly && + Placeholder == other.Placeholder && + Forms == other.Forms && + Contains == other.Contains && + ContainedIn == other.ContainedIn; + } + } + + public override int GetHashCode() + { + return (base.GetHashCode(), ReadOnly, Placeholder, Forms, Contains, ContainedIn).GetHashCode(); + } + + public static bool operator ==(TDProperty? left, TDProperty? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(TDProperty? left, TDProperty? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not TDProperty other) + { + return false; + } + else + { + return Equals(other); + } + } + + public override IEnumerable Traverse() + { + foreach (ITraversable baseChild in base.Traverse()) + { + yield return baseChild; + } + + if (ReadOnly != null) + { + foreach (ITraversable item in ReadOnly.Traverse()) + { + yield return item; + } + } + if (Placeholder != null) + { + foreach (ITraversable item in Placeholder.Traverse()) + { + yield return item; + } + } + if (Forms != null) + { + foreach (ITraversable item in Forms.Traverse()) + { + yield return item; + } + } + if (Contains != null) + { + foreach (ITraversable item in Contains.Traverse()) + { + yield return item; + } + } + if (ContainedIn != null) + { + foreach (ITraversable item in ContainedIn.Traverse()) + { + yield return item; + } + } + } + + public static new TDProperty Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new InvalidOperationException($"expected JSON object but found {reader.TokenType}"); + } + + TDProperty prop = new(); + + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + ParsingSupport.CheckForDuplicatePropertyName(ref reader, propertyName, prop.PropertyNames, "property"); + prop.PropertyNames[propertyName] = reader.TokenStartIndex; + reader.Read(); + + if (!TryLoadPropertyValues(prop, propertyName, ref reader)) + { + switch (propertyName) + { + case ReadOnlyName: + prop.ReadOnly = ValueTracker.Deserialize(ref reader, ReadOnlyName); + break; + case PlaceholderName: + prop.Placeholder = ValueTracker.Deserialize(ref reader, PlaceholderName); + break; + case FormsName: + prop.Forms = ArrayTracker.Deserialize(ref reader, FormsName); + break; + case ContainsName: + prop.Contains = ArrayTracker.Deserialize(ref reader, ContainsName); + break; + case ContainedInName: + prop.ContainedIn = ValueTracker.Deserialize(ref reader, ContainedInName); + break; + default: + reader.Skip(); + break; + } + } + + reader.Read(); + } + + return prop; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDSchemaReference.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDSchemaReference.cs new file mode 100644 index 0000000000..4b73e2312f --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDSchemaReference.cs @@ -0,0 +1,139 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class TDSchemaReference : IEquatable, IDeserializable + { + public const string SuccessName = "success"; + public const string ContentTypeName = TDCommon.ContentTypeName; + public const string SchemaName = "schema"; + + public ValueTracker? Success { get; set; } + + public ValueTracker? ContentType { get; set; } + + public ValueTracker? Schema { get; set; } + + public Dictionary PropertyNames { get; set; } = new(); + + public virtual bool Equals(TDSchemaReference? other) + { + if (other == null) + { + return false; + } + else + { + return Success == other.Success && ContentType == other.ContentType && Schema == other.Schema; + } + } + + public override int GetHashCode() + { + return (Success, ContentType, Schema).GetHashCode(); + } + + public static bool operator ==(TDSchemaReference? left, TDSchemaReference? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(TDSchemaReference? left, TDSchemaReference? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not TDSchemaReference other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + if (Success != null) + { + foreach (ITraversable item in Success.Traverse()) + { + yield return item; + } + } + if (ContentType != null) + { + foreach (ITraversable item in ContentType.Traverse()) + { + yield return item; + } + } + if (Schema != null) + { + foreach (ITraversable item in Schema.Traverse()) + { + yield return item; + } + } + } + + public static TDSchemaReference Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new InvalidOperationException($"expected JSON object but found {reader.TokenType}"); + } + + TDSchemaReference schemaRef = new(); + + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + ParsingSupport.CheckForDuplicatePropertyName(ref reader, propertyName, schemaRef.PropertyNames, "schema reference"); + schemaRef.PropertyNames[propertyName] = reader.TokenStartIndex; + reader.Read(); + + switch (propertyName) + { + case SuccessName: + schemaRef.Success = ValueTracker.Deserialize(ref reader, SuccessName); + break; + case ContentTypeName: + schemaRef.ContentType = ValueTracker.Deserialize(ref reader, ContentTypeName); + break; + case SchemaName: + schemaRef.Schema = ValueTracker.Deserialize(ref reader, SchemaName); + break; + default: + reader.Skip(); + break; + } + + reader.Read(); + } + + return schemaRef; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDThing.cs b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDThing.cs new file mode 100644 index 0000000000..65e45f705a --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/Model/TDThing.cs @@ -0,0 +1,295 @@ +namespace Azure.Iot.Operations.TDParser.Model +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class TDThing : IEquatable, IDeserializable + { + public const string ContextName = "@context"; + public const string TypeName = "@type"; + public const string TitleName = TDCommon.TitleName; + public const string DescriptionName = TDCommon.DescriptionName; + public const string LinksName = "links"; + public const string SchemaDefinitionsName = "schemaDefinitions"; + public const string FormsName = TDCommon.FormsName; + public const string OptionalName = "tm:optional"; + public const string ActionsName = "actions"; + public const string PropertiesName = "properties"; + public const string EventsName = "events"; + public const string IsCompositeName = "aov:isComposite"; + public const string IsEventName = "aov:isEvent"; + public const string TypeRefName = "aov:typeRef"; + + public ArrayTracker? Context { get; set; } + + public ValueTracker? Type { get; set; } + + public ValueTracker? Title { get; set; } + + public ValueTracker? Description { get; set; } + + public ArrayTracker? Links { get; set; } + + public MapTracker? SchemaDefinitions { get; set; } + + public ArrayTracker? Forms { get; set; } + + public ArrayTracker? Optional { get; set; } + + public MapTracker? Actions { get; set; } + + public MapTracker? Properties { get; set; } + + public MapTracker? Events { get; set; } + + public ValueTracker? IsComposite { get; set; } + + public ValueTracker? IsEvent { get; set; } + + public ValueTracker? TypeRef { get; set; } + + public Dictionary PropertyNames { get; set; } = new(); + + public virtual bool Equals(TDThing? other) + { + if (other == null) + { + return false; + } + else + { + return Context == other.Context && + Type == other.Type && + Title == other.Title && + Description == other.Description && + Links == other.Links && + SchemaDefinitions == other.SchemaDefinitions && + Forms == other.Forms && + Optional == other.Optional && + Actions == other.Actions && + Properties == other.Properties && + Events == other.Events && + IsComposite == other.IsComposite && + IsEvent == other.IsEvent && + TypeRef == other.TypeRef; + } + } + + public override int GetHashCode() + { + return (Context, Type, Title, Description, Links, SchemaDefinitions, Forms, Optional, Actions, Properties, Events, IsComposite, IsEvent, TypeRef).GetHashCode(); + } + + public static bool operator ==(TDThing? left, TDThing? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(TDThing? left, TDThing? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not TDThing other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + if (Context != null) + { + foreach (ITraversable item in Context.Traverse()) + { + yield return item; + } + } + if (Type != null) + { + foreach (ITraversable item in Type.Traverse()) + { + yield return item; + } + } + if (Title != null) + { + foreach (ITraversable item in Title.Traverse()) + { + yield return item; + } + } + if (Description != null) + { + foreach (ITraversable item in Description.Traverse()) + { + yield return item; + } + } + if (Links != null) + { + foreach (ITraversable item in Links.Traverse()) + { + yield return item; + } + } + if (SchemaDefinitions != null) + { + foreach (ITraversable item in SchemaDefinitions.Traverse()) + { + yield return item; + } + } + if (Forms != null) + { + foreach (ITraversable item in Forms.Traverse()) + { + yield return item; + } + } + if (Optional != null) + { + foreach (ITraversable item in Optional.Traverse()) + { + yield return item; + } + } + if (Actions != null) + { + foreach (ITraversable item in Actions.Traverse()) + { + yield return item; + } + } + if (Properties != null) + { + foreach (ITraversable item in Properties.Traverse()) + { + yield return item; + } + } + if (Events != null) + { + foreach (ITraversable item in Events.Traverse()) + { + yield return item; + } + } + if (IsComposite != null) + { + foreach (ITraversable item in IsComposite.Traverse()) + { + yield return item; + } + } + if (IsEvent != null) + { + foreach (ITraversable item in IsEvent.Traverse()) + { + yield return item; + } + } + if (TypeRef != null) + { + foreach (ITraversable item in TypeRef.Traverse()) + { + yield return item; + } + } + } + + public static TDThing Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new InvalidOperationException($"expected JSON object but found {reader.TokenType}"); + } + + TDThing thing = new(); + + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + ParsingSupport.CheckForDuplicatePropertyName(ref reader, propertyName, thing.PropertyNames, "thing"); + thing.PropertyNames[propertyName] = reader.TokenStartIndex; + reader.Read(); + + switch (propertyName) + { + case ContextName: + thing.Context = ArrayTracker.Deserialize(ref reader, ContextName); + break; + case TypeName: + thing.Type = ValueTracker.Deserialize(ref reader, TypeName); + break; + case TitleName: + thing.Title = ValueTracker.Deserialize(ref reader, TitleName); + break; + case DescriptionName: + thing.Description = ValueTracker.Deserialize(ref reader, DescriptionName); + break; + case LinksName: + thing.Links = ArrayTracker.Deserialize(ref reader, LinksName); + break; + case SchemaDefinitionsName: + thing.SchemaDefinitions = MapTracker.Deserialize(ref reader, SchemaDefinitionsName); + break; + case FormsName: + thing.Forms = ArrayTracker.Deserialize(ref reader, FormsName); + break; + case OptionalName: + thing.Optional = ArrayTracker.Deserialize(ref reader, OptionalName); + break; + case ActionsName: + thing.Actions = MapTracker.Deserialize(ref reader, ActionsName); + break; + case PropertiesName: + thing.Properties = MapTracker.Deserialize(ref reader, PropertiesName); + break; + case EventsName: + thing.Events = MapTracker.Deserialize(ref reader, EventsName); + break; + case IsCompositeName: + thing.IsComposite = ValueTracker.Deserialize(ref reader, IsCompositeName); + break; + case IsEventName: + thing.IsEvent = ValueTracker.Deserialize(ref reader, IsEventName); + break; + case TypeRefName: + thing.TypeRef = ValueTracker.Deserialize(ref reader, TypeRefName); + break; + default: + reader.Skip(); + break; + } + + reader.Read(); + } + + return thing; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/NumberHolder.cs b/codegen2/src/Azure.Iot.Operations.TDParser/NumberHolder.cs new file mode 100644 index 0000000000..e1fb426929 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/NumberHolder.cs @@ -0,0 +1,36 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class NumberHolder : BaseHolder, IEquatable, IDeserializable + { + public virtual bool Equals(NumberHolder? other) + { + if (other == null) + { + return false; + } + else + { + return Value == other.Value; + } + } + + public static NumberHolder Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.Number) + { + throw new InvalidOperationException($"expected JSON number but found {reader.TokenType}"); + } + + return new NumberHolder { Value = reader.GetDouble() }; + } + + public IEnumerable Traverse() + { + yield break; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/ObjectHolder.cs b/codegen2/src/Azure.Iot.Operations.TDParser/ObjectHolder.cs new file mode 100644 index 0000000000..1b6e1fc92f --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/ObjectHolder.cs @@ -0,0 +1,96 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class ObjectHolder : IEquatable, IDeserializable + { + public object? Value { get; set; } + + public MapTracker? ValueMap { get; set; } + + public virtual bool Equals(ObjectHolder? other) + { + if (other == null) + { + return false; + } + if (Value != other.Value) + { + return false; + } + if (ValueMap != other.ValueMap) + { + return false; + } + return true; + } + + public override int GetHashCode() + { + return (Value, ValueMap).GetHashCode(); + } + + public static bool operator ==(ObjectHolder? left, ObjectHolder? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(ObjectHolder? left, ObjectHolder? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not ObjectHolder other) + { + return false; + } + else + { + return Equals(other); + } + } + + public static ObjectHolder Deserialize(ref Utf8JsonReader reader) + { + switch (reader.TokenType) + { + case JsonTokenType.StartObject: + return new ObjectHolder { ValueMap = MapTracker.Deserialize(ref reader, string.Empty) }; + case JsonTokenType.String: + return new ObjectHolder { Value = reader.GetString()! }; + case JsonTokenType.Number: + return new ObjectHolder { Value = reader.GetDouble() }; + case JsonTokenType.True: + return new ObjectHolder { Value = true }; + case JsonTokenType.False: + return new ObjectHolder { Value = false }; + default: + throw new InvalidOperationException($"expected primitive value or JSON object but found {reader.TokenType}"); + } + } + + public IEnumerable Traverse() + { + yield break; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/ParsingSupport.cs b/codegen2/src/Azure.Iot.Operations.TDParser/ParsingSupport.cs new file mode 100644 index 0000000000..bd1db8b2b4 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/ParsingSupport.cs @@ -0,0 +1,24 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public static class ParsingSupport + { + public static void CheckForDuplicatePropertyName(ref Utf8JsonReader reader, string propertyName, Dictionary propertyNames, string objectDesc) + { + if (propertyNames.ContainsKey(propertyName)) + { + while (reader.TokenType == JsonTokenType.PropertyName) + { + reader.Read(); + reader.Skip(); + reader.Read(); + } + + throw new InvalidOperationException($"duplicate property name '{propertyName}' found in {objectDesc} object"); + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/StringHolder.cs b/codegen2/src/Azure.Iot.Operations.TDParser/StringHolder.cs new file mode 100644 index 0000000000..7109ee01d2 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/StringHolder.cs @@ -0,0 +1,36 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class StringHolder : BaseHolder, IEquatable, IDeserializable + { + public virtual bool Equals(StringHolder? other) + { + if (other == null) + { + return false; + } + else + { + return Value == other.Value; + } + } + + public static StringHolder Deserialize(ref Utf8JsonReader reader) + { + if (reader.TokenType != JsonTokenType.String) + { + throw new InvalidOperationException($"expected JSON string but found {reader.TokenType}"); + } + + return new StringHolder { Value = reader.GetString()! }; + } + + public IEnumerable Traverse() + { + yield break; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/TDParser.cs b/codegen2/src/Azure.Iot.Operations.TDParser/TDParser.cs new file mode 100644 index 0000000000..0fad5255c5 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/TDParser.cs @@ -0,0 +1,45 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System.Collections.Generic; + using System.Text; + using System.Text.Json; + using Azure.Iot.Operations.TDParser.Model; + + public class TDParser + { + public static List Parse(string tdJson) + { + return Parse(Encoding.UTF8.GetBytes(tdJson)); + } + + public static List Parse(byte[] tdJson) + { + Utf8JsonReader reader = new Utf8JsonReader(tdJson); + + reader.Read(); + if (reader.TokenType == JsonTokenType.StartArray) + { + List things = new(); + + reader.Read(); + while (reader.TokenType != JsonTokenType.EndArray) + { + things.Add(TDThing.Deserialize(ref reader)); + reader.Read(); + } + + return things; + } + else if (reader.TokenType == JsonTokenType.StartObject) + { + TDThing? thing = TDThing.Deserialize(ref reader); + if (thing != null) + { + return new List { thing }; + } + } + + return new(); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TDParser/ValueTracker.cs b/codegen2/src/Azure.Iot.Operations.TDParser/ValueTracker.cs new file mode 100644 index 0000000000..776b8ae477 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TDParser/ValueTracker.cs @@ -0,0 +1,120 @@ +namespace Azure.Iot.Operations.TDParser +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + + public class ValueTracker : IEquatable>, ISourceTracker + where T : IDeserializable + { + public required string PropertyName { get; set; } + + public required T Value { get; set; } + + public bool DeserializingFailed { get; set; } + + public string? DeserializationError { get; set; } + + public long TokenIndex { get; set; } = -1; + + public virtual bool Equals(ValueTracker? other) + { + if (other == null) + { + return false; + } + else if (Value == null || other.Value == null) + { + return Value == null && other.Value == null; + } + else + { + return Value.Equals(other.Value); + } + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + + public static bool operator ==(ValueTracker? left, ValueTracker? right) + { + if (left is null) + { + return right is null; + } + else + { + return left.Equals(right); + } + } + + public static bool operator !=(ValueTracker? left, ValueTracker? right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + else if (ReferenceEquals(obj, null)) + { + return false; + } + else if (obj is not ValueTracker other) + { + return false; + } + else + { + return Equals(other); + } + } + + public IEnumerable Traverse() + { + yield return this; + + if (Value != null) + { + foreach (ITraversable item in Value.Traverse()) + { + yield return item; + } + } + } + + public static ValueTracker Deserialize(ref Utf8JsonReader reader, string propertyName) + { + long tokenIndex = reader.TokenStartIndex; + + try + { + T value = T.Deserialize(ref reader); + return new ValueTracker + { + PropertyName = propertyName, + Value = value, + TokenIndex = tokenIndex, + }; + } + catch (Exception ex) + { + reader.Skip(); + + return new ValueTracker + { + PropertyName = propertyName, + Value = default!, + DeserializingFailed = true, + DeserializationError = ex.Message, + TokenIndex = tokenIndex, + }; + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/Azure.Iot.Operations.TypeGenerator.csproj b/codegen2/src/Azure.Iot.Operations.TypeGenerator/Azure.Iot.Operations.TypeGenerator.csproj new file mode 100644 index 0000000000..f7f6180209 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/Azure.Iot.Operations.TypeGenerator.csproj @@ -0,0 +1,87 @@ + + + + 0.10.0 + net9.0 + enable + + + + + + + + + + + + + TextTemplatingFilePreprocessor + DotNetAlias.cs + Azure.Iot.Operations.TypeGenerator + + + TextTemplatingFilePreprocessor + DotNetEnum.cs + Azure.Iot.Operations.TypeGenerator + + + TextTemplatingFilePreprocessor + DotNetObject.cs + Azure.Iot.Operations.TypeGenerator + + + TextTemplatingFilePreprocessor + RustAlias.cs + Azure.Iot.Operations.TypeGenerator + + + TextTemplatingFilePreprocessor + RustEnum.cs + Azure.Iot.Operations.TypeGenerator + + + TextTemplatingFilePreprocessor + RustObject.cs + Azure.Iot.Operations.TypeGenerator + + + + + + + + + + True + True + DotNetAlias.tt + + + True + True + DotNetEnum.tt + + + True + True + DotNetObject.tt + + + True + True + RustAlias.tt + + + True + True + RustEnum.tt + + + True + True + RustObject.tt + + + + diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/CycleBreaker.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/CycleBreaker.cs new file mode 100644 index 0000000000..2a9051c857 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/CycleBreaker.cs @@ -0,0 +1,70 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + + internal class CycleBreaker + { + private static readonly HashSet LanguagesNeedingIndirection = new () + { + TargetLanguage.Rust, + }; + + private bool indirectionNeeded; + private readonly Dictionary> directEdges; + + internal CycleBreaker(TargetLanguage targetLanguage) + { + this.indirectionNeeded = LanguagesNeedingIndirection.Contains(targetLanguage); + this.directEdges = new Dictionary>(); + } + + internal void AddIndirectionAsNeeded(SchemaType schemaType) + { + if (!this.indirectionNeeded) + { + return; + } + + if (schemaType is ObjectType objectType) + { + List targets = new (); + foreach (ObjectType.FieldInfo fieldInfo in objectType.FieldInfos.Values) + { + if (!fieldInfo.IsIndirect && fieldInfo.SchemaType is ReferenceType referenceType) + { + if (referenceType.SchemaName.Equals(objectType.SchemaName) || CanReach(referenceType.SchemaName, objectType.SchemaName, new HashSet())) + { + fieldInfo.IsIndirect = true; + } + else + { + targets.Add(referenceType.SchemaName); + } + } + } + + if (targets.Count > 0) + { + this.directEdges[objectType.SchemaName] = targets; + } + } + } + + private bool CanReach(CodeName source, CodeName endpoint, HashSet visited) + { + if (this.directEdges.TryGetValue(source, out List? targets) && visited.Add(source)) + { + foreach (CodeName target in targets) + { + if (target.Equals(endpoint) || CanReach(target, endpoint, visited)) + { + return true; + } + } + } + + return false; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/DotNetTypeGenerator.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/DotNetTypeGenerator.cs new file mode 100644 index 0000000000..28a638ef43 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/DotNetTypeGenerator.cs @@ -0,0 +1,24 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + + internal class DotNetTypeGenerator : ITypeGenerator + { + public TargetLanguage TargetLanguage { get => TargetLanguage.CSharp; } + + public GeneratedItem GenerateTypeFromSchema(SchemaType schemaType, string projectName, CodeName genNamespace, SerializationFormat serFormat, string _) + { + ITypeTemplateTransform templateTransform = schemaType switch + { + AliasType aliasType => new DotNetAlias(projectName, genNamespace, aliasType), + ObjectType objectType => new DotNetObject(projectName, genNamespace, objectType, serFormat), + EnumType enumType => new DotNetEnum(projectName, genNamespace, enumType), + _ => throw new Exception("unrecognized schema type"), + }; + + return new GeneratedItem(templateTransform.TransformText(), templateTransform.FileName, templateTransform.FolderPath); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/ISchemaStandardizer.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/ISchemaStandardizer.cs new file mode 100644 index 0000000000..f354c83d84 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/ISchemaStandardizer.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + + internal interface ISchemaStandardizer + { + SerializationFormat SerializationFormat { get; } + + bool TryGetStandardizedSchemas(Dictionary schemaTextsByName, ErrorLog errorLog, out List schemaTypes); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/ITypeGenerator.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/ITypeGenerator.cs new file mode 100644 index 0000000000..70cc795dd5 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/ITypeGenerator.cs @@ -0,0 +1,11 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + internal interface ITypeGenerator + { + TargetLanguage TargetLanguage { get; } + + GeneratedItem GenerateTypeFromSchema(SchemaType schemaType, string projectName, CodeName genNamespace, SerializationFormat serFormat, string srcSubdir); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/ITypeTemplateTransform.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/ITypeTemplateTransform.cs new file mode 100644 index 0000000000..8fab336dcc --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/ITypeTemplateTransform.cs @@ -0,0 +1,11 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal interface ITypeTemplateTransform + { + string FileName { get; } + + string FolderPath { get; } + + string TransformText(); + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/JsonSchemaStandardizer.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/JsonSchemaStandardizer.cs new file mode 100644 index 0000000000..0bf92b1eeb --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/JsonSchemaStandardizer.cs @@ -0,0 +1,958 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Text.Json; + using System.Text.RegularExpressions; + using Azure.Iot.Operations.CodeGeneration; + + internal class JsonSchemaStandardizer : ISchemaStandardizer + { + private static readonly Regex TitleRegex = new(@"^[A-Z][A-Za-z0-9]*$", RegexOptions.Compiled); + private static readonly Regex EnumValueRegex = new(@"^[A-Za-z][A-Za-z0-9_]*$", RegexOptions.Compiled); + + private readonly TypeNamer typeNamer; + + internal JsonSchemaStandardizer(TypeNamer typeNamer) + { + this.typeNamer = typeNamer; + } + + public SerializationFormat SerializationFormat { get => SerializationFormat.Json; } + + public bool TryGetStandardizedSchemas(Dictionary schemaTextsByName, ErrorLog errorLog, out List schemaTypes) + { + Dictionary schemaRootsByName = GetSchemaRootsFromSchemaTexts(schemaTextsByName, errorLog); + + Dictionary schemaTypeDict = new(); + bool hasError = false; + + foreach (KeyValuePair namedSchemaRoot in schemaRootsByName) + { + if (!TryGetSchemaType(namedSchemaRoot.Key, null, namedSchemaRoot.Value.JsonTracker, false, schemaTypeDict, schemaRootsByName, namedSchemaRoot.Value.ErrorReporter, out _, out _, true)) + { + hasError = true; + } + + foreach (string internalDefsKey in JsonSchemaValues.InternalDefsKeys) + { + if (namedSchemaRoot.Value.JsonTracker.TryGetProperty(internalDefsKey, out JsonTracker defsTracker)) + { + foreach (KeyValuePair defProp in defsTracker.EnumerateObject()) + { + if (!TryGetSchemaType(namedSchemaRoot.Key, defProp.Key, defProp.Value, false, schemaTypeDict, schemaRootsByName, namedSchemaRoot.Value.ErrorReporter, out _, out _, false)) + { + hasError = true; + } + } + } + } + } + + schemaTypes = schemaTypeDict.Values.ToList(); + return !hasError; + } + + private Dictionary GetSchemaRootsFromSchemaTexts(Dictionary schemaTextsByName, ErrorLog errorLog) + { + Dictionary schemaRootsByName = new(); + + foreach (KeyValuePair namedSchemaText in schemaTextsByName) + { + byte[] schemaBytes = Encoding.UTF8.GetBytes(namedSchemaText.Value); + string schemaFilePath = Path.GetFullPath(namedSchemaText.Key); + string schemaFolder = Path.GetDirectoryName(namedSchemaText.Value)!; + ErrorReporter errorReporter = new ErrorReporter(errorLog, schemaFilePath, schemaBytes); + Utf8JsonReader reader = new Utf8JsonReader(schemaBytes); + reader.Read(); + schemaRootsByName[namedSchemaText.Key] = new SchemaRoot(JsonTracker.Deserialize(ref reader), schemaFilePath, schemaFolder, errorReporter); + } + + return schemaRootsByName; + } + + private bool TryGetSchemaType( + string docName, + string? defKey, + JsonTracker schemaTracker, + bool orNull, + Dictionary? schemaTypes, + Dictionary schemaRootsByName, + ErrorReporter? errorReporter, + [NotNullWhen(true)] out SchemaType? schemaType, + [NotNullWhen(true)] out string? jsonSchemaType, + bool isTopLevel = false) + { + schemaType = null; + jsonSchemaType = null; + bool hasError = false; + + if (schemaTracker.ValueKind != JsonValueKind.Object) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, "JSON Schema definition has non-object value", schemaTracker.TokenIndex); + return false; + } + + if (!TryGetNestedNullableJsonElement(ref schemaTracker, errorReporter, ref orNull)) + { + return false; + } + + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyTitle, out JsonTracker titleTracker)) + { + string? title = titleTracker.GetString(); + if (titleTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(title)) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyTitle}' property has non-string or empty value", titleTracker.TokenIndex); + hasError = true; + } + else if (!this.typeNamer.SuppressTitles && !TitleRegex.IsMatch(title)) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyTitle}' property value \"{title}\" does not conform to codegen type naming rules -- it must start with an uppercase letter and contain only alphanumeric characters", titleTracker.TokenIndex); + hasError = true; + } + } + else if (isTopLevel) + { + errorReporter?.ReportError(ErrorCondition.PropertyMissing, $"JSON Schema file missing top-level '{JsonSchemaValues.PropertyTitle}' property", schemaTracker.TokenIndex); + hasError = true; + } + + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyDescription, out JsonTracker descTracker)) + { + if (descTracker.ValueKind != JsonValueKind.String) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyDescription}' property has non-string value", descTracker.TokenIndex); + hasError = true; + } + } + + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyRef, out _)) + { + return TryGetReferenceSchemaType(docName, defKey, schemaTracker, orNull, schemaTypes, schemaRootsByName, errorReporter, out schemaType, out jsonSchemaType) && !hasError; + } + + if (!schemaTracker.TryGetProperty(JsonSchemaValues.PropertyType, out JsonTracker typeTracker)) + { + errorReporter?.ReportError(ErrorCondition.PropertyMissing, $"JSON Schema definition has neither '{JsonSchemaValues.PropertyType}' nor '{JsonSchemaValues.PropertyRef}' property", schemaTracker.TokenIndex); + return false; + } + + if (typeTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(typeTracker.GetString())) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyType}' property has non-string or empty value", typeTracker.TokenIndex); + return false; + } + + if (hasError) + { + return false; + } + + jsonSchemaType = typeTracker.GetString(); + switch (jsonSchemaType) + { + case JsonSchemaValues.TypeObject: + return TryGetObjectSchemaType(docName, defKey, schemaTracker, orNull, schemaTypes, schemaRootsByName, errorReporter, out schemaType, isTopLevel); + case JsonSchemaValues.TypeArray: + return TryGetArraySchemaType(docName, defKey, schemaTracker, orNull, schemaTypes, schemaRootsByName, errorReporter, out schemaType); + case JsonSchemaValues.TypeString: + return TryGetStringSchemaType(docName, defKey, schemaTracker, orNull, schemaTypes, schemaRootsByName, errorReporter, out schemaType); + case JsonSchemaValues.TypeInteger: + return TryGetIntegerSchemaType(schemaTracker, orNull, errorReporter, out schemaType); + case JsonSchemaValues.TypeNumber: + return TryGetNumberSchemaType(schemaTracker, orNull, errorReporter, out schemaType); + case JsonSchemaValues.TypeBoolean: + return TryGetBooleanSchemaType(schemaTracker, orNull, errorReporter, out schemaType); + default: + errorReporter?.ReportError(ErrorCondition.PropertyUnsupportedValue, $"JSON Schema '{JsonSchemaValues.PropertyType}' property has unrecognized value \"{typeTracker.GetString()}\"", typeTracker.TokenIndex); + return false; + } + } + + private bool TryGetNestedNullableJsonElement(ref JsonTracker jsonTracker, ErrorReporter? errorReporter, ref bool orNull) + { + if (!jsonTracker.TryGetProperty(JsonSchemaValues.PropertyAnyOf, out JsonTracker anyOfTracker)) + { + return true; + } + + if (anyOfTracker.ValueKind != JsonValueKind.Array) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyAnyOf}' property has non-array value", anyOfTracker.TokenIndex); + return false; + } + + if (anyOfTracker.GetArrayLength() != 2) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyAnyOf}' property must have exactly two elements to represent a nullable type", anyOfTracker.TokenIndex); + return false; + } + + if (!TryDetermineNullType(anyOfTracker[0], "first", errorReporter, out bool firstIsNull) || + !TryDetermineNullType(anyOfTracker[1], "second", errorReporter, out bool secondIsNull)) + { + return false; + } + + if (firstIsNull && secondIsNull) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyAnyOf}' property has two elements that both have type '{JsonSchemaValues.TypeNull}'", anyOfTracker.TokenIndex); + return false; + } + if (!firstIsNull && !secondIsNull) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyAnyOf}' property has two elements neither of which has type '{JsonSchemaValues.TypeNull}'", anyOfTracker.TokenIndex); + return false; + } + + jsonTracker = firstIsNull ? anyOfTracker[1] : anyOfTracker[0]; + orNull = true; + return true; + } + + private bool TryDetermineNullType(JsonTracker tracker, string ordinal, ErrorReporter? errorReporter, out bool isNull) + { + isNull = false; + + if (tracker.ValueKind != JsonValueKind.Object) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyAnyOf}' {ordinal} element is not a JSON object", tracker.TokenIndex); + return false; + } + if (!tracker.TryGetProperty(JsonSchemaValues.PropertyType, out JsonTracker typeTracker)) + { + errorReporter?.ReportError(ErrorCondition.PropertyMissing, $"JSON Schema '{JsonSchemaValues.PropertyAnyOf}' {ordinal} element missing '{JsonSchemaValues.PropertyType}' property", tracker.TokenIndex); + return false; + } + if (typeTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(typeTracker.GetString())) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyAnyOf}' {ordinal} element '{JsonSchemaValues.PropertyType}' property has non-string or empty value", typeTracker.TokenIndex); + return false; + } + if (typeTracker.GetString() == JsonSchemaValues.TypeNull) + { + isNull = true; + return true; + } + + return true; + } + + private bool TryGetReferenceSchemaType( + string docName, + string? defKey, + JsonTracker schemaTracker, + bool orNull, + Dictionary? schemaTypes, + Dictionary schemaRootsByName, + ErrorReporter? errorReporter, + [NotNullWhen(true)] out SchemaType? schemaType, + [NotNullWhen(true)] out string? jsonSchemaType) + { + schemaType = null; + jsonSchemaType = null; + bool hasError = false; + + JsonTracker referencingTracker = schemaTracker.GetProperty(JsonSchemaValues.PropertyRef); + + if (!TryGetReferenceInfo(docName, referencingTracker, schemaRootsByName, errorReporter, out string refName, out string? refKey, out JsonTracker refTracker)) + { + hasError = true; + } + + foreach (KeyValuePair prop in schemaTracker.EnumerateObject()) + { + if (prop.Key != JsonSchemaValues.PropertySchema && + prop.Key != JsonSchemaValues.PropertyRef && + prop.Key != JsonSchemaValues.PropertyType && + prop.Key != JsonSchemaValues.PropertyTitle && + prop.Key != JsonSchemaValues.PropertyDescription) + { + errorReporter?.ReportWarning($"JSON Schema element has '{JsonSchemaValues.PropertyRef}' property, so '{prop.Key}' property will be ignored", prop.Value.TokenIndex); + } + } + + if (hasError || !TryGetSchemaType(refName, refKey, refTracker, orNull, null, schemaRootsByName, null, out schemaType, out jsonSchemaType)) + { + return false; + } + + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyType, out JsonTracker refTypeTracker)) + { + if (refTypeTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(refTypeTracker.GetString())) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyType}' property has non-string or empty value", refTypeTracker.TokenIndex); + return false; + } + + string referencedType = refTypeTracker.GetString(); + if (jsonSchemaType != referencedType) + { + string refString = referencingTracker.GetString(); + errorReporter?.ReportReferenceTypeError($"JSON Schema '{JsonSchemaValues.PropertyRef}' value", refString, referencingTracker.TokenIndex, referencedType, jsonSchemaType); + return false; + } + } + + if (schemaTypes != null && schemaTracker.TryGetProperty(JsonSchemaValues.PropertyTitle, out JsonTracker titleTracker) && schemaType is ReferenceType refType) + { + CodeName schemaName = new CodeName(this.typeNamer.GenerateTypeName(docName, null, titleTracker.GetString())); + if (!refType.SchemaName.Equals(schemaName)) + { + errorReporter?.RegisterSchemaName(schemaName.AsGiven, schemaTracker.TokenIndex); + string? description = schemaTracker.TryGetProperty(JsonSchemaValues.PropertyDescription, out JsonTracker descTracker) ? descTracker.GetString() : null; + schemaTypes[schemaName] = new AliasType(schemaName, description, refType.SchemaName, orNull: false); + } + } + + return true; + } + + private bool TryGetObjectSchemaType( + string docName, + string? defKey, + JsonTracker schemaTracker, + bool orNull, + Dictionary? schemaTypes, + Dictionary schemaRootsByName, + ErrorReporter? errorReporter, + [NotNullWhen(true)] out SchemaType? schemaType, + bool isTopLevel) + { + schemaType = null; + + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyAdditionalProperties, out JsonTracker addlPropsTracker)) + { + if (addlPropsTracker.ValueKind == JsonValueKind.Object) + { + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyProperties, out _)) + { + errorReporter?.ReportError(ErrorCondition.ValuesInconsistent, $"JSON Schema element has both a '{JsonSchemaValues.PropertyProperties}' property and an object-valued '{JsonSchemaValues.PropertyAdditionalProperties}' property -- intended type is ambiguous between Object and Map", schemaTracker.TokenIndex); + return false; + } + + foreach (KeyValuePair prop in schemaTracker.EnumerateObject()) + { + if (prop.Key != JsonSchemaValues.PropertySchema && + prop.Key != JsonSchemaValues.PropertyType && + prop.Key != JsonSchemaValues.PropertyTitle && + prop.Key != JsonSchemaValues.PropertyAdditionalProperties && + prop.Key != JsonSchemaValues.PropertyDescription) + { + errorReporter?.ReportWarning($"JSON Schema element defines a Map type, so '{prop.Key}' property will be ignored", prop.Value.TokenIndex); + } + } + + if (!TryGetSchemaType( + docName, + defKey, + addlPropsTracker, + orNull: false, + schemaTypes, + schemaRootsByName, + errorReporter, + out SchemaType? valueSchemaType, + out _)) + { + return false; + } + + schemaType = new MapType(valueSchemaType, orNull); + return true; + } + else if (addlPropsTracker.ValueKind != JsonValueKind.False) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyAdditionalProperties}' property must have a value that is either a JSON object or a literal false", addlPropsTracker.TokenIndex); + return false; + } + } + + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyProperties, out JsonTracker propertiesTracker)) + { + bool hasError = false; + + if (propertiesTracker.ValueKind != JsonValueKind.Object) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyProperties}' property has non-object value", propertiesTracker.TokenIndex); + return false; + } + + HashSet requiredFields = new(); + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyRequired, out JsonTracker requiredTracker)) + { + if (requiredTracker.ValueKind != JsonValueKind.Array) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyRequired}' property has non-array value", requiredTracker.TokenIndex); + hasError = true; + } + else + { + foreach (JsonTracker reqTracker in requiredTracker.EnumerateArray()) + { + string? reqName = reqTracker.GetString(); + if (reqTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(reqName)) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyRequired}' element in array has non-string or empty value", reqTracker.TokenIndex); + hasError = true; + } + else if (!propertiesTracker.TryGetProperty(reqName, out _)) + { + errorReporter?.ReportError(ErrorCondition.ItemNotFound, $"JSON Schema '{JsonSchemaValues.PropertyRequired}' element in array has value '{reqName}' that does not correspond to any property in '{JsonSchemaValues.PropertyProperties}' element", reqTracker.TokenIndex); + hasError = true; + } + else + { + requiredFields.Add(reqName!); + } + } + } + } + + Dictionary objectFields = new(); + if (schemaTypes != null) + { + foreach (KeyValuePair objProp in propertiesTracker.EnumerateObject()) + { + bool isRequired = requiredFields.Contains(objProp.Key); + if (TryGetSchemaType( + docName, + objProp.Key, + objProp.Value, + orNull: !isRequired, + schemaTypes, + schemaRootsByName, + errorReporter, + out SchemaType? fieldSchemaType, + out _)) + { + string? fieldDesc = objProp.Value.TryGetProperty(JsonSchemaValues.PropertyDescription, out JsonTracker fieldDescTracker) ? fieldDescTracker.GetString() : null; + objectFields[new CodeName(objProp.Key)] = new ObjectType.FieldInfo(fieldSchemaType, isRequired, fieldDesc); + } + else + { + hasError = true; + } + } + } + + if (!isTopLevel) + { + foreach (string internalDefsKey in JsonSchemaValues.InternalDefsKeys) + { + if (schemaTracker.TryGetProperty(internalDefsKey, out JsonTracker internalDefsTracker)) + { + errorReporter?.ReportWarning($"JSON Schema element is not a top-level Object type definition, so '{internalDefsKey}' property will be ignored", internalDefsTracker.TokenIndex); + } + } + } + + foreach (KeyValuePair prop in schemaTracker.EnumerateObject()) + { + if (prop.Key != JsonSchemaValues.PropertySchema && + prop.Key != JsonSchemaValues.PropertyType && + prop.Key != JsonSchemaValues.PropertyTitle && + prop.Key != JsonSchemaValues.PropertyProperties && + prop.Key != JsonSchemaValues.PropertyAdditionalProperties && + prop.Key != JsonSchemaValues.PropertyDescription && + prop.Key != JsonSchemaValues.PropertyRequired && + !JsonSchemaValues.InternalDefsKeys.Contains(prop.Key)) + { + errorReporter?.ReportWarning($"JSON Schema element defines an Object type, so '{prop.Key}' property will be ignored", prop.Value.TokenIndex); + } + } + + if (hasError) + { + return false; + } + + string? title = schemaTracker.TryGetProperty(JsonSchemaValues.PropertyTitle, out JsonTracker titleTracker) ? titleTracker.GetString() : null; + CodeName schemaName = new CodeName(this.typeNamer.GenerateTypeName(docName, defKey, title)); + + if (schemaTypes != null) + { + errorReporter?.RegisterSchemaName(schemaName.AsGiven, schemaTracker.TokenIndex); + string? description = schemaTracker.TryGetProperty(JsonSchemaValues.PropertyDescription, out JsonTracker descTracker) ? descTracker.GetString() : null; + schemaTypes[schemaName] = new ObjectType( + schemaName, + description, + objectFields, + orNull: false); + } + + schemaType = new ReferenceType(schemaName, isNullable: true, orNull: orNull); + return true; + } + + errorReporter?.ReportError(ErrorCondition.PropertyMissing, $"JSON Schema element has neither a '{JsonSchemaValues.PropertyProperties}' property nor an object-valued '{JsonSchemaValues.PropertyAdditionalProperties}' property", schemaTracker.TokenIndex); + return false; + } + + private bool TryGetArraySchemaType( + string docName, + string? defKey, + JsonTracker schemaTracker, + bool orNull, + Dictionary? schemaTypes, + Dictionary schemaRootsByName, + ErrorReporter? errorReporter, + [NotNullWhen(true)] out SchemaType? schemaType) + { + schemaType = null; + bool hasError = false; + + if (!schemaTracker.TryGetProperty(JsonSchemaValues.PropertyItems, out JsonTracker itemsTracker)) + { + errorReporter?.ReportError(ErrorCondition.PropertyMissing, $"JSON Schema element has type '{JsonSchemaValues.TypeArray}' but no '{JsonSchemaValues.PropertyItems}' property", schemaTracker.TokenIndex); + hasError = true; + } + else if (itemsTracker.ValueKind != JsonValueKind.Object) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyItems}' property has non-object value", itemsTracker.TokenIndex); + hasError = true; + } + + foreach (KeyValuePair prop in schemaTracker.EnumerateObject()) + { + if (prop.Key != JsonSchemaValues.PropertySchema && + prop.Key != JsonSchemaValues.PropertyType && + prop.Key != JsonSchemaValues.PropertyTitle && + prop.Key != JsonSchemaValues.PropertyItems && + prop.Key != JsonSchemaValues.PropertyDescription) + { + errorReporter?.ReportWarning($"JSON Schema element defines an Array type, so '{prop.Key}' property will be ignored", prop.Value.TokenIndex); + } + } + + if (hasError || !TryGetSchemaType( + docName, + defKey, + itemsTracker, + orNull: false, + schemaTypes, + schemaRootsByName, + errorReporter, + out SchemaType? itemSchemaType, + out _)) + { + return false; + } + + schemaType = new ArrayType(itemSchemaType, orNull); + return true; + } + + private bool TryGetStringSchemaType( + string docName, + string? defKey, + JsonTracker schemaTracker, + bool orNull, + Dictionary? schemaTypes, + Dictionary schemaRootsByName, + ErrorReporter? errorReporter, + [NotNullWhen(true)] out SchemaType? schemaType) + { + schemaType = null; + int modifierCount = 0; + bool hasError = false; + + if (!schemaTracker.TryGetProperty(JsonSchemaValues.PropertyEnum, out JsonTracker enumTracker)) + { + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyFormat, out JsonTracker formatTracker)) + { + modifierCount++; + if (formatTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(formatTracker.GetString())) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyFormat}' property has non-string or empty value", formatTracker.TokenIndex); + hasError = true; + } + } + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyContentEncoding, out JsonTracker encodingTracker)) + { + modifierCount++; + if (encodingTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(encodingTracker.GetString())) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyContentEncoding}' property has non-string or empty value", encodingTracker.TokenIndex); + hasError = true; + } + } + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyPattern, out JsonTracker patternTracker)) + { + modifierCount++; + if (patternTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(patternTracker.GetString())) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyPattern}' property has non-string or empty value", patternTracker.TokenIndex); + hasError = true; + } + } + + if (modifierCount > 1) + { + errorReporter?.ReportError(ErrorCondition.ValuesInconsistent, $"JSON Schema '{JsonSchemaValues.TypeString}' type can have at most one of '{JsonSchemaValues.PropertyFormat}', '{JsonSchemaValues.PropertyContentEncoding}', or '{JsonSchemaValues.PropertyPattern}' properties", schemaTracker.TokenIndex); + hasError = true; + } + + foreach (KeyValuePair prop in schemaTracker.EnumerateObject()) + { + if (prop.Key != JsonSchemaValues.PropertySchema && + prop.Key != JsonSchemaValues.PropertyType && + prop.Key != JsonSchemaValues.PropertyFormat && + prop.Key != JsonSchemaValues.PropertyContentEncoding && + prop.Key != JsonSchemaValues.PropertyPattern && + prop.Key != JsonSchemaValues.PropertyDescription) + { + errorReporter?.ReportWarning($"JSON Schema element defines a String type, so '{prop.Key}' property will be ignored", prop.Value.TokenIndex); + } + } + + if (hasError) + { + return false; + } + + if (formatTracker.ValueKind == JsonValueKind.String) + { + switch (formatTracker.GetString()!) + { + case JsonSchemaValues.FormatDate: + schemaType = new DateType(orNull); + return true; + case JsonSchemaValues.FormatDateTime: + schemaType = new DateTimeType(orNull); + return true; + case JsonSchemaValues.FormatTime: + schemaType = new TimeType(orNull); + return true; + case JsonSchemaValues.FormatDuration: + schemaType = new DurationType(orNull); + return true; + case JsonSchemaValues.FormatUuid: + schemaType = new UuidType(orNull); + return true; + default: + errorReporter?.ReportError(ErrorCondition.PropertyUnsupportedValue, $"JSON Schema '{JsonSchemaValues.PropertyFormat}' property has unrecognized value \"{formatTracker.GetString()}\"", formatTracker.TokenIndex); + return false; + } + } + + if (encodingTracker.ValueKind == JsonValueKind.String) + { + switch (encodingTracker.GetString()!) + { + case JsonSchemaValues.ContentEncodingBase64: + schemaType = new BytesType(orNull); + return true; + default: + errorReporter?.ReportError(ErrorCondition.PropertyUnsupportedValue, $"JSON Schema '{JsonSchemaValues.PropertyContentEncoding}' property has unrecognized value \"{encodingTracker.GetString()}\"", encodingTracker.TokenIndex); + return false; + } + } + + if (patternTracker.ValueKind == JsonValueKind.String) + { + switch (patternTracker.GetString()) + { + case JsonSchemaValues.PatternDecimal: + schemaType = new DecimalType(orNull); + return true; + default: + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyPattern}' property has unprocessable value \"{patternTracker.GetString()}\"", patternTracker.TokenIndex); + return false; + } + } + + schemaType = new StringType(orNull); + return true; + } + + List enumValues = new(); + if (enumTracker.ValueKind != JsonValueKind.Array) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyEnum}' property has non-array value", enumTracker.TokenIndex); + hasError = true; + } + else + { + foreach (JsonTracker valueTracker in enumTracker.EnumerateArray()) + { + if (valueTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(valueTracker.GetString())) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyEnum}' element in array has non-string or empty value", valueTracker.TokenIndex); + hasError = true; + } + else if (!EnumValueRegex.IsMatch(valueTracker.GetString()!)) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyEnum}' element value \"{valueTracker.GetString()}\"must start with a letter and contain only alphanumerics and underscores", valueTracker.TokenIndex); + hasError = true; + } + else + { + enumValues.Add(new CodeName(valueTracker.GetString()!)); + } + } + } + + foreach (KeyValuePair prop in schemaTracker.EnumerateObject()) + { + if (prop.Key != JsonSchemaValues.PropertySchema && + prop.Key != JsonSchemaValues.PropertyType && + prop.Key != JsonSchemaValues.PropertyTitle && + prop.Key != JsonSchemaValues.PropertyEnum && + prop.Key != JsonSchemaValues.PropertyDescription) + { + errorReporter?.ReportWarning($"JSON Schema element defines an enumerated String type, so '{prop.Key}' property will be ignored", prop.Value.TokenIndex); + } + } + + if (hasError) + { + return false; + } + + string? title = schemaTracker.TryGetProperty(JsonSchemaValues.PropertyTitle, out JsonTracker titleTracker) ? titleTracker.GetString() : null; + CodeName schemaName = new CodeName(this.typeNamer.GenerateTypeName(docName, defKey, title)); + + if (schemaTypes != null) + { + errorReporter?.RegisterSchemaName(schemaName.AsGiven, schemaTracker.TokenIndex); + string? description = schemaTracker.TryGetProperty(JsonSchemaValues.PropertyDescription, out JsonTracker descTracker) ? descTracker.GetString() : null; + schemaTypes[schemaName] = new EnumType( + schemaName, + description, + enumValues.ToArray(), + orNull: false); + } + + schemaType = new ReferenceType(schemaName, isNullable: false, orNull: orNull); + return true; + } + + private bool TryGetIntegerSchemaType(JsonTracker schemaTracker, bool orNull, ErrorReporter? errorReporter, [NotNullWhen(true)] out SchemaType? schemaType) + { + schemaType = null; + bool hasError = false; + long minimum = 0; + ulong maximum = ulong.MaxValue; + + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyMaximum, out JsonTracker maxTracker)) + { + if (maxTracker.ValueKind != JsonValueKind.Number) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyMaximum}' property has non-numeric value", maxTracker.TokenIndex); + hasError = true; + } + else if (!double.IsInteger(maxTracker.GetDouble())) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyMaximum}' property has non-integer numeric value", maxTracker.TokenIndex); + hasError = true; + } + else if (maxTracker.GetDouble() < 0) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyMaximum}' property has negative value", maxTracker.TokenIndex); + hasError = true; + } + else + { + maximum = maxTracker.GetUInt64(); + } + } + + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyMinimum, out JsonTracker minTracker)) + { + if (minTracker.ValueKind != JsonValueKind.Number) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyMinimum}' property has non-numeric value", minTracker.TokenIndex); + hasError = true; + } + else if (!double.IsInteger(minTracker.GetDouble())) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyMinimum}' property has non-integer numeric value", minTracker.TokenIndex); + hasError = true; + } + else if (minTracker.GetDouble() > 0) + { + errorReporter?.ReportError(ErrorCondition.PropertyInvalid, $"JSON Schema '{JsonSchemaValues.PropertyMinimum}' property has positive value", minTracker.TokenIndex); + hasError = true; + } + else + { + minimum = minTracker.GetInt64(); + } + } + + foreach (KeyValuePair prop in schemaTracker.EnumerateObject()) + { + if (prop.Key != JsonSchemaValues.PropertySchema && + prop.Key != JsonSchemaValues.PropertyType && + prop.Key != JsonSchemaValues.PropertyMaximum && + prop.Key != JsonSchemaValues.PropertyMinimum && + prop.Key != JsonSchemaValues.PropertyDescription) + { + errorReporter?.ReportWarning($"JSON Schema element defines an Integer type, so '{prop.Key}' property will be ignored", prop.Value.TokenIndex); + } + } + + if (hasError) + { + return false; + } + + schemaType = (minimum, maximum) switch + { + ( >= (long)byte.MinValue, <= (ulong)byte.MaxValue) => new UnsignedByteType(orNull), + ( >= (long)ushort.MinValue, <= (ulong)ushort.MaxValue) => new UnsignedShortType(orNull), + ( >= (long)uint.MinValue, <= (ulong)uint.MaxValue) => new UnsignedIntegerType(orNull), + ( >= (long)ulong.MinValue, <= (ulong)ulong.MaxValue) => new UnsignedLongType(orNull), + ( >= (long)sbyte.MinValue, <= (ulong)sbyte.MaxValue) => new ByteType(orNull), + ( >= (long)short.MinValue, <= (ulong)short.MaxValue) => new ShortType(orNull), + ( >= (long)int.MinValue, <= (ulong)int.MaxValue) => new IntegerType(orNull), + ( >= (long)long.MinValue, <= (ulong)long.MaxValue) => new LongType(orNull), + _ => new UnsignedLongType(orNull), + }; + + return true; + } + + private bool TryGetNumberSchemaType(JsonTracker schemaTracker, bool orNull, ErrorReporter? errorReporter, [NotNullWhen(true)] out SchemaType? schemaType) + { + schemaType = null; + bool hasError = false; + bool isDouble = true; + + if (schemaTracker.TryGetProperty(JsonSchemaValues.PropertyFormat, out JsonTracker formatTracker)) + { + if (formatTracker.ValueKind != JsonValueKind.String || string.IsNullOrEmpty(formatTracker.GetString())) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyFormat}' property has non-string or empty value", formatTracker.TokenIndex); + hasError = true; + } + else + { + switch (formatTracker.GetString()!) + { + case JsonSchemaValues.FormatFloat: + isDouble = false; + break; + case JsonSchemaValues.FormatDouble: + isDouble = true; + break; + default: + errorReporter?.ReportError(ErrorCondition.PropertyUnsupportedValue, $"JSON Schema '{JsonSchemaValues.PropertyFormat}' property has unrecognized value -- must be either '{JsonSchemaValues.FormatFloat}' or '{JsonSchemaValues.FormatDouble}'", formatTracker.TokenIndex); + hasError = true; + break; + } + } + } + + foreach (KeyValuePair prop in schemaTracker.EnumerateObject()) + { + if (prop.Key != JsonSchemaValues.PropertySchema && + prop.Key != JsonSchemaValues.PropertyType && + prop.Key != JsonSchemaValues.PropertyFormat && + prop.Key != JsonSchemaValues.PropertyDescription) + { + errorReporter?.ReportWarning($"JSON Schema element defines a Number type, so '{prop.Key}' property will be ignored", prop.Value.TokenIndex); + } + } + + if (hasError) + { + return false; + } + + schemaType = isDouble ? new DoubleType(orNull) : new FloatType(orNull); + return true; + } + + private bool TryGetBooleanSchemaType(JsonTracker schemaTracker, bool orNull, ErrorReporter? errorReporter, [NotNullWhen(true)] out SchemaType? schemaType) + { + foreach (KeyValuePair prop in schemaTracker.EnumerateObject()) + { + if (prop.Key != JsonSchemaValues.PropertySchema && + prop.Key != JsonSchemaValues.PropertyType && + prop.Key != JsonSchemaValues.PropertyDescription) + { + errorReporter?.ReportWarning($"JSON Schema element defines a Boolean type, so '{prop.Key}' property will be ignored", prop.Value.TokenIndex); + } + } + + schemaType = new BooleanType(orNull); + return true; + } + + private bool TryGetReferenceInfo( + string docName, + JsonTracker referencingTracker, + Dictionary schemaRootsByName, + ErrorReporter? errorReporter, + out string referencedName, + out string? referencedKey, + out JsonTracker referencedTracker) + { + referencedName = string.Empty; + referencedKey = null; + referencedTracker = new JsonTracker(); + + if (referencingTracker.ValueKind != JsonValueKind.String) + { + errorReporter?.ReportError(ErrorCondition.JsonInvalid, $"JSON Schema '{JsonSchemaValues.PropertyRef}' property has non-string value", referencingTracker.TokenIndex); + return false; + } + + if (string.IsNullOrEmpty(referencingTracker.GetString())) + { + errorReporter?.ReportError(ErrorCondition.PropertyEmpty, $"JSON Schema '{JsonSchemaValues.PropertyRef}' property has empty string value", referencingTracker.TokenIndex); + return false; + } + + string refString = referencingTracker.GetString(); + string unescapedString = Uri.UnescapeDataString(refString); + int fragIx = unescapedString.IndexOf('#'); + + string baseRef = fragIx switch + { + < 0 => unescapedString, + > 0 => unescapedString.Substring(0, fragIx), + 0 => docName, + }; + string fragment = fragIx < 0 ? string.Empty : unescapedString.Substring(fragIx + 2); + + referencedName = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(docName)!, baseRef)).Replace('\\', '/'); + if (!schemaRootsByName.TryGetValue(referencedName, out SchemaRoot? schemaRoot) || schemaRoot == null) + { + errorReporter?.ReportReferenceError($"JSON Schema '{JsonSchemaValues.PropertyRef}' value", $"no file provided with name {referencedName}", refString, referencingTracker.TokenIndex); + return false; + } + + int sepIx = fragment.IndexOf('/'); + string? refCollection = sepIx > 0 ? fragment.Substring(0, sepIx) : null; + referencedKey = sepIx > 0 ? fragment.Substring(sepIx + 1) : null; + + if (referencedKey != null) + { + if (!schemaRoot.JsonTracker.TryGetProperty(refCollection!, out JsonTracker collectionTracker)) + { + errorReporter?.ReportReferenceError($"JSON Schema '{JsonSchemaValues.PropertyRef}' value", $"no root '{refCollection}' property found in {referencedName}", refString, referencingTracker.TokenIndex); + return false; + } + + if (!collectionTracker.TryGetProperty(referencedKey, out referencedTracker)) + { + errorReporter?.ReportReferenceError($"JSON Schema '{JsonSchemaValues.PropertyRef}' value", $"no '{referencedKey}' property found under root '{refCollection}' property in {referencedName}", refString, referencingTracker.TokenIndex); + return false; + } + } + else + { + referencedTracker = schemaRoot.JsonTracker; + } + + return true; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/JsonSchemaValues.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/JsonSchemaValues.cs new file mode 100644 index 0000000000..b252883700 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/JsonSchemaValues.cs @@ -0,0 +1,45 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + public static class JsonSchemaValues + { + public const string PropertyAdditionalProperties = "additionalProperties"; + public const string PropertyAnyOf = "anyOf"; + public const string PropertyContentEncoding = "contentEncoding"; + public const string PropertyDescription = "description"; + public const string PropertyEnum = "enum"; + public const string PropertyFormat = "format"; + public const string PropertyItems = "items"; + public const string PropertyMaximum = "maximum"; + public const string PropertyMinimum = "minimum"; + public const string PropertyPattern = "pattern"; + public const string PropertyProperties = "properties"; + public const string PropertyRef = "$ref"; + public const string PropertyRequired = "required"; + public const string PropertySchema = "$schema"; + public const string PropertyTitle = "title"; + public const string PropertyType = "type"; + + public const string TypeArray = "array"; + public const string TypeInteger = "integer"; + public const string TypeNumber = "number"; + public const string TypeObject = "object"; + public const string TypeString = "string"; + public const string TypeBoolean = "boolean"; + public const string TypeNull = "null"; + + public const string FormatDouble = "double"; + public const string FormatFloat = "float"; + + public const string FormatDate = "date"; + public const string FormatDateTime = "date-time"; + public const string FormatDuration = "duration"; + public const string FormatTime = "time"; + public const string FormatUuid = "uuid"; + + public const string ContentEncodingBase64 = "base64"; + + public const string PatternDecimal = "^(?:\\+|-)?(?:[1-9][0-9]*|0)(?:\\.[0-9]*)?$"; + + public static readonly string[] InternalDefsKeys = { "$defs", "definitions" }; + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/JsonTracker.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/JsonTracker.cs new file mode 100644 index 0000000000..1f14c9a34c --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/JsonTracker.cs @@ -0,0 +1,194 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Text.Json; + + public class JsonTracker + { + private string stringValue = string.Empty; + private double doubleValue = 0.0; + private ulong uint64Value = 0; + private long int64Value = 0; + private Dictionary objectProperties = new Dictionary(); + private List arrayItems = new List(); + + public long TokenIndex { get; set; } = -1; + + public JsonValueKind ValueKind { get; set; } = JsonValueKind.Undefined; + + public JsonTracker this[int index] + { + get + { + if (ValueKind != JsonValueKind.Array) + { + throw new InvalidOperationException($"Cannot index into JSON token of kind {ValueKind}"); + } + return arrayItems[index]; + } + } + + public string GetString() + { + return stringValue; + } + + public double GetDouble() + { + if (ValueKind != JsonValueKind.Number) + { + throw new InvalidOperationException($"Cannot get double value from JSON token of kind {ValueKind}"); + } + return doubleValue; + } + + public ulong GetUInt64() + { + if (ValueKind != JsonValueKind.Number) + { + throw new InvalidOperationException($"Cannot get ulong value from JSON token of kind {ValueKind}"); + } + return uint64Value; + } + + public long GetInt64() + { + if (ValueKind != JsonValueKind.Number) + { + throw new InvalidOperationException($"Cannot get long value from JSON token of kind {ValueKind}"); + } + return int64Value; + } + + public bool GetBoolean() + { + if (ValueKind != JsonValueKind.True && ValueKind != JsonValueKind.False) + { + throw new InvalidOperationException($"Cannot get boolean value from JSON token of kind {ValueKind}"); + } + return ValueKind == JsonValueKind.True; + } + + public IEnumerable> EnumerateObject() + { + if (ValueKind != JsonValueKind.Object) + { + throw new InvalidOperationException($"Cannot enumerate object properties from JSON token of kind {ValueKind}"); + } + + foreach (KeyValuePair kvp in objectProperties) + { + yield return kvp; + } + } + + public IEnumerable EnumerateArray() + { + if (ValueKind != JsonValueKind.Array) + { + throw new InvalidOperationException($"Cannot enumerate array items from JSON token of kind {ValueKind}"); + } + foreach (JsonTracker item in arrayItems) + { + yield return item; + } + } + + public bool TryGetProperty(string propertyName, [NotNullWhen(true)] out JsonTracker value) + { + if (ValueKind == JsonValueKind.Object && objectProperties.TryGetValue(propertyName, out JsonTracker? innerValue)) + { + value = innerValue; + return true; + } + else + { + value = new JsonTracker(); + return false; + } + } + + public JsonTracker GetProperty(string propertyName) + { + if (!TryGetProperty(propertyName, out JsonTracker value)) + { + throw new KeyNotFoundException($"Property '{propertyName}' not found in JSON object."); + } + + return value; + } + + public int GetArrayLength() + { + if (ValueKind != JsonValueKind.Array) + { + throw new InvalidOperationException($"Cannot get array length from JSON token of kind {ValueKind}"); + } + return arrayItems.Count; + } + + public static JsonTracker Deserialize(ref Utf8JsonReader reader) + { + JsonTracker tracker = new JsonTracker + { + TokenIndex = reader.TokenStartIndex, + }; + + switch (reader.TokenType) + { + case JsonTokenType.None: + tracker.ValueKind = JsonValueKind.Undefined; + break; + case JsonTokenType.StartObject: + tracker.ValueKind = JsonValueKind.Object; + reader.Read(); + while (reader.TokenType == JsonTokenType.PropertyName) + { + string propertyName = reader.GetString()!; + reader.Read(); + tracker.objectProperties[propertyName] = Deserialize(ref reader); + reader.Read(); + } + break; + case JsonTokenType.StartArray: + tracker.ValueKind = JsonValueKind.Array; + reader.Read(); + while (reader.TokenType != JsonTokenType.EndArray) + { + tracker.arrayItems.Add(Deserialize(ref reader)); + reader.Read(); + } + break; + case JsonTokenType.String: + tracker.ValueKind = JsonValueKind.String; + tracker.stringValue = reader.GetString() ?? string.Empty; + break; + case JsonTokenType.Number: + tracker.ValueKind = JsonValueKind.Number; + tracker.doubleValue = reader.GetDouble(); + if (reader.TryGetUInt64(out ulong uint64Value)) + { + tracker.uint64Value = uint64Value; + } + if (reader.TryGetInt64(out long int64Value)) + { + tracker.int64Value = int64Value; + } + break; + case JsonTokenType.True: + tracker.ValueKind = JsonValueKind.True; + break; + case JsonTokenType.False: + tracker.ValueKind = JsonValueKind.False; + break; + case JsonTokenType.Null: + tracker.ValueKind = JsonValueKind.Null; + break; + } + + return tracker; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/RustTypeGenerator.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/RustTypeGenerator.cs new file mode 100644 index 0000000000..2652ecad92 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/RustTypeGenerator.cs @@ -0,0 +1,24 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + + internal class RustTypeGenerator : ITypeGenerator + { + public TargetLanguage TargetLanguage { get => TargetLanguage.Rust; } + + public GeneratedItem GenerateTypeFromSchema(SchemaType schemaType, string projectName, CodeName genNamespace, SerializationFormat serFormat, string srcSubdir) + { + ITypeTemplateTransform templateTransform = schemaType switch + { + AliasType aliasType => new RustAlias(genNamespace, aliasType, srcSubdir), + ObjectType objectType => new RustObject(genNamespace, objectType, allowSkipping: serFormat == SerializationFormat.Json, srcSubdir), + EnumType enumType => new RustEnum(genNamespace, enumType, srcSubdir), + _ => throw new Exception("unrecognized schema type"), + }; + + return new GeneratedItem(templateTransform.TransformText(), templateTransform.FileName, templateTransform.FolderPath); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/SchemaRoot.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/SchemaRoot.cs new file mode 100644 index 0000000000..a69ad6cc75 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/SchemaRoot.cs @@ -0,0 +1,7 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.Text.Json; + using Azure.Iot.Operations.CodeGeneration; + + public record SchemaRoot(JsonTracker JsonTracker, string FileName, string DirectoryName, ErrorReporter ErrorReporter); +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/TypeGenerator.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/TypeGenerator.cs new file mode 100644 index 0000000000..69460035a0 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/TypeGenerator.cs @@ -0,0 +1,50 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System; + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + + public class TypeGenerator + { + private ISchemaStandardizer schemaStandardizer; + private ITypeGenerator typeGenerator; + private ErrorLog errorLog; + + public TypeGenerator(SerializationFormat serializationFormat, TargetLanguage targetLanguage, TypeNamer typeNamer, ErrorLog errorLog) + { + this.schemaStandardizer = serializationFormat switch + { + SerializationFormat.Json => new JsonSchemaStandardizer(typeNamer), + _ => throw new NotSupportedException($"Serialization format {serializationFormat} is not supported."), + }; + + this.typeGenerator = targetLanguage switch + { + TargetLanguage.CSharp => new DotNetTypeGenerator(), + TargetLanguage.Rust => new RustTypeGenerator(), + _ => throw new NotSupportedException($"Target language {targetLanguage} is not supported."), + }; + + this.errorLog = errorLog; + } + + public List GenerateTypes(Dictionary schemaTextsByName, CodeName genNamespace, string projectName, string srcSubdir) + { + List generatedTypes = new(); + + CycleBreaker cycleBreaker = new (this.typeGenerator.TargetLanguage); + + if (schemaStandardizer.TryGetStandardizedSchemas(schemaTextsByName, this.errorLog, out List standardizedSchemas)) + { + foreach (SchemaType schemaType in standardizedSchemas) + { + cycleBreaker.AddIndirectionAsNeeded(schemaType); + + generatedTypes.Add(this.typeGenerator.GenerateTypeFromSchema(schemaType, projectName, genNamespace, schemaStandardizer.SerializationFormat, srcSubdir)); + } + } + + return generatedTypes; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/TypeGeneratorSupport.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/TypeGeneratorSupport.cs new file mode 100644 index 0000000000..577c1d1c04 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/TypeGeneratorSupport.cs @@ -0,0 +1,36 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.Collections.Generic; + + internal static class TypeGeneratorSupport + { + internal static IReadOnlyCollection GetReferencedSchemas(SchemaType schemaType) + { + HashSet referencedSchemas = new(); + AddReferencedSchemaNames(referencedSchemas, schemaType); + return referencedSchemas; + } + + private static void AddReferencedSchemaNames(HashSet referencedSchemas, SchemaType schemaType) + { + switch (schemaType) + { + case ArrayType arrayType: + AddReferencedSchemaNames(referencedSchemas, arrayType.ElementSchema); + break; + case MapType mapType: + AddReferencedSchemaNames(referencedSchemas, mapType.ValueSchema); + break; + case ObjectType objectType: + foreach (var fieldInfo in objectType.FieldInfos) + { + AddReferencedSchemaNames(referencedSchemas, fieldInfo.Value.SchemaType); + } + break; + case ReferenceType referenceType: + referencedSchemas.Add(referenceType); + break; + } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/AliasType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/AliasType.cs new file mode 100644 index 0000000000..31d9840088 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/AliasType.cs @@ -0,0 +1,23 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + internal class AliasType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Alias; } + + internal AliasType(CodeName schemaName, string? description, CodeName referencedName, bool orNull) + : base(orNull) + { + SchemaName = schemaName; + Description = description; + ReferencedName = referencedName; + } + + internal CodeName SchemaName { get; } + + internal string? Description { get; } + + internal CodeName ReferencedName { get; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ArrayType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ArrayType.cs new file mode 100644 index 0000000000..0b47309aa1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ArrayType.cs @@ -0,0 +1,15 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class ArrayType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Array; } + + internal ArrayType(SchemaType elementSchema, bool orNull) + : base(orNull) + { + ElementSchema = elementSchema; + } + + internal SchemaType ElementSchema { get; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/BooleanType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/BooleanType.cs new file mode 100644 index 0000000000..1532d62c54 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/BooleanType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class BooleanType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Boolean; } + + internal BooleanType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ByteType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ByteType.cs new file mode 100644 index 0000000000..bdc8d70e20 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ByteType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class ByteType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Byte; } + + internal ByteType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/BytesType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/BytesType.cs new file mode 100644 index 0000000000..ab355c28f5 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/BytesType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class BytesType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Bytes; } + + internal BytesType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DateTimeType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DateTimeType.cs new file mode 100644 index 0000000000..2325cdfd03 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DateTimeType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class DateTimeType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.DateTime; } + + internal DateTimeType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DateType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DateType.cs new file mode 100644 index 0000000000..071c83d1a9 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DateType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class DateType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Date; } + + internal DateType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DecimalType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DecimalType.cs new file mode 100644 index 0000000000..c1ea77d7bb --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DecimalType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class DecimalType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Decimal; } + + internal DecimalType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DoubleType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DoubleType.cs new file mode 100644 index 0000000000..ad0bd4ac74 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DoubleType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class DoubleType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Double; } + + internal DoubleType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DurationType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DurationType.cs new file mode 100644 index 0000000000..4783fc96c3 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/DurationType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class DurationType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Duration; } + + internal DurationType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/EnumType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/EnumType.cs new file mode 100644 index 0000000000..11f5729633 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/EnumType.cs @@ -0,0 +1,23 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + internal class EnumType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Enum; } + + internal EnumType(CodeName schemaName, string? description, CodeName[] enumValues, bool orNull) + : base(orNull) + { + SchemaName = schemaName; + Description = description; + EnumValues = enumValues; + } + + internal CodeName SchemaName { get; } + + internal string? Description { get; } + + internal CodeName[] EnumValues { get; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/FloatType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/FloatType.cs new file mode 100644 index 0000000000..bdd1184e12 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/FloatType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class FloatType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Float; } + + internal FloatType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/IntegerType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/IntegerType.cs new file mode 100644 index 0000000000..291ec53dce --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/IntegerType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class IntegerType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Integer; } + + internal IntegerType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/LongType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/LongType.cs new file mode 100644 index 0000000000..e16abf9ebe --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/LongType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class LongType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Long; } + + internal LongType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/MapType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/MapType.cs new file mode 100644 index 0000000000..a02786a8ac --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/MapType.cs @@ -0,0 +1,15 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class MapType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Map; } + + internal MapType(SchemaType valueSchema, bool orNull) + : base(orNull) + { + ValueSchema = valueSchema; + } + + internal SchemaType ValueSchema { get; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ObjectType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ObjectType.cs new file mode 100644 index 0000000000..895db00d52 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ObjectType.cs @@ -0,0 +1,43 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.Collections.Generic; + using Azure.Iot.Operations.CodeGeneration; + + internal class ObjectType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Object; } + + internal ObjectType(CodeName schemaName, string? description, Dictionary fieldInfos, bool orNull) + : base(orNull) + { + SchemaName = schemaName; + Description = description; + FieldInfos = fieldInfos; + } + + internal CodeName SchemaName { get; } + + internal string? Description { get; } + + internal Dictionary FieldInfos { get; } + + internal class FieldInfo + { + internal FieldInfo(SchemaType schemaType, bool isRequired, string? description) + { + IsIndirect = false; + SchemaType = schemaType; + IsRequired = isRequired; + Description = description; + } + + internal bool IsIndirect { get; set; } + + internal SchemaType SchemaType { get; } + + internal bool IsRequired { get; } + + internal string? Description { get; } + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ReferenceType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ReferenceType.cs new file mode 100644 index 0000000000..dbea0bbab1 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ReferenceType.cs @@ -0,0 +1,35 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + internal class ReferenceType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Reference; } + + internal ReferenceType(CodeName schemaName, bool isNullable = true, bool orNull = false) + : base(orNull) + { + SchemaName = schemaName; + IsNullable = isNullable; + } + + internal CodeName SchemaName { get; } + + internal bool IsNullable { get; } + + public override bool Equals(object? obj) + { + return Equals(obj as ReferenceType); + } + + internal bool Equals(ReferenceType? other) + { + return !ReferenceEquals(null, other) && SchemaName.Equals(other.SchemaName); + } + + public override int GetHashCode() + { + return SchemaName.GetHashCode(); + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/SchemaKind.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/SchemaKind.cs new file mode 100644 index 0000000000..7fb361b9cc --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/SchemaKind.cs @@ -0,0 +1,32 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal enum SchemaKind + { + Alias, + Array, + Boolean, + Bytes, + Byte, + DateTime, + Date, + Decimal, + Double, + Duration, + Enum, + Float, + Integer, + Long, + Map, + Object, + Reference, + Schema, + Short, + String, + Time, + UnsignedByte, + UnsignedInteger, + UnsignedLong, + UnsignedShort, + Uuid, + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/SchemaType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/SchemaType.cs new file mode 100644 index 0000000000..05fe440569 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/SchemaType.cs @@ -0,0 +1,14 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal abstract class SchemaType + { + internal SchemaType(bool orNull) + { + OrNull = orNull; + } + + internal abstract SchemaKind Kind { get; } + + internal bool OrNull { get; } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ShortType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ShortType.cs new file mode 100644 index 0000000000..cbadc85da5 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/ShortType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class ShortType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Short; } + + internal ShortType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/StringType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/StringType.cs new file mode 100644 index 0000000000..939b5dce65 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/StringType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class StringType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.String; } + + internal StringType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/TimeType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/TimeType.cs new file mode 100644 index 0000000000..2b6a15795f --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/TimeType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class TimeType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Time; } + + internal TimeType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedByteType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedByteType.cs new file mode 100644 index 0000000000..42eeb68b85 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedByteType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class UnsignedByteType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.UnsignedByte; } + + internal UnsignedByteType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedIntegerType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedIntegerType.cs new file mode 100644 index 0000000000..45950d2353 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedIntegerType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class UnsignedIntegerType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.UnsignedInteger; } + + internal UnsignedIntegerType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedLongType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedLongType.cs new file mode 100644 index 0000000000..9c92d6867e --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedLongType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class UnsignedLongType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.UnsignedLong; } + + internal UnsignedLongType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedShortType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedShortType.cs new file mode 100644 index 0000000000..f7dbdff6de --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UnsignedShortType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class UnsignedShortType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.UnsignedShort; } + + internal UnsignedShortType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UuidType.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UuidType.cs new file mode 100644 index 0000000000..09603f7572 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/common/UuidType.cs @@ -0,0 +1,12 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + internal class UuidType : SchemaType + { + internal override SchemaKind Kind { get => SchemaKind.Uuid; } + + internal UuidType(bool orNull) + : base(orNull) + { + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/DotNetSchemaSupport.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/DotNetSchemaSupport.cs new file mode 100644 index 0000000000..f97ab8fa01 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/DotNetSchemaSupport.cs @@ -0,0 +1,55 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System; + using Azure.Iot.Operations.CodeGeneration; + + internal static class DotNetSchemaSupport + { + internal static string GetType(SchemaType schemaType) + { + return schemaType switch + { + ArrayType arrayType => $"List<{GetType(arrayType.ElementSchema)}>{(schemaType.OrNull ? "?" : "")}", + MapType mapType => $"Dictionary{(schemaType.OrNull ? "?" : "")}", + ObjectType objectType => $"{objectType.SchemaName.GetTypeName(TargetLanguage.CSharp)}{(schemaType.OrNull ? "?" : "")}", + EnumType enumType => $"{enumType.SchemaName.GetTypeName(TargetLanguage.CSharp)}{(schemaType.OrNull ? "?" : "")}", + BooleanType _ => $"bool{(schemaType.OrNull ? "?" : "")}", + DoubleType _ => $"double{(schemaType.OrNull ? "?" : "")}", + FloatType _ => $"float{(schemaType.OrNull ? "?" : "")}", + IntegerType _ => $"int{(schemaType.OrNull ? "?" : "")}", + LongType _ => $"long{(schemaType.OrNull ? "?" : "")}", + ByteType _ => $"sbyte{(schemaType.OrNull ? "?" : "")}", + ShortType _ => $"short{(schemaType.OrNull ? "?" : "")}", + UnsignedIntegerType _ => $"uint{(schemaType.OrNull ? "?" : "")}", + UnsignedLongType _ => $"ulong{(schemaType.OrNull ? "?" : "")}", + UnsignedByteType _ => $"byte{(schemaType.OrNull ? "?" : "")}", + UnsignedShortType _ => $"ushort{(schemaType.OrNull ? "?" : "")}", + DateType _ => $"DateOnly{(schemaType.OrNull ? "?" : "")}", + DateTimeType _ => $"DateTime{(schemaType.OrNull ? "?" : "")}", + TimeType _ => $"TimeOnly{(schemaType.OrNull ? "?" : "")}", + DurationType _ => $"TimeSpan{(schemaType.OrNull ? "?" : "")}", + UuidType _ => $"Guid{(schemaType.OrNull ? "?" : "")}", + StringType _ => $"string{(schemaType.OrNull ? "?" : "")}", + BytesType _ => $"byte[]{(schemaType.OrNull ? "?" : "")}", + DecimalType _ => $"DecimalString{(schemaType.OrNull ? "?" : "")}", + ReferenceType referenceType => $"{referenceType.SchemaName.GetTypeName(TargetLanguage.CSharp)}{(schemaType.OrNull ? "?" : "")}", + _ => throw new Exception($"unrecognized SchemaType type {schemaType.GetType()}"), + }; + } + + internal static bool IsNullable(SchemaType schemaType) + { + return schemaType switch + { + ArrayType _ => true, + MapType _ => true, + ObjectType _ => true, + StringType _ => true, + BytesType _ => true, + DecimalType _ => true, + ReferenceType refType => refType.IsNullable, + _ => false, + }; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/code/DotNetAlias.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/code/DotNetAlias.cs new file mode 100644 index 0000000000..6f9b0ca7bd --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/code/DotNetAlias.cs @@ -0,0 +1,22 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetAlias : ITypeTemplateTransform + { + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly AliasType aliasType; + + internal DotNetAlias(string projectName, CodeName genNamespace, AliasType aliasType) + { + this.projectName = projectName; + this.genNamespace = genNamespace; + this.aliasType = aliasType; + } + + public string FileName { get => $"{this.aliasType.SchemaName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/code/DotNetEnum.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/code/DotNetEnum.cs new file mode 100644 index 0000000000..9793146f94 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/code/DotNetEnum.cs @@ -0,0 +1,22 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetEnum : ITypeTemplateTransform + { + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly EnumType enumType; + + internal DotNetEnum(string projectName, CodeName genNamespace, EnumType enumType) + { + this.projectName = projectName; + this.genNamespace = genNamespace; + this.enumType = enumType; + } + + public string FileName { get => $"{this.enumType.SchemaName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/code/DotNetObject.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/code/DotNetObject.cs new file mode 100644 index 0000000000..bcc539e156 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/code/DotNetObject.cs @@ -0,0 +1,28 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.Collections.Generic; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + + public partial class DotNetObject : ITypeTemplateTransform + { + private readonly string projectName; + private readonly CodeName genNamespace; + private readonly ObjectType objectType; + private readonly SerializationFormat serFormat; + private readonly bool needsNullCheck; + + internal DotNetObject(string projectName, CodeName genNamespace, ObjectType objectType, SerializationFormat serFormat) + { + this.projectName = projectName; + this.genNamespace = genNamespace; + this.objectType = objectType; + this.serFormat = serFormat; + this.needsNullCheck = objectType.FieldInfos.Any(fi => !fi.Value.SchemaType.OrNull && DotNetSchemaSupport.IsNullable(fi.Value.SchemaType)); + } + + public string FileName { get => $"{this.objectType.SchemaName.GetFileName(TargetLanguage.CSharp)}.g.cs"; } + + public string FolderPath { get => this.genNamespace.GetFolderName(TargetLanguage.CSharp); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetAlias.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetAlias.cs new file mode 100644 index 0000000000..f074e4615d --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetAlias.cs @@ -0,0 +1,318 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetAlias : DotNetAliasBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n"); + if (this.aliasType.Description != null) { + this.Write(" /// \r\n /// "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasType.Description)); + this.Write("\r\n /// \r\n"); + } + this.Write("global using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasType.SchemaName.GetTypeName(TargetLanguage.CSharp))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasType.ReferencedName.GetTypeName(TargetLanguage.CSharp))); + this.Write(";\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetAliasBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetAlias.tt b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetAlias.tt new file mode 100644 index 0000000000..d73fec5d64 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetAlias.tt @@ -0,0 +1,10 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +<# if (this.aliasType.Description != null) { #> + /// + /// <#=this.aliasType.Description#> + /// +<# } #> +global using <#=this.aliasType.SchemaName.GetTypeName(TargetLanguage.CSharp)#> = <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#>.<#=this.aliasType.ReferencedName.GetTypeName(TargetLanguage.CSharp)#>; diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetEnum.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetEnum.cs new file mode 100644 index 0000000000..082ce1b898 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetEnum.cs @@ -0,0 +1,332 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetEnum : DotNetEnumBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n"); + if (this.enumType.SchemaName.AsGiven.All(c => char.IsLower(c))) { + this.Write("\r\n#pragma warning disable CS8981 // The type name \'"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.enumType.SchemaName.AsGiven)); + this.Write("\' only contains lower-cased ascii characters.\r\n"); + } + this.Write("\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n"); + if (this.enumType.Description != null) { + this.Write(" /// \r\n /// "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.enumType.Description)); + this.Write("\r\n /// \r\n"); + } + this.Write(" [System.CodeDom.Compiler.GeneratedCode(\"Azure.Iot.Operations.ProtocolCompiler" + + "Lib\", \""); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("\")]\r\n public enum "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.enumType.SchemaName.AsGiven)); + this.Write("\r\n {\r\n"); + foreach (var enumValue in this.enumType.EnumValues) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(enumValue.AsGiven)); + this.Write(",\r\n"); + } + this.Write(" }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetEnumBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetEnum.tt b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetEnum.tt new file mode 100644 index 0000000000..d726f94c22 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetEnum.tt @@ -0,0 +1,26 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable +<# if (this.enumType.SchemaName.AsGiven.All(c => char.IsLower(c))) { #> + +#pragma warning disable CS8981 // The type name '<#=this.enumType.SchemaName.AsGiven#>' only contains lower-cased ascii characters. +<# } #> + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ +<# if (this.enumType.Description != null) { #> + /// + /// <#=this.enumType.Description#> + /// +<# } #> + [System.CodeDom.Compiler.GeneratedCode("Azure.Iot.Operations.ProtocolCompilerLib", "<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>")] + public enum <#=this.enumType.SchemaName.AsGiven#> + { +<# foreach (var enumValue in this.enumType.EnumValues) { #> + <#=enumValue.AsGiven#>, +<# } #> + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetObject.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetObject.cs new file mode 100644 index 0000000000..d7c8da4129 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetObject.cs @@ -0,0 +1,381 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class DotNetObject : DotNetObjectBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\n#nullable enable\r\n\r\nnamespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write("."); + this.Write(this.ToStringHelper.ToStringWithCulture(this.genNamespace.GetTypeName(TargetLanguage.CSharp))); + this.Write("\r\n{\r\n using System;\r\n using System.Collections.Generic;\r\n"); + if (this.serFormat == SerializationFormat.Json) { + this.Write(" using System.Text.Json.Serialization;\r\n"); + } + this.Write(" using "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.projectName)); + this.Write(";\r\n\r\n"); + if (this.objectType.Description != null) { + this.Write(" /// \r\n /// "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.objectType.Description)); + this.Write("\r\n /// \r\n"); + } + this.Write(" [System.CodeDom.Compiler.GeneratedCode(\"Azure.Iot.Operations.ProtocolCompiler" + + "Lib\", \""); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("\")]\r\n public partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.objectType.SchemaName.GetTypeName(TargetLanguage.CSharp))); + this.Write(this.ToStringHelper.ToStringWithCulture(this.Exports())); + this.Write("\r\n {\r\n"); + foreach (var fieldInfo in this.objectType.FieldInfos) { + if (fieldInfo.Value.Description != null) { + this.Write(" /// \r\n /// "); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Value.Description)); + this.Write("\r\n /// \r\n"); + } + if (this.serFormat == SerializationFormat.Json) { + this.Write(" [JsonPropertyName(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Key.AsGiven)); + this.Write("\")]\r\n [JsonIgnore(Condition = JsonIgnoreCondition."); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Value.IsRequired ? "Never" : "WhenWritingDefault")); + this.Write(")]\r\n"); + if (fieldInfo.Value.IsRequired) { + this.Write(" [JsonRequired]\r\n"); + } + } + this.Write(" public "); + this.Write(this.ToStringHelper.ToStringWithCulture(DotNetSchemaSupport.GetType(fieldInfo.Value.SchemaType))); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Key.GetFieldName(TargetLanguage.CSharp))); + this.Write(" { get; set; } = default"); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Value.SchemaType.OrNull ? "" : "!")); + this.Write(";\r\n\r\n"); + } + if (this.serFormat == SerializationFormat.Json && this.needsNullCheck) { + this.Write(" void IJsonOnDeserialized.OnDeserialized()\r\n {\r\n"); + foreach (var fieldInfo in this.objectType.FieldInfos) { + if (!fieldInfo.Value.SchemaType.OrNull && DotNetSchemaSupport.IsNullable(fieldInfo.Value.SchemaType)) { + this.Write(" if ("); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Key.GetFieldName(TargetLanguage.CSharp))); + this.Write(" is null)\r\n {\r\n throw new ArgumentNullException(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Key.AsGiven)); + this.Write(" field cannot be null\");\r\n }\r\n"); + } + } + this.Write(" }\r\n\r\n void IJsonOnSerializing.OnSerializing()\r\n {\r\n"); + foreach (var fieldInfo in this.objectType.FieldInfos) { + if (!fieldInfo.Value.SchemaType.OrNull && DotNetSchemaSupport.IsNullable(fieldInfo.Value.SchemaType)) { + this.Write(" if ("); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Key.GetFieldName(TargetLanguage.CSharp))); + this.Write(" is null)\r\n {\r\n throw new ArgumentNullException(\""); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Key.AsGiven)); + this.Write(" field cannot be null\");\r\n }\r\n"); + } + } + this.Write(" }\r\n"); + } + this.Write(" }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + + public string Exports() => this.serFormat switch + { + SerializationFormat.Json when this.needsNullCheck => " : IJsonOnDeserialized, IJsonOnSerializing", + _ => "", + }; + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class DotNetObjectBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetObject.tt b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetObject.tt new file mode 100644 index 0000000000..10bfcc7772 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/dotnet/t4/DotNetObject.tt @@ -0,0 +1,73 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +#nullable enable + +namespace <#=this.projectName#>.<#=this.genNamespace.GetTypeName(TargetLanguage.CSharp)#> +{ + using System; + using System.Collections.Generic; +<# if (this.serFormat == SerializationFormat.Json) { #> + using System.Text.Json.Serialization; +<# } #> + using <#=this.projectName#>; + +<# if (this.objectType.Description != null) { #> + /// + /// <#=this.objectType.Description#> + /// +<# } #> + [System.CodeDom.Compiler.GeneratedCode("Azure.Iot.Operations.ProtocolCompilerLib", "<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>")] + public partial class <#=this.objectType.SchemaName.GetTypeName(TargetLanguage.CSharp)#><#=this.Exports()#> + { +<# foreach (var fieldInfo in this.objectType.FieldInfos) { #> +<# if (fieldInfo.Value.Description != null) { #> + /// + /// <#=fieldInfo.Value.Description#> + /// +<# } #> +<# if (this.serFormat == SerializationFormat.Json) { #> + [JsonPropertyName("<#=fieldInfo.Key.AsGiven#>")] + [JsonIgnore(Condition = JsonIgnoreCondition.<#=fieldInfo.Value.IsRequired ? "Never" : "WhenWritingDefault"#>)] +<# if (fieldInfo.Value.IsRequired) { #> + [JsonRequired] +<# } #> +<# } #> + public <#=DotNetSchemaSupport.GetType(fieldInfo.Value.SchemaType)#> <#=fieldInfo.Key.GetFieldName(TargetLanguage.CSharp)#> { get; set; } = default<#=fieldInfo.Value.SchemaType.OrNull ? "" : "!"#>; + +<# } #> +<# if (this.serFormat == SerializationFormat.Json && this.needsNullCheck) { #> + void IJsonOnDeserialized.OnDeserialized() + { +<# foreach (var fieldInfo in this.objectType.FieldInfos) { #> +<# if (!fieldInfo.Value.SchemaType.OrNull && DotNetSchemaSupport.IsNullable(fieldInfo.Value.SchemaType)) { #> + if (<#=fieldInfo.Key.GetFieldName(TargetLanguage.CSharp)#> is null) + { + throw new ArgumentNullException("<#=fieldInfo.Key.AsGiven#> field cannot be null"); + } +<# } #> +<# } #> + } + + void IJsonOnSerializing.OnSerializing() + { +<# foreach (var fieldInfo in this.objectType.FieldInfos) { #> +<# if (!fieldInfo.Value.SchemaType.OrNull && DotNetSchemaSupport.IsNullable(fieldInfo.Value.SchemaType)) { #> + if (<#=fieldInfo.Key.GetFieldName(TargetLanguage.CSharp)#> is null) + { + throw new ArgumentNullException("<#=fieldInfo.Key.AsGiven#> field cannot be null"); + } +<# } #> +<# } #> + } +<# } #> + } +} +<#+ + public string Exports() => this.serFormat switch + { + SerializationFormat.Json when this.needsNullCheck => " : IJsonOnDeserialized, IJsonOnSerializing", + _ => "", + }; +#> diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/RustSchemaSupport.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/RustSchemaSupport.cs new file mode 100644 index 0000000000..011fa908f4 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/RustSchemaSupport.cs @@ -0,0 +1,54 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System; + using Azure.Iot.Operations.CodeGeneration; + + internal static class RustSchemaSupport + { + internal static string GetType(SchemaType schemaType, bool isIndirect) + { + string innerType = schemaType switch + { + ArrayType arrayType => $"Vec<{GetType(arrayType.ElementSchema, false)}>", + MapType mapType => $"HashMap", + ObjectType objectType => objectType.SchemaName.GetTypeName(TargetLanguage.Rust), + EnumType enumType => enumType.SchemaName.GetTypeName(TargetLanguage.Rust), + BooleanType _ => "bool", + DoubleType _ => "f64", + FloatType _ => "f32", + IntegerType _ => "i32", + LongType _ => "i64", + ByteType _ => "i8", + ShortType _ => "i16", + UnsignedIntegerType _ => "u32", + UnsignedLongType _ => "u64", + UnsignedByteType _ => "u8", + UnsignedShortType _ => "u16", + DateType _ => "Date", + DateTimeType _ => "DateTime", + TimeType _ => "Time", + DurationType _ => "Duration", + UuidType _ => "Uuid", + StringType _ => "String", + BytesType _ => "Bytes", + DecimalType _ => "Decimal", + ReferenceType referenceType => referenceType.SchemaName.GetTypeName(TargetLanguage.Rust), + _ => throw new Exception($"unrecognized SchemaType type {schemaType.GetType()}"), + }; + + string wrappedType = isIndirect ? $"Box<{innerType}>" : innerType; + + return schemaType.OrNull ? $"Option<{wrappedType}>" : wrappedType; + } + + internal static bool HasNativeDefault(SchemaType schemaType) + { + return schemaType switch + { + ArrayType _ => true, + MapType _ => true, + _ => false, + }; + } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/code/RustAlias.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/code/RustAlias.cs new file mode 100644 index 0000000000..f0f9be908a --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/code/RustAlias.cs @@ -0,0 +1,23 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustAlias : ITypeTemplateTransform + { + private readonly CodeName genNamespace; + private readonly AliasType aliasType; + private readonly string srcSubdir; + + internal RustAlias(CodeName genNamespace, AliasType aliasType, string srcSubdir) + { + this.genNamespace = genNamespace; + this.aliasType = aliasType; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.aliasType.SchemaName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/code/RustEnum.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/code/RustEnum.cs new file mode 100644 index 0000000000..7d510f3f03 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/code/RustEnum.cs @@ -0,0 +1,26 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.IO; + using System.Linq; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustEnum : ITypeTemplateTransform + { + private readonly CodeName genNamespace; + private readonly EnumType enumType; + private readonly string srcSubdir; + private readonly bool hasNonPascalNames; + + internal RustEnum(CodeName genNamespace, EnumType enumType, string srcSubdir) + { + this.genNamespace = genNamespace; + this.enumType = enumType; + this.srcSubdir = srcSubdir; + this.hasNonPascalNames = this.enumType.EnumValues.Any(v => !char.IsUpper(v.AsGiven[0]) || v.AsGiven.Contains('_')); + } + + public string FileName { get => $"{this.enumType.SchemaName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/code/RustObject.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/code/RustObject.cs new file mode 100644 index 0000000000..080ca0bb84 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/code/RustObject.cs @@ -0,0 +1,28 @@ +namespace Azure.Iot.Operations.TypeGenerator +{ + using System.Collections.Generic; + using System.IO; + using Azure.Iot.Operations.CodeGeneration; + + public partial class RustObject : ITypeTemplateTransform + { + private readonly CodeName genNamespace; + private readonly ObjectType objectType; + private readonly IReadOnlyCollection referencedSchemas; + private readonly bool allowSkipping; + private readonly string srcSubdir; + + internal RustObject(CodeName genNamespace, ObjectType objectType, bool allowSkipping, string srcSubdir) + { + this.genNamespace = genNamespace; + this.objectType = objectType; + this.referencedSchemas = TypeGeneratorSupport.GetReferencedSchemas(objectType); + this.allowSkipping = allowSkipping; + this.srcSubdir = srcSubdir; + } + + public string FileName { get => $"{this.objectType.SchemaName.GetFileName(TargetLanguage.Rust)}.rs"; } + + public string FolderPath { get => Path.Combine(this.srcSubdir, this.genNamespace.GetFolderName(TargetLanguage.Rust)); } + } +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustAlias.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustAlias.cs new file mode 100644 index 0000000000..71d7fb7c59 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustAlias.cs @@ -0,0 +1,318 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustAlias : RustAliasBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n\r\nuse super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasType.ReferencedName.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasType.ReferencedName.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n\r\n"); + if (this.aliasType.Description != null) { + this.Write("/// "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasType.Description)); + this.Write("\r\n"); + } + this.Write("pub type "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasType.SchemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(" = "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aliasType.ReferencedName.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustAliasBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustAlias.tt b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustAlias.tt new file mode 100644 index 0000000000..65de65cef4 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustAlias.tt @@ -0,0 +1,10 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ + +use super::<#=this.aliasType.ReferencedName.GetFileName(TargetLanguage.Rust)#>::<#=this.aliasType.ReferencedName.GetTypeName(TargetLanguage.Rust)#>; + +<# if (this.aliasType.Description != null) { #> +/// <#=this.aliasType.Description#> +<# } #> +pub type <#=this.aliasType.SchemaName.GetTypeName(TargetLanguage.Rust)#> = <#=this.aliasType.ReferencedName.GetTypeName(TargetLanguage.Rust)#>; diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustEnum.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustEnum.cs new file mode 100644 index 0000000000..e054669843 --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustEnum.cs @@ -0,0 +1,322 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustEnum : RustEnumBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write("; DO NOT EDIT. */\r\n"); + if (this.hasNonPascalNames) { + this.Write("\r\n#![allow(non_camel_case_types)]\r\n"); + } + this.Write("\r\nuse serde::{Deserialize, Serialize};\r\n\r\n"); + if (this.enumType.Description != null) { + this.Write("/// "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.enumType.Description)); + this.Write("\r\n"); + } + this.Write("#[derive(Serialize, Deserialize, Debug, Clone)]\r\npub enum "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.enumType.SchemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n"); + int index = 0; foreach (var enumValue in this.enumType.EnumValues) { + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(enumValue.AsGiven)); + this.Write(",\r\n"); + ++index; } + this.Write("}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustEnumBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustEnum.tt b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustEnum.tt new file mode 100644 index 0000000000..219821bfac --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustEnum.tt @@ -0,0 +1,19 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ +<# if (this.hasNonPascalNames) { #> + +#![allow(non_camel_case_types)] +<# } #> + +use serde::{Deserialize, Serialize}; + +<# if (this.enumType.Description != null) { #> +/// <#=this.enumType.Description#> +<# } #> +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum <#=this.enumType.SchemaName.GetTypeName(TargetLanguage.Rust)#> { +<# int index = 0; foreach (var enumValue in this.enumType.EnumValues) { #> + <#=enumValue.AsGiven#>, +<# ++index; } #> +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustObject.cs b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustObject.cs new file mode 100644 index 0000000000..c7681ff99b --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustObject.cs @@ -0,0 +1,363 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Azure.Iot.Operations.TypeGenerator +{ + using Azure.Iot.Operations.CodeGeneration; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class RustObject : RustObjectBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v"); + this.Write(this.ToStringHelper.ToStringWithCulture(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)); + this.Write(@"; DO NOT EDIT. */ +#![allow(unused_imports)] + +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; +use derive_builder::Builder; +use iso8601_duration::Duration; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +"); + foreach (var referencedSchema in this.referencedSchemas) { + if (!referencedSchema.SchemaName.Equals(this.objectType.SchemaName)) { + this.Write("use super::"); + this.Write(this.ToStringHelper.ToStringWithCulture(referencedSchema.SchemaName.GetFileName(TargetLanguage.Rust))); + this.Write("::"); + this.Write(this.ToStringHelper.ToStringWithCulture(referencedSchema.SchemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(";\r\n"); + } + } + this.Write("\r\n"); + if (this.objectType.Description != null) { + this.Write("/// "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.objectType.Description)); + this.Write("\r\n"); + } + this.Write("#[derive(Serialize, Deserialize, Debug, Clone, Builder)]\r\npub struct "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.objectType.SchemaName.GetTypeName(TargetLanguage.Rust))); + this.Write(" {\r\n"); + bool firstField = true; foreach (var fieldInfo in this.objectType.FieldInfos) { + if (!firstField) { + this.Write("\r\n"); + } + if (fieldInfo.Value.Description != null) { + this.Write(" /// "); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Value.Description)); + this.Write("\r\n"); + } + if (fieldInfo.Key.GetFieldName(TargetLanguage.Rust) != fieldInfo.Key.AsGiven) { + this.Write(" #[serde(rename = \""); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Key.AsGiven)); + this.Write("\")]\r\n"); + } + if (fieldInfo.Value.SchemaType.OrNull) { + if (this.allowSkipping && !fieldInfo.Value.IsRequired) { + this.Write(" #[serde(skip_serializing_if = \"Option::is_none\")]\r\n"); + } + this.Write(" #[builder(default = \"None\")]\r\n"); + } else if (RustSchemaSupport.HasNativeDefault(fieldInfo.Value.SchemaType)) { + this.Write(" #[builder(default)]\r\n"); + } + this.Write(" pub "); + this.Write(this.ToStringHelper.ToStringWithCulture(fieldInfo.Key.GetFieldName(TargetLanguage.Rust))); + this.Write(": "); + this.Write(this.ToStringHelper.ToStringWithCulture(RustSchemaSupport.GetType(fieldInfo.Value.SchemaType, fieldInfo.Value.IsIndirect))); + this.Write(",\r\n"); + firstField = false; } + this.Write("}\r\n"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class RustObjectBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustObject.tt b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustObject.tt new file mode 100644 index 0000000000..5994c8622a --- /dev/null +++ b/codegen2/src/Azure.Iot.Operations.TypeGenerator/rust/t4/RustObject.tt @@ -0,0 +1,46 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="Azure.Iot.Operations.CodeGeneration" #> +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v<#=System.Reflection.Assembly.GetExecutingAssembly().GetName().Version#>; DO NOT EDIT. */ +#![allow(unused_imports)] + +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; +use derive_builder::Builder; +use iso8601_duration::Duration; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +<# foreach (var referencedSchema in this.referencedSchemas) { #> +<# if (!referencedSchema.SchemaName.Equals(this.objectType.SchemaName)) { #> +use super::<#=referencedSchema.SchemaName.GetFileName(TargetLanguage.Rust)#>::<#=referencedSchema.SchemaName.GetTypeName(TargetLanguage.Rust)#>; +<# } #> +<# } #> + +<# if (this.objectType.Description != null) { #> +/// <#=this.objectType.Description#> +<# } #> +#[derive(Serialize, Deserialize, Debug, Clone, Builder)] +pub struct <#=this.objectType.SchemaName.GetTypeName(TargetLanguage.Rust)#> { +<# bool firstField = true; foreach (var fieldInfo in this.objectType.FieldInfos) { #> +<# if (!firstField) { #> + +<# } #> +<# if (fieldInfo.Value.Description != null) { #> + /// <#=fieldInfo.Value.Description#> +<# } #> +<# if (fieldInfo.Key.GetFieldName(TargetLanguage.Rust) != fieldInfo.Key.AsGiven) { #> + #[serde(rename = "<#=fieldInfo.Key.AsGiven#>")] +<# } #> +<# if (fieldInfo.Value.SchemaType.OrNull) { #> +<# if (this.allowSkipping && !fieldInfo.Value.IsRequired) { #> + #[serde(skip_serializing_if = "Option::is_none")] +<# } #> + #[builder(default = "None")] +<# } else if (RustSchemaSupport.HasNativeDefault(fieldInfo.Value.SchemaType)) { #> + #[builder(default)] +<# } #> + pub <#=fieldInfo.Key.GetFieldName(TargetLanguage.Rust)#>: <#=RustSchemaSupport.GetType(fieldInfo.Value.SchemaType, fieldInfo.Value.IsIndirect)#>, +<# firstField = false; } #> +} diff --git a/codegen2/src/Dtdl2Wot/Array/code/ArrayThingSchema.cs b/codegen2/src/Dtdl2Wot/Array/code/ArrayThingSchema.cs new file mode 100644 index 0000000000..e4eec34fcd --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Array/code/ArrayThingSchema.cs @@ -0,0 +1,22 @@ +namespace Dtdl2Wot +{ + using DTDLParser.Models; + + public partial class ArrayThingSchema : ITemplateTransform + { + private readonly DTArrayInfo dtArray; + private readonly int indent; + private readonly ThingDescriber thingDescriber; + + public ArrayThingSchema(DTArrayInfo dtArray, int indent, ThingDescriber thingDescriber) + { + this.dtArray = dtArray; + this.indent = indent; + this.thingDescriber = thingDescriber; + } + + public string FileName { get => string.Empty; } + + public string FolderPath { get => string.Empty; } + } +} diff --git a/codegen2/src/Dtdl2Wot/Array/t4/ArrayThingSchema.cs b/codegen2/src/Dtdl2Wot/Array/t4/ArrayThingSchema.cs new file mode 100644 index 0000000000..3bf0c31f1c --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Array/t4/ArrayThingSchema.cs @@ -0,0 +1,313 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser.Models; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class ArrayThingSchema : ArrayThingSchemaBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.PushIndent(new string(' ', this.indent)); + this.Write("\"type\": \"array\",\r\n\"items\": {\r\n"); + if (this.dtArray.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtArray.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(this.dtArray.ElementSchema, 2))); + this.Write("\r\n}"); + this.PopIndent(); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class ArrayThingSchemaBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Dtdl2Wot/Array/t4/ArrayThingSchema.tt b/codegen2/src/Dtdl2Wot/Array/t4/ArrayThingSchema.tt new file mode 100644 index 0000000000..d1f0c00671 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Array/t4/ArrayThingSchema.tt @@ -0,0 +1,12 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="DTDLParser.Models" #> +<# this.PushIndent(new string(' ', this.indent)); #> +"type": "array", +"items": { +<# if (this.dtArray.Description.Any()) { #> + "description": "<#=this.dtArray.Description.First().Value#>", +<# } #> +<#=this.thingDescriber.GetTypeAndAddenda(this.dtArray.ElementSchema, 2)#> +}<# this.PopIndent(); #> diff --git a/codegen2/src/Dtdl2Wot/CodeName.cs b/codegen2/src/Dtdl2Wot/CodeName.cs new file mode 100644 index 0000000000..fe9b3942dd --- /dev/null +++ b/codegen2/src/Dtdl2Wot/CodeName.cs @@ -0,0 +1,223 @@ +namespace Dtdl2Wot +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DTDLParser; + + public class CodeName : IEquatable + { + private readonly Dtmi? givenDtmi; + private readonly string givenName; + private readonly string[] components; + private readonly string lowerName; + private readonly string pascalName; + private readonly string camelName; + private readonly string snakeName; + + public CodeName(Dtmi dtmi) + : this(NameFromDtmi(dtmi)) + { + this.givenDtmi = dtmi; + } + + public CodeName(string givenName = "") + : this(givenName, Decompose(givenName)) + { + } + + public CodeName(string baseName, string suffix1, string? suffix2 = null, string? suffix3 = null) + : this(Extend(baseName, suffix1, suffix2, suffix3), DecomposeAndExtend(baseName, suffix1, suffix2, suffix3)) + { + } + + public CodeName(CodeName baseName, string suffix1) + : this(Extend(baseName.AsGiven, suffix1, null, null), baseName.AsComponents.Append(suffix1)) + { + } + + public CodeName(CodeName baseName, string suffix1, string suffix2) + : this(Extend(baseName.AsGiven, suffix1, suffix2, null), baseName.AsComponents.Append(suffix1).Append(suffix2)) + { + } + + public CodeName(CodeName baseName, string suffix1, string suffix2, string suffix3) + : this(Extend(baseName.AsGiven, suffix1, suffix2, suffix3), baseName.AsComponents.Append(suffix1).Append(suffix2).Append(suffix3)) + { + } + + public CodeName(string givenName, IEnumerable components) + { + this.givenName = givenName; + this.components = components.ToArray(); + lowerName = string.Concat(components); + pascalName = string.Concat(components.Select(c => char.ToUpper(c[0]) + c.Substring(1))); + camelName = pascalName.Length > 0 ? char.ToLower(pascalName[0]) + pascalName.Substring(1) : string.Empty; + snakeName = string.Join('_', components); + } + + public override string ToString() + { + throw new Exception($"ToString() called on CodeName({AsGiven})"); + } + + public override bool Equals(object? obj) + { + return Equals(obj as CodeName); + } + + public bool Equals(CodeName? other) + { + return !ReferenceEquals(null, other) && AsGiven == other.AsGiven; + } + + public override int GetHashCode() + { + return AsGiven.GetHashCode(); + } + + public bool IsEmpty => string.IsNullOrEmpty(givenName); + + public Dtmi? AsDtmi => givenDtmi; + + public string AsGiven => givenName; + + private string[] AsComponents => components; + + private string AsLower(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + return prefix ?? string.Empty + lowerName + suffix1 ?? string.Empty + suffix2 ?? string.Empty + suffix3 ?? string.Empty + suffix4 ?? string.Empty; + } + + private string AsPascal(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + return GetCapitalized(prefix) + pascalName + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + + private string AsCamel(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + if (!string.IsNullOrEmpty(prefix)) + { + return prefix + pascalName + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + else if (!string.IsNullOrEmpty(givenName)) + { + return camelName + GetCapitalized(suffix1) + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + else + { + return suffix1 + GetCapitalized(suffix2) + GetCapitalized(suffix3) + GetCapitalized(suffix4); + } + } + + private string AsSnake(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + return GetSnakePrefix(prefix) + snakeName + GetSnakeSuffix(suffix1) + GetSnakeSuffix(suffix2) + GetSnakeSuffix(suffix3) + GetSnakeSuffix(suffix4); + } + + private string AsScreamingSnake(string? suffix1 = null, string? suffix2 = null, string? suffix3 = null, string? suffix4 = null, string? prefix = null) + { + return AsSnake(suffix1, suffix2, suffix3, suffix4, prefix: prefix).ToUpperInvariant(); + } + + private static string Extend(string baseName, string suffix1, string? suffix2, string? suffix3) + { + bool snakeWise = baseName.Contains('_'); + StringBuilder givenName = new(baseName); + givenName.Append(Extension(suffix1, snakeWise)); + + if (suffix2 != null) + { + givenName.Append(Extension(suffix2, snakeWise)); + } + + if (suffix3 != null) + { + givenName.Append(Extension(suffix3, snakeWise)); + } + + return givenName.ToString(); + } + + private static List DecomposeAndExtend(string baseName, string suffix1, string? suffix2, string? suffix3) + { + List components = Decompose(baseName); + components.Add(suffix1); + + if (suffix2 != null) + { + components.Add(suffix2); + } + + if (suffix3 != null) + { + components.Add(suffix3); + } + + return components; + } + + private static List Decompose(string givenName) + { + List components = new(); + StringBuilder stringBuilder = new(); + char p = '\0'; + + foreach (char c in givenName) + { + if (((char.IsUpper(c) && char.IsLower(p)) || c == '_') && stringBuilder.Length > 0) + { + components.Add(stringBuilder.ToString()); + stringBuilder.Clear(); + } + + if (c != '_') + { + stringBuilder.Append(char.ToLower(c)); + } + + p = c; + } + + if (stringBuilder.Length > 0) + { + components.Add(stringBuilder.ToString()); + } + + return components; + } + + private static string NameFromDtmi(Dtmi dtmi, int index = -1) + { + if (index < 0) + { + index = dtmi.Labels.Length - 1; + } + + string lastLabel = dtmi.Labels[index]; + string prefix = !lastLabel.StartsWith("_") || lastLabel.StartsWith("__") ? string.Empty : NameFromDtmi(dtmi, index - 1); + return prefix + GetCapitalized(lastLabel.TrimStart('_')); + } + + private static string Extension(string suffix, bool snakeWise) + { + return snakeWise ? GetSnakeSuffix(suffix) : GetCapitalized(suffix); + } + + private static string GetCapitalized(string? suffix) + { + return suffix == null ? string.Empty : char.ToUpperInvariant(suffix[0]) + suffix.Substring(1); + } + + private static string GetSnakeSuffix(string? suffix) + { + return suffix == null ? string.Empty : $"_{suffix}"; + } + + private static string GetSnakePrefix(string? prefix) + { + return prefix == null ? string.Empty : $"{prefix}_"; + } + } +} diff --git a/codegen2/src/Dtdl2Wot/Command/code/CommandAffordance.cs b/codegen2/src/Dtdl2Wot/Command/code/CommandAffordance.cs new file mode 100644 index 0000000000..55aceab647 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Command/code/CommandAffordance.cs @@ -0,0 +1,63 @@ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser; + using DTDLParser.Models; + + public partial class CommandAffordance : ITemplateTransform + { + private readonly DTCommandInfo dtCommand; + private readonly bool usesTypes; + private readonly string contentType; + private readonly string commandTopic; + private readonly string? serviceGroupId; + private readonly bool isResponseSchemaResult; + private readonly bool isRequestTransparent; + private readonly bool isResponseTransparent; + private readonly bool isCommandIdempotent; + private readonly bool isCommandCacheable; + private readonly string? responseName; + private readonly DTSchemaInfo? responseSchema; + private readonly IReadOnlyDictionary? responseDescription; + private readonly string? errorSchemaName; + private readonly string? infoSchemaName; + private readonly Dictionary codeEnumeration; + private readonly ThingDescriber thingDescriber; + + public CommandAffordance(DTCommandInfo dtCommand, int mqttVersion, bool usesTypes, string contentType, string commandTopic, string? serviceGroupId, ThingDescriber thingDescriber) + { + this.dtCommand = dtCommand; + this.usesTypes = usesTypes; + this.contentType = contentType; + this.commandTopic = commandTopic.Replace(DtdlMqttTopicTokens.CommandName, this.dtCommand.Name); + this.serviceGroupId = serviceGroupId; + + this.isResponseSchemaResult = dtCommand.Response?.Schema != null && dtCommand.Response.Schema.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.ResultAdjunctTypeFormat, mqttVersion))); + DTFieldInfo? normalField = (dtCommand.Response?.Schema as DTObjectInfo)?.Fields?.FirstOrDefault(f => f.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.NormalResultAdjunctTypeFormat, mqttVersion)))); + DTFieldInfo? errorField = (dtCommand.Response?.Schema as DTObjectInfo)?.Fields?.FirstOrDefault(f => f.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.ErrorResultAdjunctTypeFormat, mqttVersion)))); + DTFieldInfo? infoField = (dtCommand.Response?.Schema as DTObjectInfo)?.Fields?.FirstOrDefault(f => f.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.ErrorInfoAdjunctTypeFormat, mqttVersion)))); + DTFieldInfo? codeField = (dtCommand.Response?.Schema as DTObjectInfo)?.Fields?.FirstOrDefault(f => f.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.ErrorCodeAdjunctTypeFormat, mqttVersion)))); + + this.isRequestTransparent = dtCommand.Request != null && dtCommand.Request.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.TransparentAdjunctTypeFormat, mqttVersion))); + this.isResponseTransparent = !isResponseSchemaResult && dtCommand.Response != null && dtCommand.Response.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.TransparentAdjunctTypeFormat, mqttVersion))); + this.isCommandIdempotent = dtCommand.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.IdempotentAdjunctTypeFormat, mqttVersion))); + this.isCommandCacheable = dtCommand.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.CacheableAdjunctTypeFormat, mqttVersion))); + + this.responseName = isResponseSchemaResult ? normalField?.Name : dtCommand.Response?.Name; + this.responseSchema = isResponseSchemaResult ? normalField?.Schema : dtCommand.Response?.Schema; + this.responseDescription = isResponseSchemaResult ? normalField?.Description : dtCommand.Response?.Description; + this.errorSchemaName = errorField != null ? new CodeName(errorField.Schema.Id).AsGiven : null; + this.infoSchemaName = infoField != null ? new CodeName(infoField.Schema.Id).AsGiven : null; + this.codeEnumeration = codeField != null ? ((DTEnumInfo)codeField.Schema).EnumValues.ToDictionary(v => v.Name, v => v.EnumValue.ToString()!) : new(); + + this.thingDescriber = thingDescriber; + } + + public string FileName { get => string.Empty; } + + public string FolderPath { get => string.Empty; } + + private static string Capitalize(string input) => input.Length == 0 ? input : char.ToUpper(input[0]) + input.Substring(1); + } +} diff --git a/codegen2/src/Dtdl2Wot/Command/t4/CommandAffordance.cs b/codegen2/src/Dtdl2Wot/Command/t4/CommandAffordance.cs new file mode 100644 index 0000000000..5f4163e8ed --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Command/t4/CommandAffordance.cs @@ -0,0 +1,444 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser; + using DTDLParser.Models; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class CommandAffordance : CommandAffordanceBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Name)); + this.Write("\": {\r\n"); + if (this.dtCommand.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Description.First().Value)); + this.Write("\",\r\n"); + } + if (this.dtCommand.Request != null) { + if (this.usesTypes) { + this.Write(" \"input\": {\r\n"); + if (this.isRequestTransparent) { + if (this.dtCommand.Request.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Request.Description.First().Value)); + this.Write("\",\r\n"); + } else if (this.dtCommand.Request.Schema.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Request.Schema.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(this.dtCommand.Request.Schema, 8))); + this.Write("\r\n"); + } else { + if (this.dtCommand.Request.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Request.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(" \"type\": \"object\",\r\n"); + if (!this.dtCommand.Request.Nullable) { + this.Write(" \"required\": [ \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Request.Name)); + this.Write("\" ],\r\n"); + } + this.Write(" \"properties\": {\r\n \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Request.Name)); + this.Write("\": {\r\n"); + if (!this.dtCommand.Request.Schema.Id.AbsoluteUri.StartsWith("dtmi:dtdl:instance:Schema:") && this.dtCommand.Request.Schema.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Request.Schema.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(this.dtCommand.Request.Schema, 12))); + this.Write("\r\n }\r\n }\r\n"); + } + this.Write(" },\r\n"); + } else { + this.Write(" \"input\": {\r\n \"type\": \"null\"\r\n },\r\n"); + } + } + if (this.dtCommand.Response != null) { + if (this.usesTypes) { + this.Write(" \"output\": {\r\n"); + if (this.isResponseTransparent) { + if (this.dtCommand.Response.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Response.Description.First().Value)); + this.Write("\",\r\n"); + } else if (this.dtCommand.Response.Schema.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Response.Schema.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(this.dtCommand.Response.Schema, 8))); + this.Write("\r\n"); + } else { + if (this.dtCommand.Response.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtCommand.Response.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(" \"type\": \"object\",\r\n"); + if (!this.dtCommand.Response.Nullable) { + this.Write(" \"required\": [ \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.responseName)); + this.Write("\" ],\r\n"); + } + this.Write(" \"properties\": {\r\n \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.responseName)); + this.Write("\": {\r\n"); + if (this.responseDescription?.Any() ?? false) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.responseDescription.First().Value)); + this.Write("\",\r\n"); + } else if (!this.responseSchema.Id.AbsoluteUri.StartsWith("dtmi:dtdl:instance:Schema:") && this.responseSchema.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.responseSchema.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(this.responseSchema, 12))); + this.Write("\r\n }\r\n }\r\n"); + } + this.Write(" },\r\n"); + } else { + this.Write(" \"output\": {\r\n \"type\": \"null\"\r\n },\r\n"); + } + } + if (this.isCommandIdempotent) { + this.Write(" \"idempotent\": true,\r\n"); + } + if (this.isCommandCacheable) { + this.Write(" \"safe\": true,\r\n"); + } + this.Write(" \"forms\": [\r\n {\r\n \"contentType\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\",\r\n"); + if (this.isResponseSchemaResult) { + if (this.errorSchemaName != null) { + this.Write(" \"additionalResponses\": [\r\n {\r\n \"success\": false" + + ",\r\n \"schema\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.errorSchemaName)); + this.Write("\"\r\n }\r\n ],\r\n"); + } + if (this.infoSchemaName != null) { + this.Write(" \"dtv:headerInfo\": [\r\n {\r\n \"contentType\": \"appli" + + "cation/json\",\r\n \"schema\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.infoSchemaName)); + this.Write("\"\r\n }\r\n ],\r\n"); + } + if (this.codeEnumeration.Count > 0) { + this.Write(" \"dtv:headerCode\": [\r\n"); + int ix = 1; foreach (KeyValuePair kvp in this.codeEnumeration) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(kvp.Key)); + this.Write("\""); + this.Write(this.ToStringHelper.ToStringWithCulture(ix < this.codeEnumeration.Count ? "," : "")); + this.Write("\r\n"); + ix++; } + this.Write(" ],\r\n"); + } + } + if (this.serviceGroupId != null) { + this.Write(" \"dtv:serviceGroupId\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceGroupId)); + this.Write("\",\r\n"); + } + this.Write(" \"dtv:topic\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.commandTopic ?? "")); + this.Write("\",\r\n \"op\": \"invokeaction\"\r\n }\r\n ]\r\n }"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class CommandAffordanceBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Dtdl2Wot/Command/t4/CommandAffordance.tt b/codegen2/src/Dtdl2Wot/Command/t4/CommandAffordance.tt new file mode 100644 index 0000000000..304e635db3 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Command/t4/CommandAffordance.tt @@ -0,0 +1,121 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="DTDLParser" #> +<#@ import namespace="DTDLParser.Models" #> + "<#=this.dtCommand.Name#>": { +<# if (this.dtCommand.Description.Any()) { #> + "description": "<#=this.dtCommand.Description.First().Value#>", +<# } #> +<# if (this.dtCommand.Request != null) { #> +<# if (this.usesTypes) { #> + "input": { +<# if (this.isRequestTransparent) { #> +<# if (this.dtCommand.Request.Description.Any()) { #> + "description": "<#=this.dtCommand.Request.Description.First().Value#>", +<# } else if (this.dtCommand.Request.Schema.Description.Any()) { #> + "description": "<#=this.dtCommand.Request.Schema.Description.First().Value#>", +<# } #> +<#=this.thingDescriber.GetTypeAndAddenda(this.dtCommand.Request.Schema, 8)#> +<# } else { #> +<# if (this.dtCommand.Request.Description.Any()) { #> + "description": "<#=this.dtCommand.Request.Description.First().Value#>", +<# } #> + "type": "object", +<# if (!this.dtCommand.Request.Nullable) { #> + "required": [ "<#=this.dtCommand.Request.Name#>" ], +<# } #> + "properties": { + "<#=this.dtCommand.Request.Name#>": { +<# if (!this.dtCommand.Request.Schema.Id.AbsoluteUri.StartsWith("dtmi:dtdl:instance:Schema:") && this.dtCommand.Request.Schema.Description.Any()) { #> + "description": "<#=this.dtCommand.Request.Schema.Description.First().Value#>", +<# } #> +<#=this.thingDescriber.GetTypeAndAddenda(this.dtCommand.Request.Schema, 12)#> + } + } +<# } #> + }, +<# } else { #> + "input": { + "type": "null" + }, +<# } #> +<# } #> +<# if (this.dtCommand.Response != null) { #> +<# if (this.usesTypes) { #> + "output": { +<# if (this.isResponseTransparent) { #> +<# if (this.dtCommand.Response.Description.Any()) { #> + "description": "<#=this.dtCommand.Response.Description.First().Value#>", +<# } else if (this.dtCommand.Response.Schema.Description.Any()) { #> + "description": "<#=this.dtCommand.Response.Schema.Description.First().Value#>", +<# } #> +<#=this.thingDescriber.GetTypeAndAddenda(this.dtCommand.Response.Schema, 8)#> +<# } else { #> +<# if (this.dtCommand.Response.Description.Any()) { #> + "description": "<#=this.dtCommand.Response.Description.First().Value#>", +<# } #> + "type": "object", +<# if (!this.dtCommand.Response.Nullable) { #> + "required": [ "<#=this.responseName#>" ], +<# } #> + "properties": { + "<#=this.responseName#>": { +<# if (this.responseDescription?.Any() ?? false) { #> + "description": "<#=this.responseDescription.First().Value#>", +<# } else if (!this.responseSchema.Id.AbsoluteUri.StartsWith("dtmi:dtdl:instance:Schema:") && this.responseSchema.Description.Any()) { #> + "description": "<#=this.responseSchema.Description.First().Value#>", +<# } #> +<#=this.thingDescriber.GetTypeAndAddenda(this.responseSchema, 12)#> + } + } +<# } #> + }, +<# } else { #> + "output": { + "type": "null" + }, +<# } #> +<# } #> +<# if (this.isCommandIdempotent) { #> + "idempotent": true, +<# } #> +<# if (this.isCommandCacheable) { #> + "safe": true, +<# } #> + "forms": [ + { + "contentType": "<#=this.contentType#>", +<# if (this.isResponseSchemaResult) { #> +<# if (this.errorSchemaName != null) { #> + "additionalResponses": [ + { + "success": false, + "schema": "<#=this.errorSchemaName#>" + } + ], +<# } #> +<# if (this.infoSchemaName != null) { #> + "dtv:headerInfo": [ + { + "contentType": "application/json", + "schema": "<#=this.infoSchemaName#>" + } + ], +<# } #> +<# if (this.codeEnumeration.Count > 0) { #> + "dtv:headerCode": [ +<# int ix = 1; foreach (KeyValuePair kvp in this.codeEnumeration) { #> + "<#=kvp.Key#>"<#=ix < this.codeEnumeration.Count ? "," : ""#> +<# ix++; } #> + ], +<# } #> +<# } #> +<# if (this.serviceGroupId != null) { #> + "dtv:serviceGroupId": "<#=this.serviceGroupId#>", +<# } #> + "dtv:topic": "<#=this.commandTopic ?? ""#>", + "op": "invokeaction" + } + ] + } \ No newline at end of file diff --git a/codegen2/src/Dtdl2Wot/Dtdl2Wot.csproj b/codegen2/src/Dtdl2Wot/Dtdl2Wot.csproj new file mode 100644 index 0000000000..8fc14c3bd1 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Dtdl2Wot.csproj @@ -0,0 +1,120 @@ + + + + Exe + net9.0 + enable + + + + + + + + + + + + + + True + True + ArrayThingSchema.tt + + + True + True + CommandAffordance.tt + + + True + True + EnumThingSchema.tt + + + True + True + InterfaceThing.tt + + + True + True + MapThingSchema.tt + + + True + True + PlaceholderThingSchema.tt + + + True + True + ObjectThingSchema.tt + + + True + True + PropertyAffordance.tt + + + True + True + TelemetryAffordance.tt + + + + + + TextTemplatingFilePreprocessor + ArrayThingSchema.cs + Dtdl2Wot + + + TextTemplatingFilePreprocessor + CommandAffordance.cs + Dtdl2Wot + + + TextTemplatingFilePreprocessor + EnumThingSchema.cs + Dtdl2Wot + + + TextTemplatingFilePreprocessor + InterfaceThing.cs + Dtdl2Wot + + + TextTemplatingFilePreprocessor + MapThingSchema.cs + Dtdl2Wot + + + TextTemplatingFilePreprocessor + PlaceholderThingSchema.cs + Dtdl2Wot + + + TextTemplatingFilePreprocessor + ObjectThingSchema.cs + Dtdl2Wot + + + TextTemplatingFilePreprocessor + PropertyAffordance.cs + Dtdl2Wot + + + TextTemplatingFilePreprocessor + TelemetryAffordance.cs + Dtdl2Wot + + + + + + Resources\conversion\%(Filename)%(Extension) + + + + diff --git a/codegen2/src/Dtdl2Wot/DtdlMqttExtensionValues.cs b/codegen2/src/Dtdl2Wot/DtdlMqttExtensionValues.cs new file mode 100644 index 0000000000..9d12fb76d6 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/DtdlMqttExtensionValues.cs @@ -0,0 +1,68 @@ +namespace Dtdl2Wot +{ + using System.Text.RegularExpressions; + using DTDLParser; + + public static class DtdlMqttExtensionValues + { + public static string MqttAdjunctTypePattern = @"dtmi:dtdl:extension:mqtt:v(\d+):Mqtt"; + + public static Regex MqttAdjunctTypeRegex = new Regex(MqttAdjunctTypePattern, RegexOptions.Compiled); + + public static string RequiredAdjunctTypePattern = @"dtmi:dtdl:extension:requirement:v(\d+):Required"; + + public static Regex RequiredAdjunctTypeRegex = new Regex(RequiredAdjunctTypePattern, RegexOptions.Compiled); + + public static readonly string IdempotentAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:Idempotent"; + + public static readonly string CacheableAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:Cacheable"; + + public static readonly string FragmentedAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:Fragmented"; + + public static readonly string IndirectAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:Indirect"; + + public static readonly string TransparentAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:Transparent"; + + public static readonly string IndexedAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:Indexed"; + + public static readonly string ErrorAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:Error"; + + public static readonly string ErrorMessageAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:ErrorMessage"; + + public static readonly string ResultAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:Result"; + + public static readonly string NormalResultAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:NormalResult"; + + public static readonly string ErrorResultAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:ErrorResult"; + + public static readonly string PropertyResultAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:PropertyResult"; + + public static readonly string PropertyValueAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:PropertyValue"; + + public static readonly string ReadErrorAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:ReadError"; + + public static readonly string WriteErrorAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:WriteError"; + + public static readonly string ErrorCodeAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:ErrorCode"; + + public static readonly string ErrorInfoAdjunctTypeFormat = "dtmi:dtdl:extension:mqtt:v{0}:ErrorInfo"; + + public static readonly string TelemTopicPropertyFormat = "dtmi:dtdl:extension:mqtt:v{0}:Mqtt:telemetryTopic"; + + public static readonly string PropTopicPropertyFormat = "dtmi:dtdl:extension:mqtt:v{0}:Mqtt:propertyTopic"; + + public static readonly string CmdReqTopicPropertyFormat = "dtmi:dtdl:extension:mqtt:v{0}:Mqtt:commandTopic"; + + public static readonly string TelemServiceGroupIdPropertyFormat = "dtmi:dtdl:extension:mqtt:v{0}:Mqtt:telemServiceGroupId"; + + public static readonly string CmdServiceGroupIdPropertyFormat = "dtmi:dtdl:extension:mqtt:v{0}:Mqtt:cmdServiceGroupId"; + + public static readonly string IndexPropertyFormat = "dtmi:dtdl:extension:mqtt:v{0}:Indexed:index"; + + public static readonly string PayloadFormatPropertyFormat = "dtmi:dtdl:extension:mqtt:v{0}:Mqtt:payloadFormat"; + + public static readonly string TtlPropertyFormat = "dtmi:dtdl:extension:mqtt:v{0}:Cacheable:ttl"; + + public static string GetStandardTerm(string dtmi) => dtmi.Substring(dtmi.LastIndexOf(':') + 1); + } +} diff --git a/codegen2/src/Dtdl2Wot/DtdlMqttTopicTokens.cs b/codegen2/src/Dtdl2Wot/DtdlMqttTopicTokens.cs new file mode 100644 index 0000000000..2e378a4959 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/DtdlMqttTopicTokens.cs @@ -0,0 +1,33 @@ +namespace Dtdl2Wot +{ + /// + /// Static class that defines string values of the replaceable components used in topic patterns. + /// + public static class DtdlMqttTopicTokens + { + /// + /// Token representing the DTMI of a DTDL model. + /// + public const string ModelId = "{modelId}"; + + /// + /// Token representing the name of a Command. + /// + public const string CommandName = "{commandName}"; + + /// + /// Token representing the name of a Telemetry. + /// + public const string TelemetryName = "{telemetryName}"; + + /// + /// Token representing the name of a Property. + /// + public const string PropertyName = "{propertyName}"; + + /// + /// Token representing a Property action, 'read' or 'write'. + /// + public const string PropertyAction = "{action}"; + } +} diff --git a/codegen2/src/Dtdl2Wot/Enum/code/EnumThingSchema.cs b/codegen2/src/Dtdl2Wot/Enum/code/EnumThingSchema.cs new file mode 100644 index 0000000000..83754d0d38 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Enum/code/EnumThingSchema.cs @@ -0,0 +1,44 @@ +namespace Dtdl2Wot +{ + using System; + using System.Collections.Generic; + using System.Linq; + using DTDLParser.Models; + + public partial class EnumThingSchema : ITemplateTransform + { + private readonly string enumTitle; + private readonly DTEnumInfo dtEnum; + private readonly int indent; + private readonly string valueSchema; + + public EnumThingSchema(DTEnumInfo dtEnum, int indent) + { + this.enumTitle = new CodeName(dtEnum.Id).AsGiven; + this.dtEnum = dtEnum; + this.indent = indent; + this.valueSchema = ThingDescriber.GetPrimitiveType(dtEnum.ValueSchema.Id); + } + + public string FileName { get => string.Empty; } + + public string FolderPath { get => string.Empty; } + + public static bool CanExpressAsEnum(DTEnumInfo dtEnum) + { + switch (ThingDescriber.GetPrimitiveType(dtEnum.ValueSchema.Id)) + { + case "integer": + return dtEnum.EnumValues.All(ev => (int)ev.EnumValue >= 0) // all enum values are non-negative + && dtEnum.EnumValues.Min(ev => (int)ev.EnumValue) <= 1 // minimum enum value is 0 or 1 + && dtEnum.EnumValues.Max(ev => (int)ev.EnumValue) <= dtEnum.EnumValues.Count // maximum enum value does not exceed number of enum values + && new HashSet(dtEnum.EnumValues.Select(ev => (int)ev.EnumValue)).Count == dtEnum.EnumValues.Count // all enum values are unique + && !dtEnum.EnumValues.Any(ev => ev.Name.Contains(ev.EnumValue.ToString()!)); // no enum value name contains its value + case "string": + return dtEnum.EnumValues.All(ev => ev.Name == (string)ev.EnumValue); // all enum values have matching names and values + default: + throw new InvalidOperationException($"Invalid enum value schema {dtEnum.ValueSchema.Id}"); + } + } + } +} diff --git a/codegen2/src/Dtdl2Wot/Enum/t4/EnumThingSchema.cs b/codegen2/src/Dtdl2Wot/Enum/t4/EnumThingSchema.cs new file mode 100644 index 0000000000..03141a4a59 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Enum/t4/EnumThingSchema.cs @@ -0,0 +1,321 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser.Models; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class EnumThingSchema : EnumThingSchemaBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.PushIndent(new string(' ', this.indent)); + if (CanExpressAsEnum(this.dtEnum)) { + this.Write("\"title\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.enumTitle)); + this.Write("\",\r\n\"type\": \"string\",\r\n\"enum\": [\r\n"); + int ix1 = 1; foreach (var enumValue in this.dtEnum.EnumValues.OrderBy(ev => ev.EnumValue)) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(enumValue.Name)); + this.Write("\""); + this.Write(this.ToStringHelper.ToStringWithCulture(ix1 < this.dtEnum.EnumValues.Count ? "," : "")); + this.Write("\r\n"); + ix1++; } + this.Write("]"); + } else if (this.valueSchema == "integer") { + this.Write("\"type\": \"integer\",\r\n\"minimum\": -2147483648,\r\n\"maximum\": 2147483647"); + } else { + this.Write("\"type\": \"string\""); + } this.PopIndent(); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class EnumThingSchemaBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Dtdl2Wot/Enum/t4/EnumThingSchema.tt b/codegen2/src/Dtdl2Wot/Enum/t4/EnumThingSchema.tt new file mode 100644 index 0000000000..6220550fee --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Enum/t4/EnumThingSchema.tt @@ -0,0 +1,17 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="DTDLParser.Models" #> +<# this.PushIndent(new string(' ', this.indent)); #> +<# if (CanExpressAsEnum(this.dtEnum)) { #> +"title": "<#=this.enumTitle#>", +"type": "string", +"enum": [ +<# int ix1 = 1; foreach (var enumValue in this.dtEnum.EnumValues.OrderBy(ev => ev.EnumValue)) { #> + "<#=enumValue.Name#>"<#=ix1 < this.dtEnum.EnumValues.Count ? "," : ""#> +<# ix1++; } #> +]<# } else if (this.valueSchema == "integer") { #> +"type": "integer", +"minimum": -2147483648, +"maximum": 2147483647<# } else { #> +"type": "string"<# } this.PopIndent(); #> \ No newline at end of file diff --git a/codegen2/src/Dtdl2Wot/ITemplateTransform.cs b/codegen2/src/Dtdl2Wot/ITemplateTransform.cs new file mode 100644 index 0000000000..38cb277624 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/ITemplateTransform.cs @@ -0,0 +1,11 @@ +namespace Dtdl2Wot +{ + public interface ITemplateTransform + { + string FileName { get; } + + string FolderPath { get; } + + string TransformText(); + } +} diff --git a/codegen2/src/Dtdl2Wot/Interface/code/InterfaceThing.cs b/codegen2/src/Dtdl2Wot/Interface/code/InterfaceThing.cs new file mode 100644 index 0000000000..7114c705c7 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Interface/code/InterfaceThing.cs @@ -0,0 +1,88 @@ +namespace Dtdl2Wot +{ + using System; + using System.Collections.Generic; + using System.Linq; + using DTDLParser; + using DTDLParser.Models; + + public partial class InterfaceThing : ITemplateTransform + { + private readonly DTInterfaceInfo dtInterface; + private readonly CodeName serviceName; + private readonly int mqttVersion; + private string schemaNamesPath; + private readonly string? telemetryTopic; + private readonly string? commandTopic; + private readonly string? propertyTopic; + private readonly string? telemServiceGroupId; + private readonly string? cmdServiceGroupId; + private readonly bool aggregateTelemetries; + private readonly bool aggregateProperties; + private readonly bool usesTypes; + private readonly string contentType; + private readonly Dictionary errorSchemas; + private readonly Dictionary namespacedEnums; + private readonly ThingDescriber thingDescriber; + + public InterfaceThing(IReadOnlyDictionary modelDict, Dtmi interfaceId, int mqttVersion, string schemaNamesPath) + { + this.dtInterface = (DTInterfaceInfo)modelDict[interfaceId]; + this.serviceName = new CodeName(dtInterface.Id); + this.mqttVersion = mqttVersion; + this.schemaNamesPath = schemaNamesPath; + + this.telemetryTopic = dtInterface.SupplementalProperties.TryGetValue(string.Format(DtdlMqttExtensionValues.TelemTopicPropertyFormat, mqttVersion), out object? telemTopicObj) ? ((string)telemTopicObj).Replace(DtdlMqttTopicTokens.ModelId, interfaceId.AbsoluteUri) : null; + this.commandTopic = dtInterface.SupplementalProperties.TryGetValue(string.Format(DtdlMqttExtensionValues.CmdReqTopicPropertyFormat, mqttVersion), out object? cmdTopicObj) ? ((string)cmdTopicObj).Replace(DtdlMqttTopicTokens.ModelId, interfaceId.AbsoluteUri) : null; + this.propertyTopic = dtInterface.SupplementalProperties.TryGetValue(string.Format(DtdlMqttExtensionValues.PropTopicPropertyFormat, mqttVersion), out object? propTopicObj) ? ((string)propTopicObj).Replace(DtdlMqttTopicTokens.ModelId, interfaceId.AbsoluteUri) : null; + + this.telemServiceGroupId = dtInterface.SupplementalProperties.TryGetValue(string.Format(DtdlMqttExtensionValues.TelemServiceGroupIdPropertyFormat, mqttVersion), out object? telemServiceGroupIdObj) ? (string)telemServiceGroupIdObj : null; + this.cmdServiceGroupId = dtInterface.SupplementalProperties.TryGetValue(string.Format(DtdlMqttExtensionValues.CmdServiceGroupIdPropertyFormat, mqttVersion), out object? cmdServiceGroupIdObj) ? (string)cmdServiceGroupIdObj : null; + + this.aggregateTelemetries = this.telemetryTopic != null && !this.telemetryTopic.Contains(DtdlMqttTopicTokens.TelemetryName); + this.aggregateProperties = this.propertyTopic != null && !this.propertyTopic.Contains(DtdlMqttTopicTokens.PropertyName); + + string payloadFormat = (string)dtInterface.SupplementalProperties[string.Format(DtdlMqttExtensionValues.PayloadFormatPropertyFormat, mqttVersion)]; + this.usesTypes = payloadFormat != PayloadFormat.Raw && payloadFormat != PayloadFormat.Custom; + this.contentType = payloadFormat switch + { + PayloadFormat.Avro => "application/avro", + PayloadFormat.Json => "application/json", + PayloadFormat.Raw => "application/octet-stream", + PayloadFormat.Custom => "", + _ => throw new InvalidOperationException($"InvalidOperationException \"{payloadFormat}\" not recognized"), + }; + Dtmi errorResultType = new Dtmi(string.Format(DtdlMqttExtensionValues.ErrorResultAdjunctTypeFormat, mqttVersion)); + Dtmi readErrorType = new Dtmi(string.Format(DtdlMqttExtensionValues.ReadErrorAdjunctTypeFormat, mqttVersion)); + Dtmi writeErrorType = new Dtmi(string.Format(DtdlMqttExtensionValues.WriteErrorAdjunctTypeFormat, mqttVersion)); + Dtmi errorInfoType = new Dtmi(string.Format(DtdlMqttExtensionValues.ErrorInfoAdjunctTypeFormat, mqttVersion)); + + IEnumerable errorFields = modelDict.Values.Where(e => e.EntityKind == DTEntityKind.Field).Select(e => (DTFieldInfo)e).Where(f => f.SupplementalTypes.Contains(errorResultType) || f.SupplementalTypes.Contains(readErrorType) || f.SupplementalTypes.Contains(writeErrorType) || f.SupplementalTypes.Contains(errorInfoType)); + + this.errorSchemas = new Dictionary(); + foreach (DTFieldInfo errorField in errorFields) + { + this.errorSchemas[new CodeName(errorField.Schema.Id).AsGiven] = errorField.Schema; + } + + this.namespacedEnums = new(); + foreach (DTEntityInfo dTEntity in modelDict.Values) + { + if (dTEntity.EntityKind == DTEntityKind.Enum) + { + DTEnumInfo dtEnum = (DTEnumInfo)dTEntity; + if (!EnumThingSchema.CanExpressAsEnum(dtEnum)) + { + this.namespacedEnums[new CodeName(dtEnum.Id).AsGiven] = dtEnum; + } + } + } + + this.thingDescriber = new ThingDescriber(mqttVersion); + } + + public string FileName { get => $"{this.serviceName.AsGiven}.TM.json"; } + + public string FolderPath { get => string.Empty; } + } +} diff --git a/codegen2/src/Dtdl2Wot/Interface/t4/InterfaceThing.cs b/codegen2/src/Dtdl2Wot/Interface/t4/InterfaceThing.cs new file mode 100644 index 0000000000..cb05e81071 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Interface/t4/InterfaceThing.cs @@ -0,0 +1,430 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser; + using DTDLParser.Models; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class InterfaceThing : InterfaceThingBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("{\r\n \"@context\": [\r\n \"https://www.w3.org/2022/wot/td/v1.1\",\r\n { \"dtv\": \"htt" + + "p://azure.com/DigitalTwins/dtmi#\" }\r\n ],\r\n \"@type\": \"tm:ThingModel\",\r\n \"title" + + "\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceName.AsGiven)); + this.Write("\",\r\n \"links\": [\r\n {\r\n \"rel\": \"dtv:naming\",\r\n \"href\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.schemaNamesPath)); + this.Write("\",\r\n \"type\": \"application/json\"\r\n }\r\n ],\r\n"); + if (this.errorSchemas.Any() || this.namespacedEnums.Any()) { + this.Write(" \"schemaDefinitions\": {\r\n"); + int ix1 = 1; foreach (KeyValuePair errorSchema in this.errorSchemas) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(errorSchema.Key)); + this.Write("\": {\r\n"); + if (errorSchema.Value.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(errorSchema.Value.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(errorSchema.Value, 6))); + this.Write("\r\n }"); + this.Write(this.ToStringHelper.ToStringWithCulture(ix1 < this.errorSchemas.Count + this.namespacedEnums.Count ? "," : "")); + this.Write("\r\n"); + ix1++; } + foreach (KeyValuePair namespacedEnum in this.namespacedEnums) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(namespacedEnum.Key)); + this.Write("\": {\r\n \"type\": \"object\",\r\n \"const\": {\r\n"); + int ix2 = 1; foreach (DTEnumValueInfo enumValue in namespacedEnum.Value.EnumValues) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(enumValue.Name)); + this.Write("\": "); + this.Write(this.ToStringHelper.ToStringWithCulture((ThingDescriber.GetPrimitiveType(namespacedEnum.Value.ValueSchema.Id) == "integer") ? enumValue.EnumValue : $"\"{enumValue.EnumValue}\"")); + this.Write(this.ToStringHelper.ToStringWithCulture(ix2 < namespacedEnum.Value.EnumValues.Count ? "," : "")); + this.Write("\r\n"); + ix2++; } + this.Write(" },\r\n"); + if (namespacedEnum.Value.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(namespacedEnum.Value.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(" \"properties\": {\r\n"); + ix2 = 1; foreach (DTEnumValueInfo enumValue in namespacedEnum.Value.EnumValues) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(enumValue.Name)); + this.Write("\": {\r\n"); + if (enumValue.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(enumValue.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(" \"type\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(ThingDescriber.GetPrimitiveType(namespacedEnum.Value.ValueSchema.Id))); + this.Write("\"\r\n }"); + this.Write(this.ToStringHelper.ToStringWithCulture(ix2 < namespacedEnum.Value.EnumValues.Count ? "," : "")); + this.Write("\r\n"); + ix2++; } + this.Write(" }\r\n }"); + this.Write(this.ToStringHelper.ToStringWithCulture(ix1 < this.errorSchemas.Count + this.namespacedEnums.Count ? "," : "")); + this.Write("\r\n"); + ix1++; } + this.Write(" },\r\n"); + } + if (this.aggregateTelemetries || this.aggregateProperties) { + this.Write(" \"forms\": [\r\n"); + if (this.aggregateTelemetries) { + this.Write(" {\r\n \"contentType\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\",\r\n \"dtv:topic\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.telemetryTopic)); + this.Write("\",\r\n \"op\": \"subscribeallevents\"\r\n }"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.aggregateProperties ? "," : "")); + this.Write("\r\n"); + } + if (this.aggregateProperties) { + if (this.dtInterface.Properties.Any(p => p.Value.Writable)) { + this.Write(" {\r\n \"contentType\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\",\r\n"); + if (this.dtInterface.Properties.Values.Any(p => p.Writable && IsSchemaPropertyResult(p.Schema) && ((DTObjectInfo)p.Schema).Fields.Any(f => IsFieldWriteError(f)))) { + this.Write(" \"additionalResponses\": [\r\n {\r\n \"success\": false\r\n }\r" + + "\n ],\r\n"); + } + this.Write(" \"dtv:topic\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyTopic.Replace(DtdlMqttTopicTokens.PropertyAction, "write"))); + this.Write("\",\r\n \"op\": \"writemultipleproperties\"\r\n },\r\n"); + } + this.Write(" {\r\n \"contentType\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\",\r\n"); + if (this.dtInterface.Properties.Values.Any(p => IsSchemaPropertyResult(p.Schema) && ((DTObjectInfo)p.Schema).Fields.Any(f => IsFieldReadError(f)))) { + this.Write(" \"additionalResponses\": [\r\n {\r\n \"success\": false\r\n }\r" + + "\n ],\r\n"); + } + this.Write(" \"dtv:topic\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyTopic.Replace(DtdlMqttTopicTokens.PropertyAction, "read"))); + this.Write("\",\r\n \"op\": \"readallproperties\"\r\n }\r\n"); + } + this.Write(" ],\r\n"); + } + this.Write(" \"actions\": {\r\n"); + int ix = 1; foreach (KeyValuePair dtCommand in this.dtInterface.Commands) { + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetCommandAffordance(dtCommand.Value, this.usesTypes, this.contentType, this.commandTopic, this.cmdServiceGroupId))); + this.Write(this.ToStringHelper.ToStringWithCulture(ix < this.dtInterface.Commands.Count ? "," : "")); + this.Write("\r\n"); + ix++; } + this.Write(" },\r\n \"properties\": {\r\n"); + ix = 1; foreach (KeyValuePair dtProperty in this.dtInterface.Properties) { + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetPropertyAffordance(dtProperty.Value, this.usesTypes, this.contentType, this.propertyTopic))); + this.Write(this.ToStringHelper.ToStringWithCulture(ix < this.dtInterface.Properties.Count ? "," : "")); + this.Write("\r\n"); + ix++; } + this.Write(" },\r\n \"events\": {\r\n"); + ix = 1; foreach (KeyValuePair dtTelemetry in this.dtInterface.Telemetries) { + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTelemetryAffordance(dtTelemetry.Value, this.usesTypes, this.contentType, this.telemetryTopic, this.telemServiceGroupId))); + this.Write(this.ToStringHelper.ToStringWithCulture(ix < this.dtInterface.Telemetries.Count ? "," : "")); + this.Write("\r\n"); + ix++; } + this.Write(" }\r\n}\r\n"); + return this.GenerationEnvironment.ToString(); + } + +private bool IsSchemaPropertyResult(DTSchemaInfo dtSchema) => dtSchema != null && dtSchema.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.PropertyResultAdjunctTypeFormat, this.mqttVersion))); + +private bool IsFieldReadError(DTFieldInfo dtField) => dtField.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.ReadErrorAdjunctTypeFormat, this.mqttVersion))); + +private bool IsFieldWriteError(DTFieldInfo dtField) => dtField.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.WriteErrorAdjunctTypeFormat, this.mqttVersion))); + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class InterfaceThingBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Dtdl2Wot/Interface/t4/InterfaceThing.tt b/codegen2/src/Dtdl2Wot/Interface/t4/InterfaceThing.tt new file mode 100644 index 0000000000..9caa4d8780 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Interface/t4/InterfaceThing.tt @@ -0,0 +1,116 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="DTDLParser" #> +<#@ import namespace="DTDLParser.Models" #> +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "<#=this.serviceName.AsGiven#>", + "links": [ + { + "rel": "dtv:naming", + "href": "<#=this.schemaNamesPath#>", + "type": "application/json" + } + ], +<# if (this.errorSchemas.Any() || this.namespacedEnums.Any()) { #> + "schemaDefinitions": { +<# int ix1 = 1; foreach (KeyValuePair errorSchema in this.errorSchemas) { #> + "<#=errorSchema.Key#>": { +<# if (errorSchema.Value.Description.Any()) { #> + "description": "<#=errorSchema.Value.Description.First().Value#>", +<# } #> +<#=this.thingDescriber.GetTypeAndAddenda(errorSchema.Value, 6)#> + }<#=ix1 < this.errorSchemas.Count + this.namespacedEnums.Count ? "," : ""#> +<# ix1++; } #> +<# foreach (KeyValuePair namespacedEnum in this.namespacedEnums) { #> + "<#=namespacedEnum.Key#>": { + "type": "object", + "const": { +<# int ix2 = 1; foreach (DTEnumValueInfo enumValue in namespacedEnum.Value.EnumValues) { #> + "<#=enumValue.Name#>": <#=(ThingDescriber.GetPrimitiveType(namespacedEnum.Value.ValueSchema.Id) == "integer") ? enumValue.EnumValue : $"\"{enumValue.EnumValue}\""#><#=ix2 < namespacedEnum.Value.EnumValues.Count ? "," : ""#> +<# ix2++; } #> + }, +<# if (namespacedEnum.Value.Description.Any()) { #> + "description": "<#=namespacedEnum.Value.Description.First().Value#>", +<# } #> + "properties": { +<# ix2 = 1; foreach (DTEnumValueInfo enumValue in namespacedEnum.Value.EnumValues) { #> + "<#=enumValue.Name#>": { +<# if (enumValue.Description.Any()) { #> + "description": "<#=enumValue.Description.First().Value#>", +<# } #> + "type": "<#=ThingDescriber.GetPrimitiveType(namespacedEnum.Value.ValueSchema.Id)#>" + }<#=ix2 < namespacedEnum.Value.EnumValues.Count ? "," : ""#> +<# ix2++; } #> + } + }<#=ix1 < this.errorSchemas.Count + this.namespacedEnums.Count ? "," : ""#> +<# ix1++; } #> + }, +<# } #> +<# if (this.aggregateTelemetries || this.aggregateProperties) { #> + "forms": [ +<# if (this.aggregateTelemetries) { #> + { + "contentType": "<#=this.contentType#>", + "dtv:topic": "<#=this.telemetryTopic#>", + "op": "subscribeallevents" + }<#=this.aggregateProperties ? "," : ""#> +<# } #> +<# if (this.aggregateProperties) { #> +<# if (this.dtInterface.Properties.Any(p => p.Value.Writable)) { #> + { + "contentType": "<#=this.contentType#>", +<# if (this.dtInterface.Properties.Values.Any(p => p.Writable && IsSchemaPropertyResult(p.Schema) && ((DTObjectInfo)p.Schema).Fields.Any(f => IsFieldWriteError(f)))) { #> + "additionalResponses": [ + { + "success": false + } + ], +<# } #> + "dtv:topic": "<#=this.propertyTopic.Replace(DtdlMqttTopicTokens.PropertyAction, "write")#>", + "op": "writemultipleproperties" + }, +<# } #> + { + "contentType": "<#=this.contentType#>", +<# if (this.dtInterface.Properties.Values.Any(p => IsSchemaPropertyResult(p.Schema) && ((DTObjectInfo)p.Schema).Fields.Any(f => IsFieldReadError(f)))) { #> + "additionalResponses": [ + { + "success": false + } + ], +<# } #> + "dtv:topic": "<#=this.propertyTopic.Replace(DtdlMqttTopicTokens.PropertyAction, "read")#>", + "op": "readallproperties" + } +<# } #> + ], +<# } #> + "actions": { +<# int ix = 1; foreach (KeyValuePair dtCommand in this.dtInterface.Commands) { #> +<#=this.thingDescriber.GetCommandAffordance(dtCommand.Value, this.usesTypes, this.contentType, this.commandTopic, this.cmdServiceGroupId)#><#=ix < this.dtInterface.Commands.Count ? "," : ""#> +<# ix++; } #> + }, + "properties": { +<# ix = 1; foreach (KeyValuePair dtProperty in this.dtInterface.Properties) { #> +<#=this.thingDescriber.GetPropertyAffordance(dtProperty.Value, this.usesTypes, this.contentType, this.propertyTopic)#><#=ix < this.dtInterface.Properties.Count ? "," : ""#> +<# ix++; } #> + }, + "events": { +<# ix = 1; foreach (KeyValuePair dtTelemetry in this.dtInterface.Telemetries) { #> +<#=this.thingDescriber.GetTelemetryAffordance(dtTelemetry.Value, this.usesTypes, this.contentType, this.telemetryTopic, this.telemServiceGroupId)#><#=ix < this.dtInterface.Telemetries.Count ? "," : ""#> +<# ix++; } #> + } +} +<#+ +private bool IsSchemaPropertyResult(DTSchemaInfo dtSchema) => dtSchema != null && dtSchema.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.PropertyResultAdjunctTypeFormat, this.mqttVersion))); + +private bool IsFieldReadError(DTFieldInfo dtField) => dtField.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.ReadErrorAdjunctTypeFormat, this.mqttVersion))); + +private bool IsFieldWriteError(DTFieldInfo dtField) => dtField.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.WriteErrorAdjunctTypeFormat, this.mqttVersion))); +#> diff --git a/codegen2/src/Dtdl2Wot/Map/code/MapThingSchema.cs b/codegen2/src/Dtdl2Wot/Map/code/MapThingSchema.cs new file mode 100644 index 0000000000..be965e4c5d --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Map/code/MapThingSchema.cs @@ -0,0 +1,22 @@ +namespace Dtdl2Wot +{ + using DTDLParser.Models; + + public partial class MapThingSchema : ITemplateTransform + { + private readonly DTMapInfo dtMap; + private readonly int indent; + private readonly ThingDescriber thingDescriber; + + public MapThingSchema(DTMapInfo dtMap, int indent, ThingDescriber thingDescriber) + { + this.dtMap = dtMap; + this.indent = indent; + this.thingDescriber = thingDescriber; + } + + public string FileName { get => string.Empty; } + + public string FolderPath { get => string.Empty; } + } +} diff --git a/codegen2/src/Dtdl2Wot/Map/code/PlaceholderThingSchema.cs b/codegen2/src/Dtdl2Wot/Map/code/PlaceholderThingSchema.cs new file mode 100644 index 0000000000..62cac3b2d4 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Map/code/PlaceholderThingSchema.cs @@ -0,0 +1,22 @@ +namespace Dtdl2Wot +{ + using DTDLParser.Models; + + public partial class PlaceholderThingSchema : ITemplateTransform + { + private readonly DTMapInfo dtMap; + private readonly int indent; + private readonly ThingDescriber thingDescriber; + + public PlaceholderThingSchema(DTMapInfo dtMap, int indent, ThingDescriber thingDescriber) + { + this.dtMap = dtMap; + this.indent = indent; + this.thingDescriber = thingDescriber; + } + + public string FileName { get => string.Empty; } + + public string FolderPath { get => string.Empty; } + } +} diff --git a/codegen2/src/Dtdl2Wot/Map/t4/MapThingSchema.cs b/codegen2/src/Dtdl2Wot/Map/t4/MapThingSchema.cs new file mode 100644 index 0000000000..4160eedebd --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Map/t4/MapThingSchema.cs @@ -0,0 +1,313 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser.Models; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class MapThingSchema : MapThingSchemaBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.PushIndent(new string(' ', this.indent)); + this.Write("\"type\": \"object\",\r\n\"dtv:additionalProperties\": {\r\n"); + if (this.dtMap.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtMap.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(this.dtMap.MapValue.Schema, 2))); + this.Write("\r\n}"); + this.PopIndent(); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class MapThingSchemaBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Dtdl2Wot/Map/t4/MapThingSchema.tt b/codegen2/src/Dtdl2Wot/Map/t4/MapThingSchema.tt new file mode 100644 index 0000000000..4e9d2c4e01 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Map/t4/MapThingSchema.tt @@ -0,0 +1,12 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="DTDLParser.Models" #> +<# this.PushIndent(new string(' ', this.indent)); #> +"type": "object", +"dtv:additionalProperties": { +<# if (this.dtMap.Description.Any()) { #> + "description": "<#=this.dtMap.Description.First().Value#>", +<# } #> +<#=this.thingDescriber.GetTypeAndAddenda(this.dtMap.MapValue.Schema, 2)#> +}<# this.PopIndent(); #> diff --git a/codegen2/src/Dtdl2Wot/Map/t4/PlaceholderThingSchema.cs b/codegen2/src/Dtdl2Wot/Map/t4/PlaceholderThingSchema.cs new file mode 100644 index 0000000000..8a9d370342 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Map/t4/PlaceholderThingSchema.cs @@ -0,0 +1,307 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser.Models; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class PlaceholderThingSchema : PlaceholderThingSchemaBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.PushIndent(new string(' ', this.indent)); + this.Write("\"dtv:placeholder\": true,\r\n"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(this.dtMap.MapValue.Schema, 0))); + this.PopIndent(); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class PlaceholderThingSchemaBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Dtdl2Wot/Map/t4/PlaceholderThingSchema.tt b/codegen2/src/Dtdl2Wot/Map/t4/PlaceholderThingSchema.tt new file mode 100644 index 0000000000..8c5834f95b --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Map/t4/PlaceholderThingSchema.tt @@ -0,0 +1,7 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="DTDLParser.Models" #> +<# this.PushIndent(new string(' ', this.indent)); #> +"dtv:placeholder": true, +<#=this.thingDescriber.GetTypeAndAddenda(this.dtMap.MapValue.Schema, 0)#><# this.PopIndent(); #> diff --git a/codegen2/src/Dtdl2Wot/Object/code/ObjectThingSchema.cs b/codegen2/src/Dtdl2Wot/Object/code/ObjectThingSchema.cs new file mode 100644 index 0000000000..7a35cb565f --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Object/code/ObjectThingSchema.cs @@ -0,0 +1,36 @@ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser; + using DTDLParser.Models; + + public partial class ObjectThingSchema : ITemplateTransform + { + private readonly string objectTitle; + private readonly IReadOnlyDictionary objectDescription; + private readonly List objectFields; + private readonly int indent; + private readonly int mqttVersion; + private readonly ThingDescriber thingDescriber; + private readonly Dtmi errorMessageAdjunctTypeId; + + public ObjectThingSchema(DTObjectInfo dtObject, int indent, int mqttVersion, ThingDescriber thingDescriber) + { + Dtmi errorCodeAdjunctTypeId = new Dtmi(string.Format(DtdlMqttExtensionValues.ErrorCodeAdjunctTypeFormat, mqttVersion)); + Dtmi errorInfoAdjunctTypeId = new Dtmi(string.Format(DtdlMqttExtensionValues.ErrorInfoAdjunctTypeFormat, mqttVersion)); + + this.objectTitle = new CodeName(dtObject.Id).AsGiven; + this.objectDescription = dtObject.Description; + this.objectFields = dtObject.Fields.Where(f => !f.SupplementalTypes.Contains(errorCodeAdjunctTypeId) && !f.SupplementalTypes.Contains(errorInfoAdjunctTypeId)).ToList(); + this.indent = indent; + this.mqttVersion = mqttVersion; + this.thingDescriber = thingDescriber; + this.errorMessageAdjunctTypeId = new Dtmi(string.Format(DtdlMqttExtensionValues.ErrorMessageAdjunctTypeFormat, mqttVersion)); + } + + public string FileName { get => string.Empty; } + + public string FolderPath { get => string.Empty; } + } +} diff --git a/codegen2/src/Dtdl2Wot/Object/t4/ObjectThingSchema.cs b/codegen2/src/Dtdl2Wot/Object/t4/ObjectThingSchema.cs new file mode 100644 index 0000000000..73f307b70f --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Object/t4/ObjectThingSchema.cs @@ -0,0 +1,343 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser.Models; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class ObjectThingSchema : ObjectThingSchemaBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.PushIndent(new string(' ', this.indent)); + this.Write("\"title\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.objectTitle)); + this.Write("\",\r\n\"type\": \"object\",\r\n"); + if (this.objectFields.Any(f => IsFieldRequired(f))) { + this.Write("\"required\": [ "); + this.Write(this.ToStringHelper.ToStringWithCulture(string.Join(", ", this.objectFields.Where(f => IsFieldRequired(f)).Select(f => $"\"{f.Name}\"")))); + this.Write(" ],\r\n"); + } + if (this.objectFields.Any(f => IsFieldMessage(f))) { + this.Write("\"dtv:errorMessage\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.objectFields.First(f => IsFieldMessage(f)).Name)); + this.Write("\",\r\n"); + } + this.Write("\"properties\": {\r\n"); + int ix2 = 1; foreach (var dtField in this.objectFields) { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(dtField.Name)); + this.Write("\": {\r\n"); + if (dtField.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(dtField.Description.First().Value)); + this.Write("\",\r\n"); + } else if (!dtField.Schema.Id.AbsoluteUri.StartsWith("dtmi:dtdl:instance:Schema:") && dtField.Schema.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(dtField.Schema.Description.First().Value)); + this.Write("\",\r\n"); + } + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(dtField.Schema, 4))); + this.Write("\r\n }"); + this.Write(this.ToStringHelper.ToStringWithCulture(ix2 < this.objectFields.Count ? "," : "")); + this.Write("\r\n"); + ix2++; } + this.Write("}"); + this.PopIndent(); + return this.GenerationEnvironment.ToString(); + } + + private bool IsFieldRequired(DTFieldInfo dtField) => dtField.SupplementalTypes.Any(t => DtdlMqttExtensionValues.RequiredAdjunctTypeRegex.IsMatch(t.AbsoluteUri)); + + private bool IsFieldMessage(DTFieldInfo dtField) => dtField.SupplementalTypes.Contains(this.errorMessageAdjunctTypeId); + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class ObjectThingSchemaBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Dtdl2Wot/Object/t4/ObjectThingSchema.tt b/codegen2/src/Dtdl2Wot/Object/t4/ObjectThingSchema.tt new file mode 100644 index 0000000000..42d76e1581 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Object/t4/ObjectThingSchema.tt @@ -0,0 +1,30 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="DTDLParser.Models" #> +<# this.PushIndent(new string(' ', this.indent)); #> +"title": "<#=this.objectTitle#>", +"type": "object", +<# if (this.objectFields.Any(f => IsFieldRequired(f))) { #> +"required": [ <#=string.Join(", ", this.objectFields.Where(f => IsFieldRequired(f)).Select(f => $"\"{f.Name}\""))#> ], +<# } #> +<# if (this.objectFields.Any(f => IsFieldMessage(f))) { #> +"dtv:errorMessage": "<#=this.objectFields.First(f => IsFieldMessage(f)).Name#>", +<# } #> +"properties": { +<# int ix2 = 1; foreach (var dtField in this.objectFields) { #> + "<#=dtField.Name#>": { +<# if (dtField.Description.Any()) { #> + "description": "<#=dtField.Description.First().Value#>", +<# } else if (!dtField.Schema.Id.AbsoluteUri.StartsWith("dtmi:dtdl:instance:Schema:") && dtField.Schema.Description.Any()) { #> + "description": "<#=dtField.Schema.Description.First().Value#>", +<# } #> +<#=this.thingDescriber.GetTypeAndAddenda(dtField.Schema, 4)#> + }<#=ix2 < this.objectFields.Count ? "," : ""#> +<# ix2++; } #> +}<# this.PopIndent(); #> +<#+ + private bool IsFieldRequired(DTFieldInfo dtField) => dtField.SupplementalTypes.Any(t => DtdlMqttExtensionValues.RequiredAdjunctTypeRegex.IsMatch(t.AbsoluteUri)); + + private bool IsFieldMessage(DTFieldInfo dtField) => dtField.SupplementalTypes.Contains(this.errorMessageAdjunctTypeId); +#> diff --git a/codegen2/src/Dtdl2Wot/PayloadFormat.cs b/codegen2/src/Dtdl2Wot/PayloadFormat.cs new file mode 100644 index 0000000000..22365dcaa1 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/PayloadFormat.cs @@ -0,0 +1,27 @@ +namespace Dtdl2Wot +{ + using System.Linq; + + /// + /// Static class that defines designators used to identify payload formats. + /// + public static class PayloadFormat + { + public const string Avro = "Avro/1.11.0"; + + public const string Json = "Json/ecma/404"; + + public const string Raw = "raw/0"; + + public const string Custom = "custom/0"; + + public static string Itemize(string separator, string mark) => + string.Join(separator, new string[] + { + Avro, + Json, + Raw, + Custom, + }.Select(s => $"{mark}{s}{mark}")); + } +} diff --git a/codegen2/src/Dtdl2Wot/Program.cs b/codegen2/src/Dtdl2Wot/Program.cs new file mode 100644 index 0000000000..96b9052e62 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Program.cs @@ -0,0 +1,79 @@ +namespace Dtdl2Wot +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.IO; + using DTDLParser; + using DTDLParser.Models; + + internal class Program + { + static int Main(string[] args) + { + if (args.Length < 2) + { + Console.WriteLine("Usage: Dtdl2Wot [schemaNamesFilePath]"); + Console.WriteLine("Converts a DTDL model file to a WoT Thing Description."); + Console.WriteLine(" Path to the input DTDL model file."); + Console.WriteLine(" Path to the output folder for the generated Thing Description."); + Console.WriteLine(" [resolverFilePath] Optional path to a JSON file that defines DTMI resolution rules."); + Console.WriteLine(" [schemaNamesFilePath] Optional path to a JSON file that defines schema naming rules."); + Console.WriteLine(" If not specified, default path is 'SchemaNames.json' in the output folder."); + Console.WriteLine(" If file does not exist, one will be created (using specified or default path)."); + return 1; + } + + FileInfo inputFile = new FileInfo(args[0]); + DirectoryInfo outputDirectory = new DirectoryInfo(args[1]); + string resolverFilePath = args.Length > 2 ? args[2] : string.Empty; + FileInfo schemaNamesFile = new FileInfo(args.Length > 3 ? args[3] : Path.Combine(outputDirectory.FullName, "SchemaNames.json")); + + string modelText = inputFile.OpenText().ReadToEnd(); + + DtdlParseLocator parseLocator = (int parseIndex, int parseLine, out string sourceName, out int sourceLine) => + { + sourceName = inputFile.Name; + sourceLine = parseLine; + return true; + }; + + ParsingOptions parsingOptions = new(); + if (!string.IsNullOrEmpty(resolverFilePath) && File.Exists(resolverFilePath)) + { + parsingOptions.DtmiResolver = new Resolver(resolverFilePath).Resolve; + } + parsingOptions.ExtensionLimitContexts = new List { new Dtmi("dtmi:dtdl:limits:onvif"), new Dtmi("dtmi:dtdl:limits:aio") }; + + var modelParser = new ModelParser(parsingOptions); + + IReadOnlyDictionary model = modelParser.Parse(modelText, parseLocator); + + foreach (DTEntityInfo dtEntity in model.Values) + { + if (dtEntity.EntityKind == DTEntityKind.Interface && dtEntity.SupplementalTypes.Any(t => DtdlMqttExtensionValues.MqttAdjunctTypeRegex.IsMatch(t.AbsoluteUri))) + { + DTInterfaceInfo dtInterface = (DTInterfaceInfo)dtEntity; + Dtmi mqttTypeId = dtInterface.SupplementalTypes.First(t => DtdlMqttExtensionValues.MqttAdjunctTypeRegex.IsMatch(t.AbsoluteUri)); + int mqttVersion = int.Parse(DtdlMqttExtensionValues.MqttAdjunctTypeRegex.Match(mqttTypeId.AbsoluteUri).Groups[1].Captures[0].Value); + + ThingGenerator thingGenerator = new ThingGenerator(model, dtInterface.Id, mqttVersion); + + thingGenerator.GenerateThing(outputDirectory, schemaNamesFile); + } + } + + if (!schemaNamesFile.Exists) + { + Stream schemaNamesStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Dtdl2Wot.Resources.conversion.SchemaNames.json")!; + string schemaNamesText = new StreamReader(schemaNamesStream).ReadToEnd(); + File.WriteAllText(schemaNamesFile.FullName, schemaNamesText); + + Console.WriteLine($" generated {schemaNamesFile.FullName}"); + } + + return 0; + } + } +} diff --git a/codegen2/src/Dtdl2Wot/Property/code/PropertyAffordance.cs b/codegen2/src/Dtdl2Wot/Property/code/PropertyAffordance.cs new file mode 100644 index 0000000000..3cd0b8e31e --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Property/code/PropertyAffordance.cs @@ -0,0 +1,50 @@ +namespace Dtdl2Wot +{ + using System.Linq; + using DTDLParser; + using DTDLParser.Models; + + public partial class PropertyAffordance : ITemplateTransform + { + private readonly DTPropertyInfo dtProperty; + private readonly bool usesTypes; + private readonly string contentType; + private readonly bool separate; + private readonly string propertyTopic; + private readonly bool isSchemaPropertyResult; + private readonly string? valueName; + private readonly DTSchemaInfo? valueSchema; + private readonly string? readErrorSchemaName; + private readonly string? writeErrorSchemaName; + private readonly bool isSchemaFragmented; + private readonly ThingDescriber thingDescriber; + + public PropertyAffordance(DTPropertyInfo dtProperty, int mqttVersion, bool usesTypes, string contentType, string propertyTopic, ThingDescriber thingDescriber) + { + this.dtProperty = dtProperty; + this.usesTypes = usesTypes; + this.contentType = contentType; + this.separate = propertyTopic.Contains(DtdlMqttTopicTokens.PropertyName); + this.propertyTopic = propertyTopic.Replace(DtdlMqttTopicTokens.PropertyName, this.dtProperty.Name); + + this.isSchemaPropertyResult = dtProperty.Schema.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.PropertyResultAdjunctTypeFormat, mqttVersion))); + DTFieldInfo? valueField = (dtProperty.Schema as DTObjectInfo)?.Fields?.FirstOrDefault(f => f.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.PropertyValueAdjunctTypeFormat, mqttVersion)))); + DTFieldInfo? readErrorField = (dtProperty.Schema as DTObjectInfo)?.Fields?.FirstOrDefault(f => f.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.ReadErrorAdjunctTypeFormat, mqttVersion)))); + DTFieldInfo? writeErrorField = (dtProperty.Schema as DTObjectInfo)?.Fields?.FirstOrDefault(f => f.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.WriteErrorAdjunctTypeFormat, mqttVersion)))); + + this.valueName = isSchemaPropertyResult ? valueField?.Name : dtProperty.Name; + this.valueSchema = isSchemaPropertyResult ? valueField?.Schema : dtProperty.Schema; + this.readErrorSchemaName = readErrorField != null ? new CodeName(readErrorField.Schema.Id).AsGiven : null; + this.writeErrorSchemaName = writeErrorField != null ? new CodeName(writeErrorField.Schema.Id).AsGiven : null; + + DTEntityInfo? fragCarrier = isSchemaPropertyResult ? valueField : dtProperty; + this.isSchemaFragmented = fragCarrier?.SupplementalTypes.Contains(new Dtmi(string.Format(DtdlMqttExtensionValues.FragmentedAdjunctTypeFormat, mqttVersion))) ?? false; + + this.thingDescriber = thingDescriber; + } + + public string FileName { get => string.Empty; } + + public string FolderPath { get => string.Empty; } + } +} diff --git a/codegen2/src/Dtdl2Wot/Property/t4/PropertyAffordance.cs b/codegen2/src/Dtdl2Wot/Property/t4/PropertyAffordance.cs new file mode 100644 index 0000000000..886bb31307 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Property/t4/PropertyAffordance.cs @@ -0,0 +1,351 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser; + using DTDLParser.Models; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class PropertyAffordance : PropertyAffordanceBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtProperty.Name)); + this.Write("\": {\r\n"); + if (this.dtProperty.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtProperty.Description.First().Value)); + this.Write("\",\r\n"); + } + if (this.usesTypes) { + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(this.valueSchema, 6, this.isSchemaFragmented))); + this.Write(",\r\n \"readOnly\": "); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtProperty.Writable ? "false" : "true")); + this.Write(",\r\n"); + } + this.Write(" \"forms\": [\r\n"); + if (this.dtProperty.Writable) { + this.Write(" {\r\n \"contentType\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\",\r\n"); + if (this.isSchemaPropertyResult && this.writeErrorSchemaName != null) { + this.Write(" \"additionalResponses\": [\r\n {\r\n \"success\": false" + + ",\r\n \"schema\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.writeErrorSchemaName)); + this.Write("\"\r\n }\r\n ],\r\n"); + } + if (this.separate) { + this.Write(" \"dtv:topic\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyTopic.Replace(DtdlMqttTopicTokens.PropertyAction, "write"))); + this.Write("\",\r\n"); + } + this.Write(" \"op\": \"writeproperty\"\r\n },\r\n"); + } + this.Write(" {\r\n \"contentType\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\",\r\n"); + if (this.isSchemaPropertyResult && this.readErrorSchemaName != null) { + this.Write(" \"additionalResponses\": [\r\n {\r\n \"success\": false" + + ",\r\n \"schema\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.readErrorSchemaName)); + this.Write("\"\r\n }\r\n ],\r\n"); + } + if (this.separate) { + this.Write(" \"dtv:topic\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.propertyTopic.Replace(DtdlMqttTopicTokens.PropertyAction, "read"))); + this.Write("\",\r\n"); + } + this.Write(" \"op\": \"readproperty\"\r\n }\r\n ]\r\n }"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class PropertyAffordanceBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Dtdl2Wot/Property/t4/PropertyAffordance.tt b/codegen2/src/Dtdl2Wot/Property/t4/PropertyAffordance.tt new file mode 100644 index 0000000000..991c8f3528 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Property/t4/PropertyAffordance.tt @@ -0,0 +1,48 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="DTDLParser" #> +<#@ import namespace="DTDLParser.Models" #> + "<#=this.dtProperty.Name#>": { +<# if (this.dtProperty.Description.Any()) { #> + "description": "<#=this.dtProperty.Description.First().Value#>", +<# } #> +<# if (this.usesTypes) { #> +<#=this.thingDescriber.GetTypeAndAddenda(this.valueSchema, 6, this.isSchemaFragmented)#>, + "readOnly": <#=this.dtProperty.Writable ? "false" : "true"#>, +<# } #> + "forms": [ +<# if (this.dtProperty.Writable) { #> + { + "contentType": "<#=this.contentType#>", +<# if (this.isSchemaPropertyResult && this.writeErrorSchemaName != null) { #> + "additionalResponses": [ + { + "success": false, + "schema": "<#=this.writeErrorSchemaName#>" + } + ], +<# } #> +<# if (this.separate) { #> + "dtv:topic": "<#=this.propertyTopic.Replace(DtdlMqttTopicTokens.PropertyAction, "write")#>", +<# } #> + "op": "writeproperty" + }, +<# } #> + { + "contentType": "<#=this.contentType#>", +<# if (this.isSchemaPropertyResult && this.readErrorSchemaName != null) { #> + "additionalResponses": [ + { + "success": false, + "schema": "<#=this.readErrorSchemaName#>" + } + ], +<# } #> +<# if (this.separate) { #> + "dtv:topic": "<#=this.propertyTopic.Replace(DtdlMqttTopicTokens.PropertyAction, "read")#>", +<# } #> + "op": "readproperty" + } + ] + } \ No newline at end of file diff --git a/codegen2/src/Dtdl2Wot/RecursionException.cs b/codegen2/src/Dtdl2Wot/RecursionException.cs new file mode 100644 index 0000000000..99699b28c1 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/RecursionException.cs @@ -0,0 +1,15 @@ +namespace Dtdl2Wot +{ + using System; + + public class RecursionException : Exception + { + public CodeName SchemaName { get; } + + public RecursionException(CodeName schemaName) + : base($"Schema {schemaName.AsGiven} refers to itself") + { + SchemaName = schemaName; + } + } +} diff --git a/codegen2/src/Dtdl2Wot/Resolver.cs b/codegen2/src/Dtdl2Wot/Resolver.cs new file mode 100644 index 0000000000..cad4db2aca --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Resolver.cs @@ -0,0 +1,127 @@ +namespace Dtdl2Wot +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text.Json; + using System.Text.RegularExpressions; + using System.Threading; + using DTDLParser; + + public class Resolver + { + private const string RegexKey = "regex"; + private const string PathKey = "path"; + private const string WildcardKey = "wild"; + private const string TokenRegexPattern = @"\{(\d+)\}"; + + private static readonly Regex tokenRegex = new Regex(TokenRegexPattern, RegexOptions.Compiled); + + private readonly string configPath; + private readonly Regex dtmiRegex; + private readonly string pathTemplate; + private readonly string? wildcard; + + public Resolver(string configPath) + { + this.configPath = configPath; + + if (!File.Exists(configPath)) + { + throw new Exception($"Resolver config file {configPath} not found"); + } + + using (StreamReader configReader = File.OpenText(configPath)) + { + using (JsonDocument configDoc = JsonDocument.Parse(configReader.ReadToEnd())) + { + if (!configDoc.RootElement.TryGetProperty(RegexKey, out JsonElement regexElt)) + { + throw new Exception($"Resolver config file {configPath} missing '{RegexKey}' property"); + } + + dtmiRegex = new Regex(regexElt.GetString()!); + + if (!configDoc.RootElement.TryGetProperty(PathKey, out JsonElement pathElt)) + { + throw new Exception($"Resolver config file {configPath} missing '{PathKey}' property"); + } + + pathTemplate = pathElt.GetString()!; + + if (configDoc.RootElement.TryGetProperty(WildcardKey, out JsonElement wildcardElt)) + { + wildcard = wildcardElt.GetString(); + } + } + } + } + + public IEnumerable Resolve(IReadOnlyCollection dtmis) + { + var refJsonTexts = new List(); + HashSet modelFilePaths = new (); + + foreach (Dtmi dtmi in dtmis) + { + Match dtmiMatch = dtmiRegex.Match(dtmi.AbsoluteUri); + if (dtmiMatch.Success) + { + string path = pathTemplate; + foreach (Match tokenMatch in tokenRegex.Matches(pathTemplate)) + { + int groupIndex = int.Parse(tokenMatch.Groups[1].Captures[0].Value); + path = path.Replace($"{{{groupIndex}}}", dtmiMatch.Groups[groupIndex].Captures[0].Value); + } + + string relativePath = Path.Combine(Path.GetDirectoryName(configPath)!, path); + string modelFolderPath = Path.GetDirectoryName(relativePath) ?? "."; + string modelFileName = Path.GetFileName(relativePath) ?? "*.json"; + string modelFilePath; + + if (wildcard != null) + { + string? parentFolderPath = Path.GetDirectoryName(modelFolderPath); + if (parentFolderPath != null) + { + string leafFolderName = Path.GetFileName(modelFolderPath); + modelFolderPath = Directory.GetDirectories(parentFolderPath, leafFolderName.Replace('_', '*')).FirstOrDefault(d => Path.GetFileName(d).Length == leafFolderName.Length) ?? modelFolderPath; + } + + modelFilePath = Directory.GetFiles(modelFolderPath, modelFileName.Replace('_', '*')).FirstOrDefault(d => Path.GetFileName(d).Length == modelFileName.Length) ?? Path.Combine(modelFolderPath, modelFileName); + } + else + { + modelFilePath = Path.Combine(modelFolderPath, modelFileName); + } + + if (File.Exists(modelFilePath) && !modelFilePaths.Contains(modelFilePath)) + { + string jsonText = File.ReadAllText(modelFilePath); + refJsonTexts.Add(jsonText); + modelFilePaths.Add(modelFilePath); + } + } + } + + return refJsonTexts; + } + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously +#pragma warning disable CS8425 // 'CancellationToken' is not decorated with the 'EnumeratorCancellation' attribute, so the cancellation token parameter from the generated 'IAsyncEnumerable<>.GetAsyncEnumerator' will be unconsumed + public async IAsyncEnumerable ResolveAsync(IReadOnlyCollection dtmis, CancellationToken _) + { + IEnumerable values = Resolve(dtmis); + if (values != null) + { + foreach (string value in values) + { + yield return value; + } + } + } +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously +#pragma warning restore CS8425 // 'CancellationToken' is not decorated with the 'EnumeratorCancellation' attribute, so the cancellation token parameter from the generated 'IAsyncEnumerable<>.GetAsyncEnumerator' will be unconsumed + } +} diff --git a/codegen2/src/Dtdl2Wot/Telemetry/code/TelemetryAffordance.cs b/codegen2/src/Dtdl2Wot/Telemetry/code/TelemetryAffordance.cs new file mode 100644 index 0000000000..1937385d7c --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Telemetry/code/TelemetryAffordance.cs @@ -0,0 +1,31 @@ +namespace Dtdl2Wot +{ + using DTDLParser.Models; + + public partial class TelemetryAffordance : ITemplateTransform + { + private readonly DTTelemetryInfo dtTelemetry; + private readonly bool usesTypes; + private readonly string contentType; + private readonly bool separate; + private readonly string telemetryTopic; + private readonly string? serviceGroupId; + private readonly ThingDescriber thingDescriber; + + public TelemetryAffordance(DTTelemetryInfo dtTelemetry, bool usesTypes, string contentType, string telemetryTopic, string? serviceGroupId, ThingDescriber thingDescriber) + { + this.dtTelemetry = dtTelemetry; + this.usesTypes = usesTypes; + this.contentType = contentType; + this.separate = telemetryTopic.Contains(DtdlMqttTopicTokens.TelemetryName); + this.telemetryTopic = telemetryTopic.Replace(DtdlMqttTopicTokens.TelemetryName, this.dtTelemetry.Name); + this.serviceGroupId = serviceGroupId; + + this.thingDescriber = thingDescriber; + } + + public string FileName { get => string.Empty; } + + public string FolderPath { get => string.Empty; } + } +} diff --git a/codegen2/src/Dtdl2Wot/Telemetry/t4/TelemetryAffordance.cs b/codegen2/src/Dtdl2Wot/Telemetry/t4/TelemetryAffordance.cs new file mode 100644 index 0000000000..9a70a2505d --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Telemetry/t4/TelemetryAffordance.cs @@ -0,0 +1,333 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 18.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace Dtdl2Wot +{ + using System.Collections.Generic; + using System.Linq; + using DTDLParser; + using DTDLParser.Models; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public partial class TelemetryAffordance : TelemetryAffordanceBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write(" \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtTelemetry.Name)); + this.Write("\": {\r\n"); + if (this.dtTelemetry.Description.Any()) { + this.Write(" \"description\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.dtTelemetry.Description.First().Value)); + this.Write("\",\r\n"); + } + if (this.usesTypes) { + this.Write(" \"data\": {\r\n"); + this.Write(this.ToStringHelper.ToStringWithCulture(this.thingDescriber.GetTypeAndAddenda(this.dtTelemetry.Schema, 8))); + this.Write("\r\n },\r\n"); + } else { + this.Write(" \"data\": {\r\n \"type\": \"null\"\r\n },\r\n"); + } + this.Write(" \"forms\": [\r\n {\r\n \"contentType\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.contentType)); + this.Write("\",\r\n"); + if (this.serviceGroupId != null) { + this.Write(" \"dtv:serviceGroupId\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.serviceGroupId)); + this.Write("\",\r\n"); + } + if (this.separate) { + this.Write(" \"dtv:topic\": \""); + this.Write(this.ToStringHelper.ToStringWithCulture(this.telemetryTopic)); + this.Write("\",\r\n"); + } + this.Write(" \"op\": \"subscribeevent\"\r\n }\r\n ]\r\n }"); + return this.GenerationEnvironment.ToString(); + } + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "18.0.0.0")] + public class TelemetryAffordanceBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/codegen2/src/Dtdl2Wot/Telemetry/t4/TelemetryAffordance.tt b/codegen2/src/Dtdl2Wot/Telemetry/t4/TelemetryAffordance.tt new file mode 100644 index 0000000000..1fd5a65943 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/Telemetry/t4/TelemetryAffordance.tt @@ -0,0 +1,31 @@ +<#@ template language="C#" linePragmas="false" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="DTDLParser" #> +<#@ import namespace="DTDLParser.Models" #> + "<#=this.dtTelemetry.Name#>": { +<# if (this.dtTelemetry.Description.Any()) { #> + "description": "<#=this.dtTelemetry.Description.First().Value#>", +<# } #> +<# if (this.usesTypes) { #> + "data": { +<#=this.thingDescriber.GetTypeAndAddenda(this.dtTelemetry.Schema, 8)#> + }, +<# } else { #> + "data": { + "type": "null" + }, +<# } #> + "forms": [ + { + "contentType": "<#=this.contentType#>", +<# if (this.serviceGroupId != null) { #> + "dtv:serviceGroupId": "<#=this.serviceGroupId#>", +<# } #> +<# if (this.separate) { #> + "dtv:topic": "<#=this.telemetryTopic#>", +<# } #> + "op": "subscribeevent" + } + ] + } \ No newline at end of file diff --git a/codegen2/src/Dtdl2Wot/ThingDescriber.cs b/codegen2/src/Dtdl2Wot/ThingDescriber.cs new file mode 100644 index 0000000000..bb5bc75a59 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/ThingDescriber.cs @@ -0,0 +1,132 @@ +namespace Dtdl2Wot +{ + using System; + using System.Collections.Generic; + using DTDLParser; + using DTDLParser.Models; + + public class ThingDescriber + { + private readonly int mqttVersion; + private HashSet ancestralSchemaIds; + + public ThingDescriber(int mqttVersion) + { + this.mqttVersion = mqttVersion; + this.ancestralSchemaIds = new HashSet(); + } + + public string GetTypeAndAddenda(DTSchemaInfo dtSchema, int indent, bool isFragmented = false) + { + if (dtSchema.EntityKind == DTEntityKind.Object) + { + ITemplateTransform templateTransform = new ObjectThingSchema((DTObjectInfo)dtSchema, indent, this.mqttVersion, this); + return this.GetTransformedText(templateTransform, dtSchema.Id); + } + + if (dtSchema.EntityKind == DTEntityKind.Enum) + { + ITemplateTransform templateTransform = new EnumThingSchema((DTEnumInfo)dtSchema, indent); + return this.GetTransformedText(templateTransform, dtSchema.Id); + } + + if (dtSchema.EntityKind == DTEntityKind.Array) + { + ITemplateTransform templateTransform = new ArrayThingSchema((DTArrayInfo)dtSchema, indent, this); + return this.GetTransformedText(templateTransform, dtSchema.Id); + } + + if (dtSchema.EntityKind == DTEntityKind.Map) + { + ITemplateTransform templateTransform = isFragmented ? new PlaceholderThingSchema((DTMapInfo)dtSchema, indent, this) : new MapThingSchema((DTMapInfo)dtSchema, indent, this); + return this.GetTransformedText(templateTransform, dtSchema.Id); + } + + string it = new string(' ', indent); + string nl = $"{Environment.NewLine}{it}"; + + return dtSchema.Id.AbsoluteUri switch + { + "dtmi:dtdl:instance:Schema:boolean;2" => $"{it}\"type\": \"boolean\"", + "dtmi:dtdl:instance:Schema:double;2" => $"{it}\"type\": \"number\",{nl}\"minimum\": -1.80e+308,{nl}\"maximum\": 1.80e+308", + "dtmi:dtdl:instance:Schema:float;2" => $"{it}\"type\": \"number\",{nl}\"minimum\": -3.40e+38,{nl}\"maximum\": 3.40e+38", + "dtmi:dtdl:instance:Schema:integer;2" => $"{it}\"type\": \"integer\",{nl}\"minimum\": -2147483648,{nl}\"maximum\": 2147483647", + "dtmi:dtdl:instance:Schema:long;2" => $"{it}\"type\": \"integer\",{nl}\"minimum\": -9223372036854775808,{nl}\"maximum\": 9223372036854775807", + "dtmi:dtdl:instance:Schema:byte;4" => $"{it}\"type\": \"integer\",{nl}\"minimum\": -128,{nl}\"maximum\": 127", + "dtmi:dtdl:instance:Schema:short;4" => $"{it}\"type\": \"integer\",{nl}\"minimum\": -32768,{nl}\"maximum\": 32767", + "dtmi:dtdl:instance:Schema:unsignedInteger;4" => $"{it}\"type\": \"integer\",{nl}\"minimum\": 0,{nl}\"maximum\": 4294967295", + "dtmi:dtdl:instance:Schema:unsignedLong;4" => $"{it}\"type\": \"integer\",{nl}\"minimum\": 0,{nl}\"maximum\": 18446744073709551615", + "dtmi:dtdl:instance:Schema:unsignedByte;4" => $"{it}\"type\": \"integer\",{nl}\"minimum\": 0,{nl}\"maximum\": 255", + "dtmi:dtdl:instance:Schema:unsignedShort;4" => $"{it}\"type\": \"integer\",{nl}\"minimum\": 0,{nl}\"maximum\": 65535", + "dtmi:dtdl:instance:Schema:date;2" => $"{it}\"type\": \"string\",{nl}\"format\": \"date\"", + "dtmi:dtdl:instance:Schema:dateTime;2" => $"{it}\"type\": \"string\",{nl}\"format\": \"date-time\"", + "dtmi:dtdl:instance:Schema:time;2" => $"{it}\"type\": \"string\",{nl}\"format\": \"time\"", + "dtmi:dtdl:instance:Schema:duration;2" => $"{it}\"type\": \"string\",{nl}\"pattern\": " + @"""^P(?!$)(?:(?:(?:(?:\\d+Y)|(?:\\d+\\.\\d+Y$))?(?:(?:\\d+M)|(?:\\d+\\.\\d+M$))?)|(?:(?:(?:\\d+W)|(?:\\d+\\.\\d+W$))?))(?:(?:\\d+D)|(?:\\d+\\.\\d+D$))?(?:T(?!$)(?:(?:\\d+H)|(?:\\d+\\.\\d+H$))?(?:(?:\\d+M)|(?:\\d+\\.\\d+M$))?(?:\\d+(?:\\.\\d+)?S)?)?$""", + "dtmi:dtdl:instance:Schema:string;2" => $"{it}\"type\": \"string\"", + "dtmi:dtdl:instance:Schema:uuid;4" => $"{it}\"type\": \"string\",{nl}\"format\": \"uuid\"", + "dtmi:dtdl:instance:Schema:bytes;4" => $"{it}\"type\": \"string\",{nl}\"contentEncoding\": \"base64\"", + "dtmi:dtdl:instance:Schema:decimal;4" => $"{it}\"type\": \"string\",{nl}\"pattern\": " + @"""^(?:\\+|-)?(?:[1-9][0-9]*|0)(?:\\.[0-9]*)?$""", + _ => string.Empty, + }; + } + + public string GetCommandAffordance(DTCommandInfo dtCommand, bool usesTypes, string contentType, string commandTopic, string serviceGroupId) + { + ITemplateTransform templateTransform = new CommandAffordance(dtCommand, this.mqttVersion, usesTypes, contentType, commandTopic, serviceGroupId, this); + return templateTransform.TransformText(); + } + + public string GetPropertyAffordance(DTPropertyInfo dtProperty, bool usesTypes, string contentType, string propertyTopic) + { + ITemplateTransform templateTransform = new PropertyAffordance(dtProperty, this.mqttVersion, usesTypes, contentType, propertyTopic, this); + return templateTransform.TransformText(); + } + + public string GetTelemetryAffordance(DTTelemetryInfo dtTelemetry, bool usesTypes, string contentType, string telemetryTopic, string serviceGroupId) + { + ITemplateTransform templateTransform = new TelemetryAffordance(dtTelemetry, usesTypes, contentType, telemetryTopic, serviceGroupId, this); + return templateTransform.TransformText(); + } + + public static string GetPrimitiveType(Dtmi primitiveSchemaId) + { + return primitiveSchemaId.AbsoluteUri switch + { + "dtmi:dtdl:instance:Schema:boolean;2" => "boolean", + "dtmi:dtdl:instance:Schema:double;2" => "number", + "dtmi:dtdl:instance:Schema:float;2" => "number", + "dtmi:dtdl:instance:Schema:integer;2" => "integer", + "dtmi:dtdl:instance:Schema:long;2" => "integer", + "dtmi:dtdl:instance:Schema:byte;4" => "integer", + "dtmi:dtdl:instance:Schema:short;4" => "integer", + "dtmi:dtdl:instance:Schema:unsignedInteger;4" => "integer", + "dtmi:dtdl:instance:Schema:unsignedLong;4" => "integer", + "dtmi:dtdl:instance:Schema:unsignedByte;4" => "integer", + "dtmi:dtdl:instance:Schema:unsignedShort;4" => "integer", + "dtmi:dtdl:instance:Schema:date;2" => "string", + "dtmi:dtdl:instance:Schema:dateTime;2" => "string", + "dtmi:dtdl:instance:Schema:time;2" => "string", + "dtmi:dtdl:instance:Schema:duration;2" => "string", + "dtmi:dtdl:instance:Schema:string;2" => "string", + "dtmi:dtdl:instance:Schema:uuid;4" => "string", + "dtmi:dtdl:instance:Schema:bytes;4" => "string", + "dtmi:dtdl:instance:Schema:decimal;4" => "string", + _ => "null", + }; + } + + private string GetTransformedText(ITemplateTransform templateTransform, Dtmi schemaId) + { + if (this.ancestralSchemaIds.Contains(schemaId)) + { + throw new RecursionException(new CodeName(schemaId)); + } + + this.ancestralSchemaIds.Add(schemaId); + string text = templateTransform.TransformText(); + this.ancestralSchemaIds.Remove(schemaId); + + return text; + } + } +} diff --git a/codegen2/src/Dtdl2Wot/ThingGenerator.cs b/codegen2/src/Dtdl2Wot/ThingGenerator.cs new file mode 100644 index 0000000000..6605858317 --- /dev/null +++ b/codegen2/src/Dtdl2Wot/ThingGenerator.cs @@ -0,0 +1,54 @@ +namespace Dtdl2Wot +{ + using System; + using System.Collections.Generic; + using System.IO; + using DTDLParser; + using DTDLParser.Models; + + public class ThingGenerator + { + private readonly IReadOnlyDictionary modelDict; + private readonly Dtmi interfaceId; + private readonly int mqttVersion; + + public ThingGenerator(IReadOnlyDictionary modelDict, Dtmi interfaceId, int mqttVersion) + { + this.modelDict = modelDict; + this.interfaceId = interfaceId; + this.mqttVersion = mqttVersion; + } + + public bool GenerateThing(DirectoryInfo outDir, FileInfo schemaNamesFile) + { + string schemaNamesPath = Path.GetRelativePath(outDir.FullName, schemaNamesFile.FullName).Replace('\\', '/'); + + DTInterfaceInfo dtInterface = (DTInterfaceInfo)modelDict[interfaceId]; + + ITemplateTransform interfaceThingTransform = new InterfaceThing(modelDict, interfaceId, this.mqttVersion, schemaNamesPath); + + string interfaceThingText; + try + { + interfaceThingText = interfaceThingTransform.TransformText(); + } + catch (RecursionException rex) + { + Console.WriteLine($"Unable to generate Thing Description {interfaceThingTransform.FileName} because {rex.SchemaName.AsDtmi} has a self-referential definition"); + return false; + } + + if (!outDir.Exists) + { + outDir.Create(); + } + + string filePath = Path.Combine(outDir.FullName, interfaceThingTransform.FileName); + File.WriteAllText(filePath, interfaceThingText); + + Console.WriteLine($" generated {filePath}"); + + return true; + } + } +} diff --git a/codegen2/src/TDParse/Program.cs b/codegen2/src/TDParse/Program.cs new file mode 100644 index 0000000000..2dd48a0f60 --- /dev/null +++ b/codegen2/src/TDParse/Program.cs @@ -0,0 +1,95 @@ +namespace TDParse +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Azure.Iot.Operations.TDParser; + using Azure.Iot.Operations.TDParser.Model; + + internal class Program + { + static void Main(string[] args) + { + if (args.Length < 1) + { + Console.WriteLine("Usage: TDParse "); + return; + } + + FileInfo file = new FileInfo(args[0]); + if (!file.Exists) + { + Console.WriteLine($"File not found: {file.FullName}"); + return; + } + + string tdJson = File.ReadAllText(file.FullName); + List? things = TDParser.Parse(tdJson); + if (things != null) + { + TDThing? thing = things.First(); + if (thing.Context?.Elements != null) + { + foreach (var context in thing.Context.Elements) + { + Console.WriteLine($"@context: {context}"); + } + } + Console.WriteLine($"Type: {thing.Type}"); + Console.WriteLine($"Title: {thing.Title}"); + if (thing.Forms?.Elements != null) + { + foreach (var form in thing.Forms.Elements) + { + if (form.Value.ContentType != null) + { + Console.WriteLine($" ContentType: {form.Value.ContentType}"); + } + if (form.Value.Topic != null) + { + Console.WriteLine($" Topic: {form.Value.Topic}"); + } + if (form.Value.Op != null) + { + Console.WriteLine($" Op: {string.Join(", ", form.Value.Op)}"); + } + } + } + if (thing.Events?.Entries != null) + { + foreach (var evt in thing.Events.Entries) + { + Console.WriteLine($"Event: {evt.Key}"); + } + } + if (thing.SchemaDefinitions?.Entries != null) + { + foreach (var schema in thing.SchemaDefinitions.Entries) + { + Console.WriteLine($"SchemaDefinition: {schema.Key}"); + if (schema.Value.Value.AdditionalProperties != null) + { + Console.WriteLine($" AdditionalProperties: {schema.Value.Value.AdditionalProperties}"); + } + } + } + if (thing.Properties?.Entries != null) + { + foreach (var prop in thing.Properties.Entries) + { + Console.WriteLine($"Property: {prop.Key}"); + if (prop.Value.Value.AdditionalProperties != null) + { + Console.WriteLine($" AdditionalProperties: {prop.Value.Value.AdditionalProperties}"); + } + } + } + } + else + { + Console.WriteLine("Failed to parse the Thing Description."); + } + } + } +} diff --git a/codegen2/src/TDParse/TDParse.csproj b/codegen2/src/TDParse/TDParse.csproj new file mode 100644 index 0000000000..c9ccdc8384 --- /dev/null +++ b/codegen2/src/TDParse/TDParse.csproj @@ -0,0 +1,13 @@ + + + + Exe + net9.0 + enable + + + + + + + diff --git a/codegen2/test/.gitignore b/codegen2/test/.gitignore new file mode 100644 index 0000000000..97f3e31276 --- /dev/null +++ b/codegen2/test/.gitignore @@ -0,0 +1 @@ +sandbox/ diff --git a/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/Azure.Iot.Operations.ProtocolCompiler.UnitTests.csproj b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/Azure.Iot.Operations.ProtocolCompiler.UnitTests.csproj new file mode 100644 index 0000000000..1307cfa6ad --- /dev/null +++ b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/Azure.Iot.Operations.ProtocolCompiler.UnitTests.csproj @@ -0,0 +1,33 @@ + + + + net9.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/ProtocolCompilerTester.cs b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/ProtocolCompilerTester.cs new file mode 100644 index 0000000000..962c0994d2 --- /dev/null +++ b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/ProtocolCompilerTester.cs @@ -0,0 +1,217 @@ +namespace Azure.Iot.Operations.ProtocolCompiler.UnitTests +{ + using Xunit; + using System.Buffers; + using System.IO; + using System.Text.Json; + using Azure.Iot.Operations.CodeGeneration; + using Azure.Iot.Operations.ProtocolCompilerLib; + + public class ProtocolCompilerTester + { + private const string basePath = "../../../.."; + private const string testCasesPath = $"{basePath}/test-cases"; + private const string successCasesPath = $"{testCasesPath}/success"; + private const string failureCasesPath = $"{testCasesPath}/failure"; + private const string tmPath = $"{basePath}/thing-models"; + private const string schemasPath = $"{basePath}/schemas"; + private const string namerPath = $"{basePath}/name-rules"; + private const string sandboxPath = $"{basePath}/sandbox"; + + static ProtocolCompilerTester() + { + } + + public static IEnumerable GetFailureTestCases() + { + foreach (string testCasePath in Directory.GetFiles(failureCasesPath, @"*.json")) + { + string testCaseName = Path.GetFileNameWithoutExtension(testCasePath); + yield return new object[] { testCaseName }; + } + } + + public static IEnumerable GetSuccessTestCases() + { + foreach (string testCasePath in Directory.GetFiles(successCasesPath, @"*.json")) + { + string testCaseName = Path.GetFileNameWithoutExtension(testCasePath); + yield return new object[] { testCaseName }; + } + } + + [Theory] + [MemberData(nameof(GetFailureTestCases))] + public async Task TestProtocolCompilerFailures(string testCaseName) + { + TestCase? testCase; + using (StreamReader streamReader = File.OpenText($"{failureCasesPath}/{testCaseName}.json")) + { + testCase = JsonSerializer.Deserialize(streamReader.ReadToEnd()); + Assert.False(testCase == null, $"Test case '{testCaseName}' descriptor failed to deserialize"); + Assert.False(testCase.Success, $"Test case '{testCaseName}' is in failure folder but is marked as success."); + Assert.False(testCase.Errors.Length == 0, $"Test case '{testCaseName}' is marked as failure but descriptor contains no 'errors' elements."); + } + + OptionContainer options = GetOptionContainer(testCaseName, testCase.CommandLine); + + if (options.OutputDir.Exists) + { + options.OutputDir.Delete(recursive: true); + } + + ErrorLog errorLog = CommandPerformer.GenerateCode(options, (_, _) => { }, suppressExternalTools: true); + + if (errorLog.HasErrors) + { + if (errorLog.FatalError != null) + { + CheckError(testCaseName, errorLog.FatalError, testCase.Errors); + } + + foreach (ErrorRecord errorRecord in errorLog.Errors) + { + CheckError(testCaseName, errorRecord, testCase.Errors); + } + } + else + { + Assert.Fail($"Test case '{testCaseName}' was expected to fail but returned no errors."); + } + } + + [Theory] + [MemberData(nameof(GetSuccessTestCases))] + public async Task TestProtocolCompilerSuccesses(string testCaseName) + { + TestCase? testCase; + using (StreamReader streamReader = File.OpenText($"{successCasesPath}/{testCaseName}.json")) + { + testCase = JsonSerializer.Deserialize(streamReader.ReadToEnd()); + Assert.False(testCase == null, $"Test case '{testCaseName}' descriptor failed to deserialize"); + Assert.True(testCase.Success, $"Test case '{testCaseName}' is in success folder but is marked as failure."); + Assert.True(testCase.Errors.Length == 0, $"Test case '{testCaseName}' is marked as success but descriptor contains 'errors' elements."); + } + + OptionContainer options = GetOptionContainer(testCaseName, testCase.CommandLine); + + if (options.OutputDir.Exists) + { + options.OutputDir.Delete(recursive: true); + } + + ErrorLog errorLog = CommandPerformer.GenerateCode(options, (_, _) => { }, suppressExternalTools: true); + + if (errorLog.HasErrors) + { + if (errorLog.FatalError != null) + { + Assert.Fail($"Test case '{testCaseName}' was expected to succeed but returned fatal error: '{errorLog.FatalError.Message}', file: {errorLog.FatalError.Filename}, line: {errorLog.FatalError.LineNumber}"); + } + else + { + Assert.Fail($"Test case '{testCaseName}' was expected to succeed but returned {errorLog.Errors.Count} error(s) including: '{errorLog.Errors.First().Message}', file: {errorLog.Errors.First().Filename}, line: {errorLog.Errors.First().LineNumber}"); + } + } + } + + private static void CheckError(string testCaseName, ErrorRecord errorRecord, TestError[] errors) + { + TestError? expectedError = GetBestMatchingExpectedError(errorRecord, errors); + if (expectedError != null) + { + if (!Enum.TryParse(expectedError.Condition, out ErrorCondition expectedCondition)) + { + Assert.Fail($"Test case '{testCaseName}' contains invalid error condition string '{expectedError.Condition}' in expected errors."); + } + + Assert.True(expectedCondition == errorRecord.Condition, $"Test case '{testCaseName}' returned error with unexpected condition. Expected: '{expectedCondition}', Actual: '{errorRecord.Condition}'; Message: \"{errorRecord.Message}\""); + Assert.True(expectedError.Filename == errorRecord.Filename, $"Test case '{testCaseName}' returned error with unexpected filename. Expected: '{expectedError.Filename}', Actual: '{errorRecord.Filename}'; Message: \"{errorRecord.Message}\""); + Assert.True(expectedError.LineNumber == errorRecord.LineNumber, $"Test case '{testCaseName}' returned error with unexpected line number. Expected: {expectedError.LineNumber}, Actual: {errorRecord.LineNumber}; Message: \"{errorRecord.Message}\""); + Assert.True(expectedError.CfLineNumber == errorRecord.CfLineNumber, $"Test case '{testCaseName}' returned error with unexpected cfLine number. Expected: {expectedError.CfLineNumber}, Actual: {errorRecord.CfLineNumber}; Message: \"{errorRecord.Message}\""); + Assert.True(expectedError.CrossRef == errorRecord.CrossRef, $"Test case '{testCaseName}' returned error with unexpected crossRef. Expected: '{expectedError.CrossRef}', Actual: '{errorRecord.CrossRef}'; Message: \"{errorRecord.Message}\""); + } + else + { + Assert.Fail($"Test case '{testCaseName}' returned unexpected error: '{errorRecord.Message}', file: {errorRecord.Filename}, line: {errorRecord.LineNumber}, cfLine: {errorRecord.CfLineNumber}, crossRef: '{errorRecord.CrossRef}'"); + } + } + + private static TestError? GetBestMatchingExpectedError(ErrorRecord errorRecord, TestError[] errors) + { + if (errors.Length == 0) + { + return null; + } + + List fileMatches = errors.Where(e => e.Filename == errorRecord.Filename).ToList(); + if (fileMatches.Count == 0) + { + return errors[0]; + } + + List conditionMatches = fileMatches.Where(e => e.Condition == errorRecord.Condition.ToString()).ToList(); + if (conditionMatches.Count == 0) + { + return fileMatches[0]; + } + + int minLineOffset = conditionMatches.Min(e => Math.Abs(e.LineNumber - errorRecord.LineNumber)); + List minOffsetMatches = conditionMatches.Where(e => Math.Abs(e.LineNumber - errorRecord.LineNumber) == minLineOffset).ToList(); + + int minCfLineOffset = minOffsetMatches.Min(e => Math.Abs(e.CfLineNumber - errorRecord.CfLineNumber)); + List minCfOffsetMatches = minOffsetMatches.Where(e => Math.Abs(e.CfLineNumber - errorRecord.CfLineNumber) == minCfLineOffset).ToList(); + + return minCfOffsetMatches.First(); + } + + private static OptionContainer GetOptionContainer(string testCaseName, TestCommandLine commandLine) + { + string testCaseSandboxPath = $"{sandboxPath}/{testCaseName}"; + + if (commandLine.ThingFiles.Any(tf => Path.IsPathRooted(tf))) + { + Assert.Fail($"Test case '{testCaseName}' specifies absolute path for thing file, which is not supported in test."); + } + if (commandLine.SchemaFiles.Any(sf => Path.IsPathRooted(sf))) + { + Assert.Fail($"Test case '{testCaseName}' specifies absolute path for schema file, which is not supported in test."); + } + if (commandLine.TypeNamerFile != null && Path.IsPathRooted(commandLine.TypeNamerFile)) + { + Assert.Fail($"Test case '{testCaseName}' specifies absolute path for type namer file, which is not supported in test."); + } + if (commandLine.OutputDir != null && Path.IsPathRooted(commandLine.OutputDir)) + { + Assert.Fail($"Test case '{testCaseName}' specifies absolute path for output directory, which is not supported in test."); + } + if (commandLine.WorkingDir != null && Path.IsPathRooted(commandLine.WorkingDir)) + { + Assert.Fail($"Test case '{testCaseName}' specifies absolute path for working directory, which is not supported in test."); + } + + FileInfo[] thingFiles = commandLine.ThingFiles.Select(tf => new FileInfo(Path.GetFullPath($"{tmPath}/{tf}"))).ToArray(); + string[] schemaFiles = commandLine.SchemaFiles.Select(tf => Path.GetFullPath($"{schemasPath}/{tf}")).ToArray(); + FileInfo? typeNamerFile = commandLine.TypeNamerFile != null ? new FileInfo(Path.GetFullPath($"{namerPath}/{commandLine.TypeNamerFile}")) : null; + DirectoryInfo outputDir = new DirectoryInfo(commandLine.OutputDir != null ? Path.GetFullPath($"{testCaseSandboxPath}/{commandLine.OutputDir}") : $"{testCaseSandboxPath}/{CommandPerformer.DefaultOutDir}"); + DirectoryInfo workingDir = new DirectoryInfo(commandLine.WorkingDir != null ? Path.GetFullPath($"{outputDir}/{commandLine.WorkingDir}") : $"{outputDir}/{CommandPerformer.DefaultWorkingDir}"); + string genNamespace = commandLine.GenNamespace ?? CommandPerformer.DefaultNamespace; + + return new OptionContainer + { + ThingFiles = thingFiles, + SchemaFiles = schemaFiles, + TypeNamerFile = typeNamerFile, + OutputDir = outputDir, + WorkingDir = workingDir, + GenNamespace = genNamespace, + SdkPath = commandLine.SdkPath, + Language = commandLine.Language ?? "none", + ClientOnly = commandLine.ClientOnly, + ServerOnly = commandLine.ServerOnly, + NoProj = commandLine.NoProj, + DefaultImpl = commandLine.DefaultImpl + }; + } + } +} diff --git a/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestCase.cs b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestCase.cs new file mode 100644 index 0000000000..534b4a00d7 --- /dev/null +++ b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestCase.cs @@ -0,0 +1,17 @@ +namespace Azure.Iot.Operations.ProtocolCompiler.UnitTests +{ + using System.Collections.Generic; + using System.Text.Json.Serialization; + + public class TestCase + { + [JsonPropertyName("success")] + public bool Success { get; set; } + + [JsonPropertyName("commandLine")] + public TestCommandLine CommandLine { get; set; } = new(); + + [JsonPropertyName("errors")] + public TestError[] Errors { get; set; } = []; + } +} diff --git a/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestCommandLine.cs b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestCommandLine.cs new file mode 100644 index 0000000000..cd0dfad7f6 --- /dev/null +++ b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestCommandLine.cs @@ -0,0 +1,44 @@ +namespace Azure.Iot.Operations.ProtocolCompiler.UnitTests +{ + using System.Collections.Generic; + using System.Text.Json.Serialization; + + public class TestCommandLine + { + [JsonPropertyName("thingFiles")] + public string[] ThingFiles { get; set; } = []; + + [JsonPropertyName("schemas")] + public string[] SchemaFiles { get; set; } = []; + + [JsonPropertyName("typeNamer")] + public string? TypeNamerFile { get; set; } + + [JsonPropertyName("outDir")] + public string? OutputDir { get; set; } + + [JsonPropertyName("workingDir")] + public string? WorkingDir { get; set; } + + [JsonPropertyName("namespaace")] + public string? GenNamespace { get; set; } + + [JsonPropertyName("sdkPath")] + public string? SdkPath { get; set; } + + [JsonPropertyName("lang")] + public string? Language { get; set; } + + [JsonPropertyName("clientOnly")] + public bool ClientOnly { get; set; } + + [JsonPropertyName("serverOnly")] + public bool ServerOnly { get; set; } + + [JsonPropertyName("noProj")] + public bool NoProj { get; set; } + + [JsonPropertyName("defaultImpl")] + public bool DefaultImpl { get; set; } + } +} diff --git a/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestError.cs b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestError.cs new file mode 100644 index 0000000000..e068e86a0c --- /dev/null +++ b/codegen2/test/Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestError.cs @@ -0,0 +1,23 @@ +namespace Azure.Iot.Operations.ProtocolCompiler.UnitTests +{ + using System.Text.Json.Serialization; + using Azure.Iot.Operations.CodeGeneration; + + public class TestError + { + [JsonPropertyName("condition")] + public string Condition { get; set; } = string.Empty; + + [JsonPropertyName("filename")] + public string Filename { get; set; } = string.Empty; + + [JsonPropertyName("line")] + public int LineNumber { get; set; } = 0; + + [JsonPropertyName("cfLine")] + public int CfLineNumber { get; set; } = 0; + + [JsonPropertyName("crossRef")] + public string CrossRef { get; set; } = string.Empty; + } +} diff --git a/codegen2/test/name-rules/DefaultSchemaNames.json b/codegen2/test/name-rules/DefaultSchemaNames.json new file mode 100644 index 0000000000..0157789b7a --- /dev/null +++ b/codegen2/test/name-rules/DefaultSchemaNames.json @@ -0,0 +1,129 @@ +{ + "suppressTitles": false, + "constantsSchema": "Constants", + "aggregateEventName": "Events", + "aggregateEventSchema": "EventCollection", + "aggregatePropName": "Properties", + "aggregatePropSchema": "PropertyCollection", + "aggregatePropWriteSchema": "PropertyUpdate", + "aggregatePropReadRespSchema": "PropertyCollectionReadRespSchema", + "aggregatePropWriteRespSchema": "PropertyCollectionWriteRespSchema", + "aggregatePropReadErrSchema": "PropertyCollectionReadError", + "aggregatePropWriteErrSchema": "PropertyCollectionWriteError", + "readRequesterBinder": "ReadRequester", + "readResponderBinder": "ReadResponder", + "writeRequesterBinder": "WriteRequester", + "writeResponderBinder": "WriteResponder", + "aggregateReadRespValueField": "_properties", + "aggregateRespErrorField": "_errors", + "eventSchema": { + "in": [ "eventName" ], + "out": "{eventName}Event", + "capitalize": true + }, + "eventValueSchema": { + "in": [ "eventName" ], + "out": "Event{eventName}Value", + "capitalize": true + }, + "eventSenderBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Sender", + "capitalize": true + }, + "eventReceiverBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Receiver", + "capitalize": true + }, + "propSchema": { + "in": [ "propName" ], + "out": "{propName}Property", + "capitalize": true + }, + "writablePropSchema": { + "in": [ "propName" ], + "out": "{propName}WritableProperty", + "capitalize": true + }, + "propReadRespSchema": { + "in": [ "propName" ], + "out": "{propName}ReadRespSchema", + "capitalize": true + }, + "propWriteRespSchema": { + "in": [ "propName" ], + "out": "{propName}WriteRespSchema", + "capitalize": true + }, + "propValueSchema": { + "in": [ "propName" ], + "out": "Property{propName}Value", + "capitalize": true + }, + "propReadActName": { + "in": [ "propName" ], + "out": "Read{propName}", + "capitalize": true + }, + "propWriteActName": { + "in": [ "propName" ], + "out": "Write{propName}", + "capitalize": true + }, + "propMaintainerBinder": { + "in": [ "propSchema" ], + "out": "{propSchema}Maintainer", + "capitalize": true + }, + "propConsumerBinder": { + "in": [ "propSchema" ], + "out": "{propSchema}Consumer", + "capitalize": true + }, + "actionInSchema": { + "in": [ "actionName" ], + "out": "{actionName}InputArguments", + "capitalize": true + }, + "actionOutSchema": { + "in": [ "actionName" ], + "out": "{actionName}OutputArguments", + "capitalize": true + }, + "actionRespSchema": { + "in": [ "actionName" ], + "out": "{actionName}ResponseSchema", + "capitalize": true + }, + "actionExecutorBinder": { + "in": [ "actionName" ], + "out": "{actionName}ActionExecutor", + "capitalize": true + }, + "actionInvokerBinder": { + "in": [ "actionName" ], + "out": "{actionName}ActionInvoker", + "capitalize": true + }, + "propReadRespErrorField": { + "in": [ "propName", "errorSchemaName" ], + "out": "_error", + "capitalize": false + }, + "propWriteRespErrorField": { + "in": [ "propName", "errorSchemaName" ], + "out": "_error", + "capitalize": false + }, + "actionRespErrorField": { + "in": [ "actionName", "errorSchemaName" ], + "out": "_error", + "capitalize": false + }, + "backupSchemaName": { + "in": [ "parentSchemaName", "childName" ], + "out": "{parentSchemaName}{childName}", + "capitalize": true + } +} diff --git a/codegen2/test/sandbox/ActionInputRefNotFound/schemas/PokeInputArguments.schema.json b/codegen2/test/sandbox/ActionInputRefNotFound/schemas/PokeInputArguments.schema.json new file mode 100644 index 0000000000..d337104721 --- /dev/null +++ b/codegen2/test/sandbox/ActionInputRefNotFound/schemas/PokeInputArguments.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "PokeInputArguments", + "type": "object", + "$ref": "../../../schemas/json-schemas/Nonexistent.json" +} diff --git a/codegen2/test/sandbox/ActionInputRefString/schemas/PokeInputArguments.schema.json b/codegen2/test/sandbox/ActionInputRefString/schemas/PokeInputArguments.schema.json new file mode 100644 index 0000000000..0941a3f88e --- /dev/null +++ b/codegen2/test/sandbox/ActionInputRefString/schemas/PokeInputArguments.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "PokeInputArguments", + "type": "object", + "$ref": "../../../schemas/json-schemas/AStringSchema.json" +} diff --git a/codegen2/test/sandbox/ActionOutputRefNotFound/schemas/PeekOutputArguments.schema.json b/codegen2/test/sandbox/ActionOutputRefNotFound/schemas/PeekOutputArguments.schema.json new file mode 100644 index 0000000000..3c798b3cdf --- /dev/null +++ b/codegen2/test/sandbox/ActionOutputRefNotFound/schemas/PeekOutputArguments.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "PeekOutputArguments", + "type": "object", + "$ref": "../../../schemas/json-schemas/Nonexistent.json" +} diff --git a/codegen2/test/sandbox/ActionOutputRefString/schemas/PeekOutputArguments.schema.json b/codegen2/test/sandbox/ActionOutputRefString/schemas/PeekOutputArguments.schema.json new file mode 100644 index 0000000000..871206c7b8 --- /dev/null +++ b/codegen2/test/sandbox/ActionOutputRefString/schemas/PeekOutputArguments.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "PeekOutputArguments", + "type": "object", + "$ref": "../../../schemas/json-schemas/AStringSchema.json" +} diff --git a/codegen2/test/sandbox/DuplicatedGeneratedNameBetweenThingAndJsonSchema/schemas/AnObjectSchema.schema.json b/codegen2/test/sandbox/DuplicatedGeneratedNameBetweenThingAndJsonSchema/schemas/AnObjectSchema.schema.json new file mode 100644 index 0000000000..0163fc7160 --- /dev/null +++ b/codegen2/test/sandbox/DuplicatedGeneratedNameBetweenThingAndJsonSchema/schemas/AnObjectSchema.schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AnObjectSchema", + "description": "Output arguments for action 'doit'", + "type": "object", + "additionalProperties": false, + "properties": { + "outVal": { + "description": "The 'outVal' Field.", + "type": "string" + } + } +} diff --git a/codegen2/test/sandbox/DuplicatedGeneratedNameBetweenThingAndJsonSchema/schemas/DoitInputArguments.schema.json b/codegen2/test/sandbox/DuplicatedGeneratedNameBetweenThingAndJsonSchema/schemas/DoitInputArguments.schema.json new file mode 100644 index 0000000000..48eeaa82f5 --- /dev/null +++ b/codegen2/test/sandbox/DuplicatedGeneratedNameBetweenThingAndJsonSchema/schemas/DoitInputArguments.schema.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "DoitInputArguments", + "type": "object", + "$ref": "../../../schemas/json-schemas/AnObjectSchema.json" +} diff --git a/codegen2/test/sandbox/EventRefNotFound/schemas/AlertEvent.schema.json b/codegen2/test/sandbox/EventRefNotFound/schemas/AlertEvent.schema.json new file mode 100644 index 0000000000..60811f462c --- /dev/null +++ b/codegen2/test/sandbox/EventRefNotFound/schemas/AlertEvent.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AlertEvent", + "description": "Container for the 'alert' Event data.", + "type": "object", + "additionalProperties": false, + "required": [ "alert" ], + "properties": { + "alert": { + "description": "The 'alert' Event data value.", + "$ref": "../../../schemas/json-schemas/Nonexistent.json" + } + } +} diff --git a/codegen2/test/sandbox/PropertyRefNotFound/schemas/AlphaProperty.schema.json b/codegen2/test/sandbox/PropertyRefNotFound/schemas/AlphaProperty.schema.json new file mode 100644 index 0000000000..281fa8cc77 --- /dev/null +++ b/codegen2/test/sandbox/PropertyRefNotFound/schemas/AlphaProperty.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AlphaProperty", + "description": "Container for the 'alpha' Property.", + "type": "object", + "additionalProperties": false, + "required": [ "alpha" ], + "properties": { + "alpha": { + "description": "The 'alpha' Property value.", + "$ref": "../../../schemas/json-schemas/Nonexistent.json" + } + } +} diff --git a/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/AlphaProperty.schema.json b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/AlphaProperty.schema.json new file mode 100644 index 0000000000..43c4fe77ce --- /dev/null +++ b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/AlphaProperty.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AlphaProperty", + "description": "Container for the 'alpha' Property.", + "type": "object", + "additionalProperties": false, + "required": [ "alpha" ], + "properties": { + "alpha": { + "description": "The 'alpha' Property value.", + "type": "string" + } + } +} diff --git a/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/AlphaWriteResponseSchema.schema.json b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/AlphaWriteResponseSchema.schema.json new file mode 100644 index 0000000000..634811bde2 --- /dev/null +++ b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/AlphaWriteResponseSchema.schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AlphaWriteResponseSchema", + "description": "Response to a 'alpha' Property write.", + "type": "object", + "additionalProperties": false, + "properties": { + "_error": { + "description": "Write error for the 'alpha' Property.", + "$ref": "Error.schema.json" + } + } +} diff --git a/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/Error.schema.json b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/Error.schema.json new file mode 100644 index 0000000000..58bb4b011c --- /dev/null +++ b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/Error.schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "Error", + "type": "object", + "additionalProperties": false, + "properties": { + "error": { + "description": "The 'error' Field.", + "type": "string" + } + } +} diff --git a/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/PropertyCollection.schema.json b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/PropertyCollection.schema.json new file mode 100644 index 0000000000..45836c6c80 --- /dev/null +++ b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/PropertyCollection.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "PropertyCollection", + "description": "Response to read of all Properties", + "type": "object", + "additionalProperties": false, + "properties": { + "_errors": { + "description": "Errors when operation fails.", + "$ref": "PropertyCollectionReadError.schema.json" + }, + "_properties": { + "description": "Properties when operation succeeds.", + "$ref": "PropertyCollection.schema.json" + } + } +} diff --git a/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/PropertyCollectionReadError.schema.json b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/PropertyCollectionReadError.schema.json new file mode 100644 index 0000000000..34bd8e88d7 --- /dev/null +++ b/codegen2/test/sandbox/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses/schemas/PropertyCollectionReadError.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "PropertyCollectionReadError", + "description": "Errors from any Property read.", + "type": "object", + "additionalProperties": false, + "properties": { + } +} diff --git a/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/AlphaProperty.schema.json b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/AlphaProperty.schema.json new file mode 100644 index 0000000000..43c4fe77ce --- /dev/null +++ b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/AlphaProperty.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AlphaProperty", + "description": "Container for the 'alpha' Property.", + "type": "object", + "additionalProperties": false, + "required": [ "alpha" ], + "properties": { + "alpha": { + "description": "The 'alpha' Property value.", + "type": "string" + } + } +} diff --git a/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/AlphaReadResponseSchema.schema.json b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/AlphaReadResponseSchema.schema.json new file mode 100644 index 0000000000..59613cbe18 --- /dev/null +++ b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/AlphaReadResponseSchema.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AlphaReadResponseSchema", + "description": "Response to a 'alpha' Property read.", + "type": "object", + "additionalProperties": false, + "required": [ ], + "properties": { + "_error": { + "description": "Read error for the 'alpha' Property.", + "$ref": "Error.schema.json" + }, + "alpha": { + "description": "The 'alpha' Property value.", + "type": "string" + } + } +} diff --git a/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/Error.schema.json b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/Error.schema.json new file mode 100644 index 0000000000..58bb4b011c --- /dev/null +++ b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/Error.schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "Error", + "type": "object", + "additionalProperties": false, + "properties": { + "error": { + "description": "The 'error' Field.", + "type": "string" + } + } +} diff --git a/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/PropertyCollection.schema.json b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/PropertyCollection.schema.json new file mode 100644 index 0000000000..bba97733d9 --- /dev/null +++ b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/PropertyCollection.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "PropertyCollection", + "description": "Values of all Properties.", + "type": "object", + "additionalProperties": false, + "required": [ "alpha" ], + "properties": { + "alpha": { + "description": "The 'alpha' Property value.", + "type": "string" + } + } +} diff --git a/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/PropertyCollectionWriteError.schema.json b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/PropertyCollectionWriteError.schema.json new file mode 100644 index 0000000000..80c8d3d45c --- /dev/null +++ b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/PropertyCollectionWriteError.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "PropertyCollectionWriteError", + "description": "Errors from any Property write.", + "type": "object", + "additionalProperties": false, + "properties": { + } +} diff --git a/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/PropertyUpdate.schema.json b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/PropertyUpdate.schema.json new file mode 100644 index 0000000000..e85c2dffbe --- /dev/null +++ b/codegen2/test/sandbox/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses/schemas/PropertyUpdate.schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "PropertyUpdate", + "description": "Response to write of multiple Properties", + "type": "object", + "additionalProperties": false, + "properties": { + "_errors": { + "description": "Errors when operation fails.", + "$ref": "PropertyCollectionWriteError.schema.json" + } + } +} diff --git a/codegen2/test/schemas/json-schemas/AStringSchema.json b/codegen2/test/schemas/json-schemas/AStringSchema.json new file mode 100644 index 0000000000..6336f9817c --- /dev/null +++ b/codegen2/test/schemas/json-schemas/AStringSchema.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AStringSchema", + "type": "string" +} diff --git a/codegen2/test/schemas/json-schemas/AnObjectSchema.json b/codegen2/test/schemas/json-schemas/AnObjectSchema.json new file mode 100644 index 0000000000..309e870032 --- /dev/null +++ b/codegen2/test/schemas/json-schemas/AnObjectSchema.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AnObjectSchema", + "type": "object", + "additionalProperties": false, + "required": [ "alpha", "beta" ], + "properties": { + "alpha": { + "type": "string" + }, + "beta": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } +} diff --git a/codegen2/test/schemas/json-schemas/AnotherObjectSchema.json b/codegen2/test/schemas/json-schemas/AnotherObjectSchema.json new file mode 100644 index 0000000000..f6b3ef1e00 --- /dev/null +++ b/codegen2/test/schemas/json-schemas/AnotherObjectSchema.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema", + "title": "AnObjectSchema", + "type": "object", + "additionalProperties": false, + "properties": { + "gamma": { + "type": "string" + } + } +} diff --git a/codegen2/test/test-cases/SampleTest.json b/codegen2/test/test-cases/SampleTest.json new file mode 100644 index 0000000000..b0d93354df --- /dev/null +++ b/codegen2/test/test-cases/SampleTest.json @@ -0,0 +1,26 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "valid/Noop.TM.json" ], + "schemas": [ ], + "typeNamer": null, + "outDir": "", + "workingDir": "", + "namespaace": "", + "sdkPath": null, + "lang": "none", + "clientOnly": false, + "serverOnly": false, + "noProj": false, + "defaultImpl": false + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "OneAction.TM.json", + "line": 32, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionContainedIn.json b/codegen2/test/test-cases/failure/ActionContainedIn.json new file mode 100644 index 0000000000..d501c4a8bd --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionContainedIn.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionContainedIn.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionContainedIn.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionContains.json b/codegen2/test/test-cases/failure/ActionContains.json new file mode 100644 index 0000000000..f2d96ee24e --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionContains.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionContains.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionContains.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionDuplicateSchemaNamesObjectsDifferentFieldValues.json b/codegen2/test/test-cases/failure/ActionDuplicateSchemaNamesObjectsDifferentFieldValues.json new file mode 100644 index 0000000000..af2841d158 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionDuplicateSchemaNamesObjectsDifferentFieldValues.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "ActionDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json", + "line": 14, + "cfLine": 23, + "crossRef": "" + }, + { + "condition": "Duplication", + "filename": "ActionDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json", + "line": 23, + "cfLine": 14, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionDuplicateSchemaNamesObjectsDifferentFields.json b/codegen2/test/test-cases/failure/ActionDuplicateSchemaNamesObjectsDifferentFields.json new file mode 100644 index 0000000000..cd463815ad --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionDuplicateSchemaNamesObjectsDifferentFields.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionDuplicateSchemaNamesObjectsDifferentFields.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "ActionDuplicateSchemaNamesObjectsDifferentFields.TM.json", + "line": 14, + "cfLine": 19, + "crossRef": "" + }, + { + "condition": "Duplication", + "filename": "ActionDuplicateSchemaNamesObjectsDifferentFields.TM.json", + "line": 23, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseContentTypeEmpty.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseContentTypeEmpty.json new file mode 100644 index 0000000000..36167172ce --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseContentTypeEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseContentTypeEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ActionFormAdditionalResponseContentTypeEmpty.TM.json", + "line": 36, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseContentTypeText.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseContentTypeText.json new file mode 100644 index 0000000000..15e32809c1 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseContentTypeText.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseContentTypeText.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "ActionFormAdditionalResponseContentTypeText.TM.json", + "line": 36, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoMatchingSchemaDefinition.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoMatchingSchemaDefinition.json new file mode 100644 index 0000000000..760c68a241 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoMatchingSchemaDefinition.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseNoMatchingSchemaDefinition.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "ActionFormAdditionalResponseNoMatchingSchemaDefinition.TM.json", + "line": 28, + "cfLine": 8, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoSchema.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoSchema.json new file mode 100644 index 0000000000..26dee01b5f --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoSchema.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseNoSchema.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "ActionFormAdditionalResponseNoSchema.TM.json", + "line": 34, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoSchemaDefinitions.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoSchemaDefinitions.json new file mode 100644 index 0000000000..a8888f68f6 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoSchemaDefinitions.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseNoSchemaDefinitions.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "ActionFormAdditionalResponseNoSchemaDefinitions.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoSuccess.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoSuccess.json new file mode 100644 index 0000000000..41baf2e260 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseNoSuccess.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseNoSuccess.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "ActionFormAdditionalResponseNoSuccess.TM.json", + "line": 33, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSchemaDefinitionMap.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSchemaDefinitionMap.json new file mode 100644 index 0000000000..48483ef666 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSchemaDefinitionMap.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseSchemaDefinitionMap.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionFormAdditionalResponseSchemaDefinitionMap.TM.json", + "line": 33, + "cfLine": 16, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSchemaDefinitionNotObject.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSchemaDefinitionNotObject.json new file mode 100644 index 0000000000..d636a0f07f --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSchemaDefinitionNotObject.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseSchemaDefinitionNotObject.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionFormAdditionalResponseSchemaDefinitionNotObject.TM.json", + "line": 35, + "cfLine": 9, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSchemaEmpty.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSchemaEmpty.json new file mode 100644 index 0000000000..74bf7b9c01 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSchemaEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseSchemaEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ActionFormAdditionalResponseSchemaEmpty.TM.json", + "line": 36, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSuccessTrue.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSuccessTrue.json new file mode 100644 index 0000000000..a3dc6e06bc --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseSuccessTrue.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseSuccessTrue.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "ActionFormAdditionalResponseSuccessTrue.TM.json", + "line": 34, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponseUnsupportedProperty.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseUnsupportedProperty.json new file mode 100644 index 0000000000..ec0d87889d --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponseUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponseUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionFormAdditionalResponseUnsupportedProperty.TM.json", + "line": 37, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormAdditionalResponsesMultiple.json b/codegen2/test/test-cases/failure/ActionFormAdditionalResponsesMultiple.json new file mode 100644 index 0000000000..14bf5e45b6 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormAdditionalResponsesMultiple.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormAdditionalResponsesMultiple.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementsPlural", + "filename": "ActionFormAdditionalResponsesMultiple.TM.json", + "line": 33, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormContentTypeText.json b/codegen2/test/test-cases/failure/ActionFormContentTypeText.json new file mode 100644 index 0000000000..c070918fc4 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormContentTypeText.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormContentTypeText.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "ActionFormContentTypeText.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderCodeEmpty.json b/codegen2/test/test-cases/failure/ActionFormHeaderCodeEmpty.json new file mode 100644 index 0000000000..f7875e673a --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderCodeEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderCodeEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ActionFormHeaderCodeEmpty.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderCodeNoMatchingSchemaDefinition.json b/codegen2/test/test-cases/failure/ActionFormHeaderCodeNoMatchingSchemaDefinition.json new file mode 100644 index 0000000000..0a0732e193 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderCodeNoMatchingSchemaDefinition.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderCodeNoMatchingSchemaDefinition.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "ActionFormHeaderCodeNoMatchingSchemaDefinition.TM.json", + "line": 20, + "cfLine": 8, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderCodeNoSchemaDefinitions.json b/codegen2/test/test-cases/failure/ActionFormHeaderCodeNoSchemaDefinitions.json new file mode 100644 index 0000000000..d0d6e5b8da --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderCodeNoSchemaDefinitions.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderCodeNoSchemaDefinitions.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "ActionFormHeaderCodeNoSchemaDefinitions.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderCodeSchemaDefinitionNotEnum.json b/codegen2/test/test-cases/failure/ActionFormHeaderCodeSchemaDefinitionNotEnum.json new file mode 100644 index 0000000000..e942cf1207 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderCodeSchemaDefinitionNotEnum.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderCodeSchemaDefinitionNotEnum.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionFormHeaderCodeSchemaDefinitionNotEnum.TM.json", + "line": 20, + "cfLine": 9, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoContentTypeEmpty.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoContentTypeEmpty.json new file mode 100644 index 0000000000..015cfbef0f --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoContentTypeEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoContentTypeEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ActionFormHeaderInfoContentTypeEmpty.TM.json", + "line": 36, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoContentTypeNotJson.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoContentTypeNotJson.json new file mode 100644 index 0000000000..101cee5505 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoContentTypeNotJson.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoContentTypeNotJson.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "ActionFormHeaderInfoContentTypeNotJson.TM.json", + "line": 36, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoContentType.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoContentType.json new file mode 100644 index 0000000000..bbd6a0a4c1 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoContentType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoNoContentType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "ActionFormHeaderInfoNoContentType.TM.json", + "line": 33, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoMatchingSchemaDefinition.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoMatchingSchemaDefinition.json new file mode 100644 index 0000000000..8d7c370f91 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoMatchingSchemaDefinition.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoNoMatchingSchemaDefinition.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "ActionFormHeaderInfoNoMatchingSchemaDefinition.TM.json", + "line": 28, + "cfLine": 8, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoSchema.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoSchema.json new file mode 100644 index 0000000000..1dc47e9aef --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoSchema.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoNoSchema.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "ActionFormHeaderInfoNoSchema.TM.json", + "line": 34, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoSchemaDefinitions.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoSchemaDefinitions.json new file mode 100644 index 0000000000..1fbe3bc8ae --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoNoSchemaDefinitions.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoNoSchemaDefinitions.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "ActionFormHeaderInfoNoSchemaDefinitions.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoSchemaDefinitionMap.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoSchemaDefinitionMap.json new file mode 100644 index 0000000000..1548a28317 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoSchemaDefinitionMap.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoSchemaDefinitionMap.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionFormHeaderInfoSchemaDefinitionMap.TM.json", + "line": 33, + "cfLine": 16, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoSchemaDefinitionNotObject.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoSchemaDefinitionNotObject.json new file mode 100644 index 0000000000..20638fcb47 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoSchemaDefinitionNotObject.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoSchemaDefinitionNotObject.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionFormHeaderInfoSchemaDefinitionNotObject.TM.json", + "line": 35, + "cfLine": 9, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoSchemaEmpty.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoSchemaEmpty.json new file mode 100644 index 0000000000..cba7ecec85 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoSchemaEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoSchemaEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ActionFormHeaderInfoSchemaEmpty.TM.json", + "line": 36, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoSuccessTrue.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoSuccessTrue.json new file mode 100644 index 0000000000..fce9d098d2 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoSuccessTrue.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoSuccessTrue.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "ActionFormHeaderInfoSuccessTrue.TM.json", + "line": 34, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfoUnsupportedProperty.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfoUnsupportedProperty.json new file mode 100644 index 0000000000..f39952762e --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfoUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfoUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionFormHeaderInfoUnsupportedProperty.TM.json", + "line": 37, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormHeaderInfosMultiple.json b/codegen2/test/test-cases/failure/ActionFormHeaderInfosMultiple.json new file mode 100644 index 0000000000..a62bf5d2ee --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormHeaderInfosMultiple.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormHeaderInfosMultiple.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementsPlural", + "filename": "ActionFormHeaderInfosMultiple.TM.json", + "line": 33, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormOpInvokeDuplicate.json b/codegen2/test/test-cases/failure/ActionFormOpInvokeDuplicate.json new file mode 100644 index 0000000000..33091668ea --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormOpInvokeDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormOpInvokeDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "ActionFormOpInvokeDuplicate.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormOpRead.json b/codegen2/test/test-cases/failure/ActionFormOpRead.json new file mode 100644 index 0000000000..a209e6367f --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormOpRead.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/ActionFormOpRead.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "ActionFormOpRead.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormOpSub.json b/codegen2/test/test-cases/failure/ActionFormOpSub.json new file mode 100644 index 0000000000..0a2c48d713 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormOpSub.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/ActionFormOpSub.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "ActionFormOpSub.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormOpWrite.json b/codegen2/test/test-cases/failure/ActionFormOpWrite.json new file mode 100644 index 0000000000..c8be572390 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormOpWrite.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/ActionFormOpWrite.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "ActionFormOpWrite.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormServiceGroupEmpty.json b/codegen2/test/test-cases/failure/ActionFormServiceGroupEmpty.json new file mode 100644 index 0000000000..0980317341 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormServiceGroupEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormServiceGroupEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ActionFormServiceGroupEmpty.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormTopicNoContentType.json b/codegen2/test/test-cases/failure/ActionFormTopicNoContentType.json new file mode 100644 index 0000000000..1890130db1 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormTopicNoContentType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormTopicNoContentType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionFormTopicNoContentType.TM.json", + "line": 11, + "cfLine": 12, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormTopicTokenAction.json b/codegen2/test/test-cases/failure/ActionFormTopicTokenAction.json new file mode 100644 index 0000000000..dc407455a0 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormTopicTokenAction.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormTopicTokenAction.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ActionFormTopicTokenAction.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormTopicTokenConsumerId.json b/codegen2/test/test-cases/failure/ActionFormTopicTokenConsumerId.json new file mode 100644 index 0000000000..684defffa3 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormTopicTokenConsumerId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormTopicTokenConsumerId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ActionFormTopicTokenConsumerId.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormTopicTokenMaintainerId.json b/codegen2/test/test-cases/failure/ActionFormTopicTokenMaintainerId.json new file mode 100644 index 0000000000..8ada3ef4c3 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormTopicTokenMaintainerId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormTopicTokenMaintainerId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ActionFormTopicTokenMaintainerId.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormTopicTokenSenderId.json b/codegen2/test/test-cases/failure/ActionFormTopicTokenSenderId.json new file mode 100644 index 0000000000..745dcc43e7 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormTopicTokenSenderId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormTopicTokenSenderId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ActionFormTopicTokenSenderId.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormUnsupportedProperty.json b/codegen2/test/test-cases/failure/ActionFormUnsupportedProperty.json new file mode 100644 index 0000000000..38a7828308 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionFormUnsupportedProperty.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormsEmpty.json b/codegen2/test/test-cases/failure/ActionFormsEmpty.json new file mode 100644 index 0000000000..fa82a10611 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormsEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/ActionFormsEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementMissing", + "filename": "ActionFormsEmpty.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormsMultipleContentTypes.json b/codegen2/test/test-cases/failure/ActionFormsMultipleContentTypes.json new file mode 100644 index 0000000000..15b80f95f1 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormsMultipleContentTypes.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormsMultipleContentTypes.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionFormsMultipleContentTypes.TM.json", + "line": 12, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormsOpInvokeDuplicate.json b/codegen2/test/test-cases/failure/ActionFormsOpInvokeDuplicate.json new file mode 100644 index 0000000000..07d4f8d5c7 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormsOpInvokeDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormsOpInvokeDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "ActionFormsOpInvokeDuplicate.TM.json", + "line": 14, + "cfLine": 19, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormsOplessAndInvoke.json b/codegen2/test/test-cases/failure/ActionFormsOplessAndInvoke.json new file mode 100644 index 0000000000..90fab20da2 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormsOplessAndInvoke.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormsOplessAndInvoke.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionFormsOplessAndInvoke.TM.json", + "line": 16, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionFormsOplessDuplicate.json b/codegen2/test/test-cases/failure/ActionFormsOplessDuplicate.json new file mode 100644 index 0000000000..19eeb4ca0c --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionFormsOplessDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionFormsOplessDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionFormsOplessDuplicate.TM.json", + "line": 11, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputMap.json b/codegen2/test/test-cases/failure/ActionInputMap.json new file mode 100644 index 0000000000..79cb4e6245 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputMap.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputMap.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionInputMap.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputNullAndContentTypeJson.json b/codegen2/test/test-cases/failure/ActionInputNullAndContentTypeJson.json new file mode 100644 index 0000000000..9579abc546 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputNullAndContentTypeJson.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputNullAndContentTypeJson.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionInputNullAndContentTypeJson.TM.json", + "line": 11, + "cfLine": 15, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputNumber.json b/codegen2/test/test-cases/failure/ActionInputNumber.json new file mode 100644 index 0000000000..f536e00958 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputNumber.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputNumber.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionInputNumber.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputObjectAndContentTypeCustom.json b/codegen2/test/test-cases/failure/ActionInputObjectAndContentTypeCustom.json new file mode 100644 index 0000000000..02ae3d9e66 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputObjectAndContentTypeCustom.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputObjectAndContentTypeCustom.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionInputObjectAndContentTypeCustom.TM.json", + "line": 11, + "cfLine": 20, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputObjectAndContentTypeRaw.json b/codegen2/test/test-cases/failure/ActionInputObjectAndContentTypeRaw.json new file mode 100644 index 0000000000..118ca29787 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputObjectAndContentTypeRaw.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputObjectAndContentTypeRaw.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionInputObjectAndContentTypeRaw.TM.json", + "line": 11, + "cfLine": 20, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputObjectConst.json b/codegen2/test/test-cases/failure/ActionInputObjectConst.json new file mode 100644 index 0000000000..60cf852604 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputObjectConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputObjectConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionInputObjectConst.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputObjectErrorMessage.json b/codegen2/test/test-cases/failure/ActionInputObjectErrorMessage.json new file mode 100644 index 0000000000..95e11c2fdd --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputObjectErrorMessage.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputObjectErrorMessage.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionInputObjectErrorMessage.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputRefNotFound.json b/codegen2/test/test-cases/failure/ActionInputRefNotFound.json new file mode 100644 index 0000000000..3b6b7a746b --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputRefNotFound.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputRefNotFound.TM.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "ActionInputRefNotFound.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputRefString.json b/codegen2/test/test-cases/failure/ActionInputRefString.json new file mode 100644 index 0000000000..fbaf5f18d9 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputRefString.json @@ -0,0 +1,17 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputRefString.TM.json" ], + "schemas": [ "../schemas/json-schemas/AStringSchema.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionInputRefString.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionInputString.json b/codegen2/test/test-cases/failure/ActionInputString.json new file mode 100644 index 0000000000..e81a6bc430 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionInputString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionInputString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionInputString.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionMemberOfEmpty.json b/codegen2/test/test-cases/failure/ActionMemberOfEmpty.json new file mode 100644 index 0000000000..f4ae0bba25 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionMemberOfEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionMemberOfEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ActionMemberOfEmpty.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionMemberOfWithoutAovContext.json b/codegen2/test/test-cases/failure/ActionMemberOfWithoutAovContext.json new file mode 100644 index 0000000000..19108d122f --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionMemberOfWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionMemberOfWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ActionMemberOfWithoutAovContext.TM.json", + "line": 10, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionNamespaceEmpty.json b/codegen2/test/test-cases/failure/ActionNamespaceEmpty.json new file mode 100644 index 0000000000..52e96c0137 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionNamespaceEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionNamespaceEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ActionNamespaceEmpty.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionNamespaceWithoutAovContext.json b/codegen2/test/test-cases/failure/ActionNamespaceWithoutAovContext.json new file mode 100644 index 0000000000..a06e2319b6 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionNamespaceWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionNamespaceWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ActionNamespaceWithoutAovContext.TM.json", + "line": 10, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionNoForms.json b/codegen2/test/test-cases/failure/ActionNoForms.json new file mode 100644 index 0000000000..513fd87717 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionNoForms.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionNoForms.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "ActionNoForms.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionNoFormsWithTopic.json b/codegen2/test/test-cases/failure/ActionNoFormsWithTopic.json new file mode 100644 index 0000000000..80fabb217e --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionNoFormsWithTopic.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionNoFormsWithTopic.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "ActionNoFormsWithTopic.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputMap.json b/codegen2/test/test-cases/failure/ActionOutputMap.json new file mode 100644 index 0000000000..500430207d --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputMap.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputMap.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionOutputMap.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputNullAndContentTypeJson.json b/codegen2/test/test-cases/failure/ActionOutputNullAndContentTypeJson.json new file mode 100644 index 0000000000..215f3599c5 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputNullAndContentTypeJson.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputNullAndContentTypeJson.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionOutputNullAndContentTypeJson.TM.json", + "line": 11, + "cfLine": 15, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputNumber.json b/codegen2/test/test-cases/failure/ActionOutputNumber.json new file mode 100644 index 0000000000..2b1ec43098 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputNumber.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputNumber.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionOutputNumber.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputObjectAndContentTypeCustom.json b/codegen2/test/test-cases/failure/ActionOutputObjectAndContentTypeCustom.json new file mode 100644 index 0000000000..e4d3e00f95 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputObjectAndContentTypeCustom.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputObjectAndContentTypeCustom.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionOutputObjectAndContentTypeCustom.TM.json", + "line": 11, + "cfLine": 20, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputObjectAndContentTypeRaw.json b/codegen2/test/test-cases/failure/ActionOutputObjectAndContentTypeRaw.json new file mode 100644 index 0000000000..12e2b6f356 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputObjectAndContentTypeRaw.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputObjectAndContentTypeRaw.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "ActionOutputObjectAndContentTypeRaw.TM.json", + "line": 11, + "cfLine": 20, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputObjectConst.json b/codegen2/test/test-cases/failure/ActionOutputObjectConst.json new file mode 100644 index 0000000000..a9723304f0 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputObjectConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputObjectConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionOutputObjectConst.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputObjectErrorMessage.json b/codegen2/test/test-cases/failure/ActionOutputObjectErrorMessage.json new file mode 100644 index 0000000000..45169beebf --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputObjectErrorMessage.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputObjectErrorMessage.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionOutputObjectErrorMessage.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputRefNotFound.json b/codegen2/test/test-cases/failure/ActionOutputRefNotFound.json new file mode 100644 index 0000000000..db711fa790 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputRefNotFound.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputRefNotFound.TM.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "ActionOutputRefNotFound.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputRefString.json b/codegen2/test/test-cases/failure/ActionOutputRefString.json new file mode 100644 index 0000000000..8870926819 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputRefString.json @@ -0,0 +1,17 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputRefString.TM.json" ], + "schemas": [ "../schemas/json-schemas/AStringSchema.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionOutputRefString.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionOutputString.json b/codegen2/test/test-cases/failure/ActionOutputString.json new file mode 100644 index 0000000000..96b02ec2b1 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionOutputString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionOutputString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "ActionOutputString.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionUnsupportedProperty.json b/codegen2/test/test-cases/failure/ActionUnsupportedProperty.json new file mode 100644 index 0000000000..67763c3f5e --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ActionUnsupportedProperty.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ActionsTopicDuplication.json b/codegen2/test/test-cases/failure/ActionsTopicDuplication.json new file mode 100644 index 0000000000..c41a438ad3 --- /dev/null +++ b/codegen2/test/test-cases/failure/ActionsTopicDuplication.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ActionsTopicDuplication.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "ActionsTopicDuplication.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "sample/dup/{executorId}" + }, + { + "condition": "Duplication", + "filename": "ActionsTopicDuplication.TM.json", + "line": 22, + "cfLine": 0, + "crossRef": "sample/dup/{executorId}" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ArrayKey.json b/codegen2/test/test-cases/failure/ArrayKey.json new file mode 100644 index 0000000000..16faf1b789 --- /dev/null +++ b/codegen2/test/test-cases/failure/ArrayKey.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/ArrayKey.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "ArrayKey.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/BooleanKey.json b/codegen2/test/test-cases/failure/BooleanKey.json new file mode 100644 index 0000000000..70caeed8bc --- /dev/null +++ b/codegen2/test/test-cases/failure/BooleanKey.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/BooleanKey.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "BooleanKey.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ContextAovWrongUri.json b/codegen2/test/test-cases/failure/ContextAovWrongUri.json new file mode 100644 index 0000000000..5258c04721 --- /dev/null +++ b/codegen2/test/test-cases/failure/ContextAovWrongUri.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ContextAovWrongUri.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ContextAovWrongUri.TM.json", + "line": 5, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ContextDtvWrongPrefix.json b/codegen2/test/test-cases/failure/ContextDtvWrongPrefix.json new file mode 100644 index 0000000000..5dccd3fa38 --- /dev/null +++ b/codegen2/test/test-cases/failure/ContextDtvWrongPrefix.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ContextDtvWrongPrefix.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "ContextDtvWrongPrefix.TM.json", + "line": 2, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ContextDtvWrongUri.json b/codegen2/test/test-cases/failure/ContextDtvWrongUri.json new file mode 100644 index 0000000000..a42ec314ed --- /dev/null +++ b/codegen2/test/test-cases/failure/ContextDtvWrongUri.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ContextDtvWrongUri.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ContextDtvWrongUri.TM.json", + "line": 4, + "cfLine": 0, + "crossRef": "" + }, + { + "condition": "PropertyMissing", + "filename": "ContextDtvWrongUri.TM.json", + "line": 2, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ContextMissingDtv.json b/codegen2/test/test-cases/failure/ContextMissingDtv.json new file mode 100644 index 0000000000..aeee13c476 --- /dev/null +++ b/codegen2/test/test-cases/failure/ContextMissingDtv.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ContextMissingDtv.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "ContextMissingDtv.TM.json", + "line": 2, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ContextMissingWot.json b/codegen2/test/test-cases/failure/ContextMissingWot.json new file mode 100644 index 0000000000..de498b760c --- /dev/null +++ b/codegen2/test/test-cases/failure/ContextMissingWot.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/ContextMissingWot.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "ContextMissingWot.TM.json", + "line": 2, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ContextWrongType.json b/codegen2/test/test-cases/failure/ContextWrongType.json new file mode 100644 index 0000000000..6355c5beb4 --- /dev/null +++ b/codegen2/test/test-cases/failure/ContextWrongType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/ContextWrongType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "ContextWrongType.TM.json", + "line": 2, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/DuplicateGeneratedNameAcrossThingsInOneFile.json b/codegen2/test/test-cases/failure/DuplicateGeneratedNameAcrossThingsInOneFile.json new file mode 100644 index 0000000000..530f6497ae --- /dev/null +++ b/codegen2/test/test-cases/failure/DuplicateGeneratedNameAcrossThingsInOneFile.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/DuplicateGeneratedNameAcrossThingsInOneFile.TM.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "DuplicateGeneratedNameAcrossThingsInOneFile.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "MyPropSchema" + }, + { + "condition": "Duplication", + "filename": "DuplicateGeneratedNameAcrossThingsInOneFile.TM.json", + "line": 39, + "cfLine": 0, + "crossRef": "MyPropSchema" + } + ] +} diff --git a/codegen2/test/test-cases/failure/DuplicateGeneratedNameAcrossThingsInTwoFiles.json b/codegen2/test/test-cases/failure/DuplicateGeneratedNameAcrossThingsInTwoFiles.json new file mode 100644 index 0000000000..3bbad314ad --- /dev/null +++ b/codegen2/test/test-cases/failure/DuplicateGeneratedNameAcrossThingsInTwoFiles.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "valid/ThingOneWithGeneratedNameMyPropSchema.TM.json", "valid/ThingTwoWithGeneratedNameMyPropSchema.TM.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "ThingOneWithGeneratedNameMyPropSchema.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "MyPropSchema" + }, + { + "condition": "Duplication", + "filename": "ThingTwoWithGeneratedNameMyPropSchema.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "MyPropSchema" + } + ] +} diff --git a/codegen2/test/test-cases/failure/DuplicateKey.json b/codegen2/test/test-cases/failure/DuplicateKey.json new file mode 100644 index 0000000000..2b1df693d7 --- /dev/null +++ b/codegen2/test/test-cases/failure/DuplicateKey.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/DuplicateKey.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "DuplicateKey.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/DuplicateThingNameAcrossThingsInOneFile.json b/codegen2/test/test-cases/failure/DuplicateThingNameAcrossThingsInOneFile.json new file mode 100644 index 0000000000..9d80d99c48 --- /dev/null +++ b/codegen2/test/test-cases/failure/DuplicateThingNameAcrossThingsInOneFile.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/DuplicateThingNameAcrossThingsInOneFile.TM.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "DuplicateThingNameAcrossThingsInOneFile.TM.json", + "line": 8, + "cfLine": 0, + "crossRef": "MyThing" + }, + { + "condition": "Duplication", + "filename": "DuplicateThingNameAcrossThingsInOneFile.TM.json", + "line": 30, + "cfLine": 0, + "crossRef": "MyThing" + } + ] +} diff --git a/codegen2/test/test-cases/failure/DuplicateThingNameAcrossThingsInTwoFiles.json b/codegen2/test/test-cases/failure/DuplicateThingNameAcrossThingsInTwoFiles.json new file mode 100644 index 0000000000..e29404e341 --- /dev/null +++ b/codegen2/test/test-cases/failure/DuplicateThingNameAcrossThingsInTwoFiles.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "valid/ThingOneNamedMyThing.TM.json", "valid/ThingTwoNamedMyThing.TM.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "ThingOneNamedMyThing.TM.json", + "line": 7, + "cfLine": 0, + "crossRef": "MyThing" + }, + { + "condition": "Duplication", + "filename": "ThingTwoNamedMyThing.TM.json", + "line": 7, + "cfLine": 0, + "crossRef": "MyThing" + } + ] +} diff --git a/codegen2/test/test-cases/failure/DuplicatedGeneratedNameAcrossJsonSchemas.json b/codegen2/test/test-cases/failure/DuplicatedGeneratedNameAcrossJsonSchemas.json new file mode 100644 index 0000000000..4d3144dad0 --- /dev/null +++ b/codegen2/test/test-cases/failure/DuplicatedGeneratedNameAcrossJsonSchemas.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "schemas": [ "../schemas/json-schemas/AnObjectSchema.json", "../schemas/json-schemas/AnotherObjectSchema.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "AnObjectSchema.json", + "line": 1, + "cfLine": 0, + "crossRef": "AnObjectSchema" + }, + { + "condition": "Duplication", + "filename": "AnotherObjectSchema.json", + "line": 1, + "cfLine": 0, + "crossRef": "AnObjectSchema" + } + ] +} diff --git a/codegen2/test/test-cases/failure/DuplicatedGeneratedNameBetweenThingAndJsonSchema.json b/codegen2/test/test-cases/failure/DuplicatedGeneratedNameBetweenThingAndJsonSchema.json new file mode 100644 index 0000000000..831d7c4817 --- /dev/null +++ b/codegen2/test/test-cases/failure/DuplicatedGeneratedNameBetweenThingAndJsonSchema.json @@ -0,0 +1,24 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/DuplicatedGeneratedNameBetweenThingAndJsonSchema.TM.json" ], + "schemas": [ "../schemas/json-schemas/AnObjectSchema.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "DuplicatedGeneratedNameBetweenThingAndJsonSchema.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "AnObjectSchema" + }, + { + "condition": "Duplication", + "filename": "AnObjectSchema.json", + "line": 1, + "cfLine": 0, + "crossRef": "AnObjectSchema" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventBooleanConst.json b/codegen2/test/test-cases/failure/EventBooleanConst.json new file mode 100644 index 0000000000..c6f97c5218 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventBooleanConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventBooleanConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventBooleanConst.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventContainedInEmpty.json b/codegen2/test/test-cases/failure/EventContainedInEmpty.json new file mode 100644 index 0000000000..e8b36134bb --- /dev/null +++ b/codegen2/test/test-cases/failure/EventContainedInEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventContainedInEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "EventContainedInEmpty.TM.json", + "line": 30, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventContainedInEventContainsOther.json b/codegen2/test/test-cases/failure/EventContainedInEventContainsOther.json new file mode 100644 index 0000000000..c9608c3ee2 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventContainedInEventContainsOther.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventContainedInEventContainsOther.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "EventContainedInEventContainsOther.TM.json", + "line": 31, + "cfLine": 18, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventContainedInNoEvent.json b/codegen2/test/test-cases/failure/EventContainedInNoEvent.json new file mode 100644 index 0000000000..56cdf4dc38 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventContainedInNoEvent.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventContainedInNoEvent.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "EventContainedInNoEvent.TM.json", + "line": 30, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventContainedInWithoutAovContext.json b/codegen2/test/test-cases/failure/EventContainedInWithoutAovContext.json new file mode 100644 index 0000000000..36fff7619e --- /dev/null +++ b/codegen2/test/test-cases/failure/EventContainedInWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventContainedInWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "EventContainedInWithoutAovContext.TM.json", + "line": 29, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventContainsEmpty.json b/codegen2/test/test-cases/failure/EventContainsEmpty.json new file mode 100644 index 0000000000..0bc014fcbe --- /dev/null +++ b/codegen2/test/test-cases/failure/EventContainsEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventContainsEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementMissing", + "filename": "EventContainsEmpty.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventContainsEventContainedInOther.json b/codegen2/test/test-cases/failure/EventContainsEventContainedInOther.json new file mode 100644 index 0000000000..24c81ce847 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventContainsEventContainedInOther.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventContainsEventContainedInOther.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "EventContainsEventContainedInOther.TM.json", + "line": 18, + "cfLine": 31, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventContainsNoEvent.json b/codegen2/test/test-cases/failure/EventContainsNoEvent.json new file mode 100644 index 0000000000..50d651c4a0 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventContainsNoEvent.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventContainsNoEvent.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "EventContainsNoEvent.TM.json", + "line": 18, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventContainsWithoutAovContext.json b/codegen2/test/test-cases/failure/EventContainsWithoutAovContext.json new file mode 100644 index 0000000000..a768526aa2 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventContainsWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventContainsWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "EventContainsWithoutAovContext.TM.json", + "line": 17, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesDifferentTypes.json b/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesDifferentTypes.json new file mode 100644 index 0000000000..d3ad9731c1 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesDifferentTypes.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventDuplicateSchemaNamesDifferentTypes.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "EventDuplicateSchemaNamesDifferentTypes.TM.json", + "line": 14, + "cfLine": 32, + "crossRef": "" + }, + { + "condition": "Duplication", + "filename": "EventDuplicateSchemaNamesDifferentTypes.TM.json", + "line": 32, + "cfLine": 14, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesEnumsDifferentValues.json b/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesEnumsDifferentValues.json new file mode 100644 index 0000000000..6f13cb360a --- /dev/null +++ b/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesEnumsDifferentValues.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventDuplicateSchemaNamesEnumsDifferentValues.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "EventDuplicateSchemaNamesEnumsDifferentValues.TM.json", + "line": 14, + "cfLine": 32, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesObjectsDifferentFieldValues.json b/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesObjectsDifferentFieldValues.json new file mode 100644 index 0000000000..6fdf10a64a --- /dev/null +++ b/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesObjectsDifferentFieldValues.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "EventDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json", + "line": 18, + "cfLine": 36, + "crossRef": "" + }, + { + "condition": "Duplication", + "filename": "EventDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json", + "line": 36, + "cfLine": 18, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesObjectsDifferentFields.json b/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesObjectsDifferentFields.json new file mode 100644 index 0000000000..184267d0ac --- /dev/null +++ b/codegen2/test/test-cases/failure/EventDuplicateSchemaNamesObjectsDifferentFields.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventDuplicateSchemaNamesObjectsDifferentFields.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "EventDuplicateSchemaNamesObjectsDifferentFields.TM.json", + "line": 18, + "cfLine": 32, + "crossRef": "" + }, + { + "condition": "Duplication", + "filename": "EventDuplicateSchemaNamesObjectsDifferentFields.TM.json", + "line": 36, + "cfLine": 14, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormAdditionalResponses.json b/codegen2/test/test-cases/failure/EventFormAdditionalResponses.json new file mode 100644 index 0000000000..b3086235ba --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormAdditionalResponses.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormAdditionalResponses.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventFormAdditionalResponses.TM.json", + "line": 32, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormContentTypeText.json b/codegen2/test/test-cases/failure/EventFormContentTypeText.json new file mode 100644 index 0000000000..e34f264f83 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormContentTypeText.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormContentTypeText.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "EventFormContentTypeText.TM.json", + "line": 19, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormHeaderCode.json b/codegen2/test/test-cases/failure/EventFormHeaderCode.json new file mode 100644 index 0000000000..74bb6676be --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormHeaderCode.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormHeaderCode.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventFormHeaderCode.TM.json", + "line": 31, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormHeaderInfo.json b/codegen2/test/test-cases/failure/EventFormHeaderInfo.json new file mode 100644 index 0000000000..4f4e688b87 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormHeaderInfo.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormHeaderInfo.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventFormHeaderInfo.TM.json", + "line": 32, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormOpInvoke.json b/codegen2/test/test-cases/failure/EventFormOpInvoke.json new file mode 100644 index 0000000000..2d7e72725d --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormOpInvoke.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/EventFormOpInvoke.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "EventFormOpInvoke.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormOpRead.json b/codegen2/test/test-cases/failure/EventFormOpRead.json new file mode 100644 index 0000000000..2a4284b94c --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormOpRead.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/EventFormOpRead.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "EventFormOpRead.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormOpSubAll.json b/codegen2/test/test-cases/failure/EventFormOpSubAll.json new file mode 100644 index 0000000000..87c4ffba16 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormOpSubAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/EventFormOpSubAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "EventFormOpSubAll.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormOpSubDuplicate.json b/codegen2/test/test-cases/failure/EventFormOpSubDuplicate.json new file mode 100644 index 0000000000..170ce1c5c4 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormOpSubDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormOpSubDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "EventFormOpSubDuplicate.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormOpWrite.json b/codegen2/test/test-cases/failure/EventFormOpWrite.json new file mode 100644 index 0000000000..ffb891bc4d --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormOpWrite.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/EventFormOpWrite.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "EventFormOpWrite.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormServiceGroupEmpty.json b/codegen2/test/test-cases/failure/EventFormServiceGroupEmpty.json new file mode 100644 index 0000000000..58bf360100 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormServiceGroupEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormServiceGroupEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "EventFormServiceGroupEmpty.TM.json", + "line": 22, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormTopicNoContentType.json b/codegen2/test/test-cases/failure/EventFormTopicNoContentType.json new file mode 100644 index 0000000000..364e760c56 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormTopicNoContentType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormTopicNoContentType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "EventFormTopicNoContentType.TM.json", + "line": 18, + "cfLine": 19, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormTopicTokenAction.json b/codegen2/test/test-cases/failure/EventFormTopicTokenAction.json new file mode 100644 index 0000000000..0204e11707 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormTopicTokenAction.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormTopicTokenAction.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "EventFormTopicTokenAction.TM.json", + "line": 20, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormTopicTokenConsumerId.json b/codegen2/test/test-cases/failure/EventFormTopicTokenConsumerId.json new file mode 100644 index 0000000000..d5c3d3bc6a --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormTopicTokenConsumerId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormTopicTokenConsumerId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "EventFormTopicTokenConsumerId.TM.json", + "line": 20, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormTopicTokenExecutorId.json b/codegen2/test/test-cases/failure/EventFormTopicTokenExecutorId.json new file mode 100644 index 0000000000..296dafe055 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormTopicTokenExecutorId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormTopicTokenExecutorId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "EventFormTopicTokenExecutorId.TM.json", + "line": 20, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormTopicTokenInvokerId.json b/codegen2/test/test-cases/failure/EventFormTopicTokenInvokerId.json new file mode 100644 index 0000000000..2df4ec6212 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormTopicTokenInvokerId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormTopicTokenInvokerId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "EventFormTopicTokenInvokerId.TM.json", + "line": 20, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormTopicTokenMaintainerId.json b/codegen2/test/test-cases/failure/EventFormTopicTokenMaintainerId.json new file mode 100644 index 0000000000..8ee77e60f7 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormTopicTokenMaintainerId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormTopicTokenMaintainerId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "EventFormTopicTokenMaintainerId.TM.json", + "line": 20, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormUnsupportedProperty.json b/codegen2/test/test-cases/failure/EventFormUnsupportedProperty.json new file mode 100644 index 0000000000..71e081ebd4 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventFormUnsupportedProperty.TM.json", + "line": 22, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormsEmpty.json b/codegen2/test/test-cases/failure/EventFormsEmpty.json new file mode 100644 index 0000000000..eb3a116a83 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormsEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/EventFormsEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementMissing", + "filename": "EventFormsEmpty.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormsOpSubDuplicate.json b/codegen2/test/test-cases/failure/EventFormsOpSubDuplicate.json new file mode 100644 index 0000000000..d82580e1b8 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormsOpSubDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormsOpSubDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "EventFormsOpSubDuplicate.TM.json", + "line": 21, + "cfLine": 26, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormsOplessAndSub.json b/codegen2/test/test-cases/failure/EventFormsOplessAndSub.json new file mode 100644 index 0000000000..5514c78359 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormsOplessAndSub.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormsOplessAndSub.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "EventFormsOplessAndSub.TM.json", + "line": 23, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventFormsOplessDuplicate.json b/codegen2/test/test-cases/failure/EventFormsOplessDuplicate.json new file mode 100644 index 0000000000..165008347a --- /dev/null +++ b/codegen2/test/test-cases/failure/EventFormsOplessDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventFormsOplessDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "EventFormsOplessDuplicate.TM.json", + "line": 18, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventIntegerConst.json b/codegen2/test/test-cases/failure/EventIntegerConst.json new file mode 100644 index 0000000000..b40402af5b --- /dev/null +++ b/codegen2/test/test-cases/failure/EventIntegerConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventIntegerConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventIntegerConst.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventMemberOf.json b/codegen2/test/test-cases/failure/EventMemberOf.json new file mode 100644 index 0000000000..36faad93da --- /dev/null +++ b/codegen2/test/test-cases/failure/EventMemberOf.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventMemberOf.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventMemberOf.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventNamespaceEmpty.json b/codegen2/test/test-cases/failure/EventNamespaceEmpty.json new file mode 100644 index 0000000000..b92bdf833c --- /dev/null +++ b/codegen2/test/test-cases/failure/EventNamespaceEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventNamespaceEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "EventNamespaceEmpty.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventNamespaceWithoutAovContext.json b/codegen2/test/test-cases/failure/EventNamespaceWithoutAovContext.json new file mode 100644 index 0000000000..c05df5a972 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventNamespaceWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventNamespaceWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "EventNamespaceWithoutAovContext.TM.json", + "line": 17, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventNoForms.json b/codegen2/test/test-cases/failure/EventNoForms.json new file mode 100644 index 0000000000..163d4d179a --- /dev/null +++ b/codegen2/test/test-cases/failure/EventNoForms.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventNoForms.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "EventNoForms.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventNoFormsWithTopicAndNoRootFormSubAll.json b/codegen2/test/test-cases/failure/EventNoFormsWithTopicAndNoRootFormSubAll.json new file mode 100644 index 0000000000..1b54db46bf --- /dev/null +++ b/codegen2/test/test-cases/failure/EventNoFormsWithTopicAndNoRootFormSubAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventNoFormsWithTopicAndNoRootFormSubAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "EventNoFormsWithTopicAndNoRootFormSubAll.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventNullAndContentTypeJson.json b/codegen2/test/test-cases/failure/EventNullAndContentTypeJson.json new file mode 100644 index 0000000000..a070aed8a2 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventNullAndContentTypeJson.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventNullAndContentTypeJson.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "EventNullAndContentTypeJson.TM.json", + "line": 15, + "cfLine": 19, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventNumberConst.json b/codegen2/test/test-cases/failure/EventNumberConst.json new file mode 100644 index 0000000000..16100b8535 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventNumberConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventNumberConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventNumberConst.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventObjectAndContentTypeCustom.json b/codegen2/test/test-cases/failure/EventObjectAndContentTypeCustom.json new file mode 100644 index 0000000000..4cedf60cb2 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventObjectAndContentTypeCustom.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventObjectAndContentTypeCustom.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "EventObjectAndContentTypeCustom.TM.json", + "line": 15, + "cfLine": 24, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventObjectAndContentTypeRaw.json b/codegen2/test/test-cases/failure/EventObjectAndContentTypeRaw.json new file mode 100644 index 0000000000..0be0cdfc62 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventObjectAndContentTypeRaw.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventObjectAndContentTypeRaw.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "EventObjectAndContentTypeRaw.TM.json", + "line": 15, + "cfLine": 24, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventObjectConst.json b/codegen2/test/test-cases/failure/EventObjectConst.json new file mode 100644 index 0000000000..d380968fa7 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventObjectConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventObjectConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventObjectConst.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventObjectErrorMessage.json b/codegen2/test/test-cases/failure/EventObjectErrorMessage.json new file mode 100644 index 0000000000..9b53472212 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventObjectErrorMessage.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventObjectErrorMessage.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventObjectErrorMessage.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventRefNotFound.json b/codegen2/test/test-cases/failure/EventRefNotFound.json new file mode 100644 index 0000000000..9eb6245124 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventRefNotFound.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventRefNotFound.TM.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "EventRefNotFound.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventStringConst.json b/codegen2/test/test-cases/failure/EventStringConst.json new file mode 100644 index 0000000000..76867e18f9 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventStringConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventStringConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventStringConst.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventUnsupportedProperty.json b/codegen2/test/test-cases/failure/EventUnsupportedProperty.json new file mode 100644 index 0000000000..e855325227 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "EventUnsupportedProperty.TM.json", + "line": 24, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/EventsTopicDuplication.json b/codegen2/test/test-cases/failure/EventsTopicDuplication.json new file mode 100644 index 0000000000..bbed9bf662 --- /dev/null +++ b/codegen2/test/test-cases/failure/EventsTopicDuplication.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/EventsTopicDuplication.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "EventsTopicDuplication.TM.json", + "line": 20, + "cfLine": 0, + "crossRef": "sample/dup/TestThing" + }, + { + "condition": "Duplication", + "filename": "EventsTopicDuplication.TM.json", + "line": 32, + "cfLine": 0, + "crossRef": "sample/dup/TestThing" + } + ] +} diff --git a/codegen2/test/test-cases/failure/IntegerConstDecimalPlaces.json b/codegen2/test/test-cases/failure/IntegerConstDecimalPlaces.json new file mode 100644 index 0000000000..ba88983d97 --- /dev/null +++ b/codegen2/test/test-cases/failure/IntegerConstDecimalPlaces.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/IntegerConstDecimalPlaces.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "IntegerConstDecimalPlaces.TM.json", + "line": 13, + "cfLine": 12, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/IntegerConstScaleFactor.json b/codegen2/test/test-cases/failure/IntegerConstScaleFactor.json new file mode 100644 index 0000000000..9b3097459e --- /dev/null +++ b/codegen2/test/test-cases/failure/IntegerConstScaleFactor.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/IntegerConstScaleFactor.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "IntegerConstScaleFactor.TM.json", + "line": 13, + "cfLine": 12, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/IntegerDecimalPlacesNotInteger.json b/codegen2/test/test-cases/failure/IntegerDecimalPlacesNotInteger.json new file mode 100644 index 0000000000..55acd87eae --- /dev/null +++ b/codegen2/test/test-cases/failure/IntegerDecimalPlacesNotInteger.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/IntegerDecimalPlacesNotInteger.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "IntegerDecimalPlacesNotInteger.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/IntegerDecimalPlacesWithoutAovContext.json b/codegen2/test/test-cases/failure/IntegerDecimalPlacesWithoutAovContext.json new file mode 100644 index 0000000000..7b72f4f9f1 --- /dev/null +++ b/codegen2/test/test-cases/failure/IntegerDecimalPlacesWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/IntegerDecimalPlacesWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "IntegerDecimalPlacesWithoutAovContext.TM.json", + "line": 13, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/IntegerScaleFactorNotInteger.json b/codegen2/test/test-cases/failure/IntegerScaleFactorNotInteger.json new file mode 100644 index 0000000000..7acb327dd1 --- /dev/null +++ b/codegen2/test/test-cases/failure/IntegerScaleFactorNotInteger.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/IntegerScaleFactorNotInteger.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "IntegerScaleFactorNotInteger.TM.json", + "line": 14, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/IntegerScaleFactorWithoutAovContext.json b/codegen2/test/test-cases/failure/IntegerScaleFactorWithoutAovContext.json new file mode 100644 index 0000000000..3ec2eca4dd --- /dev/null +++ b/codegen2/test/test-cases/failure/IntegerScaleFactorWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/IntegerScaleFactorWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "IntegerScaleFactorWithoutAovContext.TM.json", + "line": 13, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/IsCompositeAndIsEvent.json b/codegen2/test/test-cases/failure/IsCompositeAndIsEvent.json new file mode 100644 index 0000000000..35672faabe --- /dev/null +++ b/codegen2/test/test-cases/failure/IsCompositeAndIsEvent.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/IsCompositeAndIsEvent.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "IsCompositeAndIsEvent.TM.json", + "line": 9, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/IsCompositeWithoutAovContext.json b/codegen2/test/test-cases/failure/IsCompositeWithoutAovContext.json new file mode 100644 index 0000000000..37785eb1c2 --- /dev/null +++ b/codegen2/test/test-cases/failure/IsCompositeWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/IsCompositeWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "IsCompositeWithoutAovContext.TM.json", + "line": 8, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/IsEventWithoutAovContext.json b/codegen2/test/test-cases/failure/IsEventWithoutAovContext.json new file mode 100644 index 0000000000..e037b9000e --- /dev/null +++ b/codegen2/test/test-cases/failure/IsEventWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/IsEventWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "IsEventWithoutAovContext.TM.json", + "line": 8, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkCapabilityTypeJsonNotTm.json b/codegen2/test/test-cases/failure/LinkCapabilityTypeJsonNotTm.json new file mode 100644 index 0000000000..abe6dc83e2 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkCapabilityTypeJsonNotTm.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkCapabilityTypeJsonNotTm.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "LinkCapabilityTypeJsonNotTm.TM.json", + "line": 13, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkCapabilityWithoutAovContext.json b/codegen2/test/test-cases/failure/LinkCapabilityWithoutAovContext.json new file mode 100644 index 0000000000..40cd03f15b --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkCapabilityWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkCapabilityWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "LinkCapabilityWithoutAovContext.TM.json", + "line": 10, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkComponentTypeJsonNotTm.json b/codegen2/test/test-cases/failure/LinkComponentTypeJsonNotTm.json new file mode 100644 index 0000000000..1f8fac20c1 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkComponentTypeJsonNotTm.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkComponentTypeJsonNotTm.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "LinkComponentTypeJsonNotTm.TM.json", + "line": 13, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkComponentWithoutAovContext.json b/codegen2/test/test-cases/failure/LinkComponentWithoutAovContext.json new file mode 100644 index 0000000000..0b5e35b2e3 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkComponentWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkComponentWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "LinkComponentWithoutAovContext.TM.json", + "line": 10, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkExtendsTypeJsonNotTm.json b/codegen2/test/test-cases/failure/LinkExtendsTypeJsonNotTm.json new file mode 100644 index 0000000000..aaacfd9e6e --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkExtendsTypeJsonNotTm.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkExtendsTypeJsonNotTm.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "LinkExtendsTypeJsonNotTm.TM.json", + "line": 12, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkNamingEmptyHref.json b/codegen2/test/test-cases/failure/LinkNamingEmptyHref.json new file mode 100644 index 0000000000..80f3cd1093 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkNamingEmptyHref.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkNamingEmptyHref.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "LinkNamingEmptyHref.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkNamingEmptyType.json b/codegen2/test/test-cases/failure/LinkNamingEmptyType.json new file mode 100644 index 0000000000..9f4463e84b --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkNamingEmptyType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkNamingEmptyType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "LinkNamingEmptyType.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkNamingNoHref.json b/codegen2/test/test-cases/failure/LinkNamingNoHref.json new file mode 100644 index 0000000000..0aa56a1969 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkNamingNoHref.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkNamingNoHref.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "LinkNamingNoHref.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkNamingNoType.json b/codegen2/test/test-cases/failure/LinkNamingNoType.json new file mode 100644 index 0000000000..a4a944ffee --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkNamingNoType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkNamingNoType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "LinkNamingNoType.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkNamingNonJsonFile.json b/codegen2/test/test-cases/failure/LinkNamingNonJsonFile.json new file mode 100644 index 0000000000..5a9802f863 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkNamingNonJsonFile.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkNamingNonJsonFile.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "LinkNamingNonJsonFile.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkNamingNonexistentFile.json b/codegen2/test/test-cases/failure/LinkNamingNonexistentFile.json new file mode 100644 index 0000000000..cafcdeb799 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkNamingNonexistentFile.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkNamingNonexistentFile.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "LinkNamingNonexistentFile.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkNamingTypeNotJson.json b/codegen2/test/test-cases/failure/LinkNamingTypeNotJson.json new file mode 100644 index 0000000000..ef3eca0919 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkNamingTypeNotJson.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkNamingTypeNotJson.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "LinkNamingTypeNotJson.TM.json", + "line": 12, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkNamingUnsupportedProperty.json b/codegen2/test/test-cases/failure/LinkNamingUnsupportedProperty.json new file mode 100644 index 0000000000..2bb9a92c49 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkNamingUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkNamingUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "LinkNamingUnsupportedProperty.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkReferenceRefType.json b/codegen2/test/test-cases/failure/LinkReferenceRefType.json new file mode 100644 index 0000000000..a10d0ce840 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkReferenceRefType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkReferenceRefType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "LinkReferenceRefType.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkReferenceTypeJsonNotTm.json b/codegen2/test/test-cases/failure/LinkReferenceTypeJsonNotTm.json new file mode 100644 index 0000000000..06f7515ed3 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkReferenceTypeJsonNotTm.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkReferenceTypeJsonNotTm.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "LinkReferenceTypeJsonNotTm.TM.json", + "line": 13, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkReferenceWithoutAovContext.json b/codegen2/test/test-cases/failure/LinkReferenceWithoutAovContext.json new file mode 100644 index 0000000000..1c4c56c9e6 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkReferenceWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkReferenceWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "LinkReferenceWithoutAovContext.TM.json", + "line": 10, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkTypedReferenceNoRefType.json b/codegen2/test/test-cases/failure/LinkTypedReferenceNoRefType.json new file mode 100644 index 0000000000..28c23f068f --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkTypedReferenceNoRefType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkTypedReferenceNoRefType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "LinkTypedReferenceNoRefType.TM.json", + "line": 10, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkTypedReferenceRefTypeEmpty.json b/codegen2/test/test-cases/failure/LinkTypedReferenceRefTypeEmpty.json new file mode 100644 index 0000000000..f15842b418 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkTypedReferenceRefTypeEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkTypedReferenceRefTypeEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "LinkTypedReferenceRefTypeEmpty.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkTypedReferenceTypeJsonNotTm.json b/codegen2/test/test-cases/failure/LinkTypedReferenceTypeJsonNotTm.json new file mode 100644 index 0000000000..8a1b8c7397 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkTypedReferenceTypeJsonNotTm.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkTypedReferenceTypeJsonNotTm.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "LinkTypedReferenceTypeJsonNotTm.TM.json", + "line": 14, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/LinkTypedReferenceWithoutAovContext.json b/codegen2/test/test-cases/failure/LinkTypedReferenceWithoutAovContext.json new file mode 100644 index 0000000000..2eec4a7413 --- /dev/null +++ b/codegen2/test/test-cases/failure/LinkTypedReferenceWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/LinkTypedReferenceWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "LinkTypedReferenceWithoutAovContext.TM.json", + "line": 10, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/MissingColon.json b/codegen2/test/test-cases/failure/MissingColon.json new file mode 100644 index 0000000000..4d61d15081 --- /dev/null +++ b/codegen2/test/test-cases/failure/MissingColon.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/MissingColon.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "MissingColon.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/MissingComma.json b/codegen2/test/test-cases/failure/MissingComma.json new file mode 100644 index 0000000000..4a4294d0d4 --- /dev/null +++ b/codegen2/test/test-cases/failure/MissingComma.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/MissingComma.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "MissingComma.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/MissingElement.json b/codegen2/test/test-cases/failure/MissingElement.json new file mode 100644 index 0000000000..077768d520 --- /dev/null +++ b/codegen2/test/test-cases/failure/MissingElement.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/MissingElement.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "MissingElement.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/MissingKey.json b/codegen2/test/test-cases/failure/MissingKey.json new file mode 100644 index 0000000000..3677d60641 --- /dev/null +++ b/codegen2/test/test-cases/failure/MissingKey.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/MissingKey.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "MissingKey.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/MissingValue.json b/codegen2/test/test-cases/failure/MissingValue.json new file mode 100644 index 0000000000..a8c505bb73 --- /dev/null +++ b/codegen2/test/test-cases/failure/MissingValue.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/MissingValue.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "MissingValue.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/MultipleLinksNaming.json b/codegen2/test/test-cases/failure/MultipleLinksNaming.json new file mode 100644 index 0000000000..e6ac3fe54b --- /dev/null +++ b/codegen2/test/test-cases/failure/MultipleLinksNaming.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/MultipleLinksNaming.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "MultipleLinksNaming.TM.json", + "line": 8, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NoClientOrServer.json b/codegen2/test/test-cases/failure/NoClientOrServer.json new file mode 100644 index 0000000000..920bc475eb --- /dev/null +++ b/codegen2/test/test-cases/failure/NoClientOrServer.json @@ -0,0 +1,19 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "valid/OneAction.TM.json" ], + "schemas": [], + "lang": "none", + "clientOnly": true, + "serverOnly": true + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "", + "line": 0, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NoContext.json b/codegen2/test/test-cases/failure/NoContext.json new file mode 100644 index 0000000000..32dfafac56 --- /dev/null +++ b/codegen2/test/test-cases/failure/NoContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/NoContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "NoContext.TM.json", + "line": 0, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NoThingOrSchema.json b/codegen2/test/test-cases/failure/NoThingOrSchema.json new file mode 100644 index 0000000000..32298c276e --- /dev/null +++ b/codegen2/test/test-cases/failure/NoThingOrSchema.json @@ -0,0 +1,17 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [], + "schemas": [], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementMissing", + "filename": "", + "line": 0, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NoTitle.json b/codegen2/test/test-cases/failure/NoTitle.json new file mode 100644 index 0000000000..dbfa42acef --- /dev/null +++ b/codegen2/test/test-cases/failure/NoTitle.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/NoTitle.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "NoTitle.TM.json", + "line": 0, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NoType.json b/codegen2/test/test-cases/failure/NoType.json new file mode 100644 index 0000000000..d926b8c881 --- /dev/null +++ b/codegen2/test/test-cases/failure/NoType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/NoType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "NoType.TM.json", + "line": 0, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NonexistentThing.json b/codegen2/test/test-cases/failure/NonexistentThing.json new file mode 100644 index 0000000000..190986f31c --- /dev/null +++ b/codegen2/test/test-cases/failure/NonexistentThing.json @@ -0,0 +1,17 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "nothing/Nonexistent.TM.json" ], + "schemas": [], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "", + "line": 0, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NumberConstDecimalPlaces.json b/codegen2/test/test-cases/failure/NumberConstDecimalPlaces.json new file mode 100644 index 0000000000..766e82f46f --- /dev/null +++ b/codegen2/test/test-cases/failure/NumberConstDecimalPlaces.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/NumberConstDecimalPlaces.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "NumberConstDecimalPlaces.TM.json", + "line": 13, + "cfLine": 12, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NumberConstScaleFactor.json b/codegen2/test/test-cases/failure/NumberConstScaleFactor.json new file mode 100644 index 0000000000..928e9c7ffb --- /dev/null +++ b/codegen2/test/test-cases/failure/NumberConstScaleFactor.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/NumberConstScaleFactor.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "NumberConstScaleFactor.TM.json", + "line": 13, + "cfLine": 12, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NumberDecimalPlacesNotInteger.json b/codegen2/test/test-cases/failure/NumberDecimalPlacesNotInteger.json new file mode 100644 index 0000000000..378dfdf451 --- /dev/null +++ b/codegen2/test/test-cases/failure/NumberDecimalPlacesNotInteger.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/NumberDecimalPlacesNotInteger.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "NumberDecimalPlacesNotInteger.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NumberDecimalPlacesWithoutAovContext.json b/codegen2/test/test-cases/failure/NumberDecimalPlacesWithoutAovContext.json new file mode 100644 index 0000000000..5dfe258f5e --- /dev/null +++ b/codegen2/test/test-cases/failure/NumberDecimalPlacesWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/NumberDecimalPlacesWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "NumberDecimalPlacesWithoutAovContext.TM.json", + "line": 13, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NumberScaleFactorWithoutAovContext.json b/codegen2/test/test-cases/failure/NumberScaleFactorWithoutAovContext.json new file mode 100644 index 0000000000..b970950f97 --- /dev/null +++ b/codegen2/test/test-cases/failure/NumberScaleFactorWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/NumberScaleFactorWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "NumberScaleFactorWithoutAovContext.TM.json", + "line": 13, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/NumericKey.json b/codegen2/test/test-cases/failure/NumericKey.json new file mode 100644 index 0000000000..00719604dc --- /dev/null +++ b/codegen2/test/test-cases/failure/NumericKey.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/NumericKey.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "NumericKey.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ObjectKey.json b/codegen2/test/test-cases/failure/ObjectKey.json new file mode 100644 index 0000000000..19023c37db --- /dev/null +++ b/codegen2/test/test-cases/failure/ObjectKey.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/ObjectKey.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "ObjectKey.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ObjectPropertyNamespaceEmpty.json b/codegen2/test/test-cases/failure/ObjectPropertyNamespaceEmpty.json new file mode 100644 index 0000000000..33e025b599 --- /dev/null +++ b/codegen2/test/test-cases/failure/ObjectPropertyNamespaceEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ObjectPropertyNamespaceEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ObjectPropertyNamespaceEmpty.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ObjectPropertyNamespaceWithoutAovContext.json b/codegen2/test/test-cases/failure/ObjectPropertyNamespaceWithoutAovContext.json new file mode 100644 index 0000000000..fa0452da7c --- /dev/null +++ b/codegen2/test/test-cases/failure/ObjectPropertyNamespaceWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ObjectPropertyNamespaceWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ObjectPropertyNamespaceWithoutAovContext.TM.json", + "line": 14, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ObjectTypeRefEmpty.json b/codegen2/test/test-cases/failure/ObjectTypeRefEmpty.json new file mode 100644 index 0000000000..be3b23230c --- /dev/null +++ b/codegen2/test/test-cases/failure/ObjectTypeRefEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ObjectTypeRefEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "ObjectTypeRefEmpty.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ObjectTypeRefWithoutAovContext.json b/codegen2/test/test-cases/failure/ObjectTypeRefWithoutAovContext.json new file mode 100644 index 0000000000..86e2828a35 --- /dev/null +++ b/codegen2/test/test-cases/failure/ObjectTypeRefWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ObjectTypeRefWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "ObjectTypeRefWithoutAovContext.TM.json", + "line": 11, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertiesReadTopicDuplication.json b/codegen2/test/test-cases/failure/PropertiesReadTopicDuplication.json new file mode 100644 index 0000000000..778af7bd09 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertiesReadTopicDuplication.json @@ -0,0 +1,44 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertiesReadTopicDuplication.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertiesReadTopicDuplication.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "sample/dup/{maintainerId}/read" + }, + { + "condition": "Duplication", + "filename": "PropertiesReadTopicDuplication.TM.json", + "line": 26, + "cfLine": 0, + "crossRef": "sample/dup/{maintainerId}/read" + }, + { + "condition": "Duplication", + "filename": "PropertiesReadTopicDuplication.TM.json", + "line": 37, + "cfLine": 0, + "crossRef": "sample/dup/{maintainerId}/read" + }, + { + "condition": "Duplication", + "filename": "PropertiesReadTopicDuplication.TM.json", + "line": 46, + "cfLine": 0, + "crossRef": "sample/dup/{maintainerId}/read" + }, + { + "condition": "Duplication", + "filename": "PropertiesReadTopicDuplication.TM.json", + "line": 61, + "cfLine": 0, + "crossRef": "sample/dup/{maintainerId}/read" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertiesWriteTopicDuplication.json b/codegen2/test/test-cases/failure/PropertiesWriteTopicDuplication.json new file mode 100644 index 0000000000..475f669087 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertiesWriteTopicDuplication.json @@ -0,0 +1,30 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertiesWriteTopicDuplication.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertiesWriteTopicDuplication.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "sample/dup/{maintainerId}/write" + }, + { + "condition": "Duplication", + "filename": "PropertiesWriteTopicDuplication.TM.json", + "line": 36, + "cfLine": 0, + "crossRef": "sample/dup/{maintainerId}/write" + }, + { + "condition": "Duplication", + "filename": "PropertiesWriteTopicDuplication.TM.json", + "line": 46, + "cfLine": 0, + "crossRef": "sample/dup/{maintainerId}/write" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyBooleanConst.json b/codegen2/test/test-cases/failure/PropertyBooleanConst.json new file mode 100644 index 0000000000..2e63500bde --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyBooleanConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyBooleanConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyBooleanConst.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyContainedInEmpty.json b/codegen2/test/test-cases/failure/PropertyContainedInEmpty.json new file mode 100644 index 0000000000..ea8ed4c6ea --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyContainedInEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyContainedInEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "PropertyContainedInEmpty.TM.json", + "line": 24, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyContainedInNoProperty.json b/codegen2/test/test-cases/failure/PropertyContainedInNoProperty.json new file mode 100644 index 0000000000..dc01073528 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyContainedInNoProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyContainedInNoProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "PropertyContainedInNoProperty.TM.json", + "line": 24, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyContainedInPropertyContainsOther.json b/codegen2/test/test-cases/failure/PropertyContainedInPropertyContainsOther.json new file mode 100644 index 0000000000..68be98a91d --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyContainedInPropertyContainsOther.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyContainedInPropertyContainsOther.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyContainedInPropertyContainsOther.TM.json", + "line": 25, + "cfLine": 14, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyContainedInWithoutAovContext.json b/codegen2/test/test-cases/failure/PropertyContainedInWithoutAovContext.json new file mode 100644 index 0000000000..391470f9d5 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyContainedInWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyContainedInWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "PropertyContainedInWithoutAovContext.TM.json", + "line": 23, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyContainsEmpty.json b/codegen2/test/test-cases/failure/PropertyContainsEmpty.json new file mode 100644 index 0000000000..5f20ba403b --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyContainsEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyContainsEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementMissing", + "filename": "PropertyContainsEmpty.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyContainsNoProperty.json b/codegen2/test/test-cases/failure/PropertyContainsNoProperty.json new file mode 100644 index 0000000000..07ddbd7920 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyContainsNoProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyContainsNoProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "PropertyContainsNoProperty.TM.json", + "line": 14, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyContainsPropertyContainedInOther.json b/codegen2/test/test-cases/failure/PropertyContainsPropertyContainedInOther.json new file mode 100644 index 0000000000..de78099433 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyContainsPropertyContainedInOther.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyContainsPropertyContainedInOther.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyContainsPropertyContainedInOther.TM.json", + "line": 14, + "cfLine": 25, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyContainsWithoutAovContext.json b/codegen2/test/test-cases/failure/PropertyContainsWithoutAovContext.json new file mode 100644 index 0000000000..d0542d1fe4 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyContainsWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyContainsWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "PropertyContainsWithoutAovContext.TM.json", + "line": 13, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesDifferentTypes.json b/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesDifferentTypes.json new file mode 100644 index 0000000000..70145aed12 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesDifferentTypes.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyDuplicateSchemaNamesDifferentTypes.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertyDuplicateSchemaNamesDifferentTypes.TM.json", + "line": 11, + "cfLine": 27, + "crossRef": "" + }, + { + "condition": "Duplication", + "filename": "PropertyDuplicateSchemaNamesDifferentTypes.TM.json", + "line": 27, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesEnumsDifferentValues.json b/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesEnumsDifferentValues.json new file mode 100644 index 0000000000..c4931fbbba --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesEnumsDifferentValues.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyDuplicateSchemaNamesEnumsDifferentValues.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertyDuplicateSchemaNamesEnumsDifferentValues.TM.json", + "line": 11, + "cfLine": 27, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesObjectsDifferentFieldValues.json b/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesObjectsDifferentFieldValues.json new file mode 100644 index 0000000000..a2a5750a45 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesObjectsDifferentFieldValues.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertyDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json", + "line": 15, + "cfLine": 31, + "crossRef": "" + }, + { + "condition": "Duplication", + "filename": "PropertyDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json", + "line": 31, + "cfLine": 15, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesObjectsDifferentFields.json b/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesObjectsDifferentFields.json new file mode 100644 index 0000000000..a8545c656e --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyDuplicateSchemaNamesObjectsDifferentFields.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyDuplicateSchemaNamesObjectsDifferentFields.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertyDuplicateSchemaNamesObjectsDifferentFields.TM.json", + "line": 15, + "cfLine": 27, + "crossRef": "" + }, + { + "condition": "Duplication", + "filename": "PropertyDuplicateSchemaNamesObjectsDifferentFields.TM.json", + "line": 31, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseContentTypeEmpty.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseContentTypeEmpty.json new file mode 100644 index 0000000000..ab5cfd4119 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseContentTypeEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseContentTypeEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "PropertyFormAdditionalResponseContentTypeEmpty.TM.json", + "line": 39, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseContentTypeText.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseContentTypeText.json new file mode 100644 index 0000000000..2604be1fdc --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseContentTypeText.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseContentTypeText.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "PropertyFormAdditionalResponseContentTypeText.TM.json", + "line": 32, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoMatchingSchemaDefinition.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoMatchingSchemaDefinition.json new file mode 100644 index 0000000000..3a629b7cc9 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoMatchingSchemaDefinition.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseNoMatchingSchemaDefinition.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "PropertyFormAdditionalResponseNoMatchingSchemaDefinition.TM.json", + "line": 31, + "cfLine": 8, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoSchema.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoSchema.json new file mode 100644 index 0000000000..9d90033d59 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoSchema.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseNoSchema.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "PropertyFormAdditionalResponseNoSchema.TM.json", + "line": 37, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoSchemaDefinitions.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoSchemaDefinitions.json new file mode 100644 index 0000000000..1c10cff77c --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoSchemaDefinitions.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseNoSchemaDefinitions.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "PropertyFormAdditionalResponseNoSchemaDefinitions.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoSuccess.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoSuccess.json new file mode 100644 index 0000000000..630d0983ac --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseNoSuccess.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseNoSuccess.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "PropertyFormAdditionalResponseNoSuccess.TM.json", + "line": 36, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSchemaDefinitionMap.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSchemaDefinitionMap.json new file mode 100644 index 0000000000..844405804e --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSchemaDefinitionMap.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseSchemaDefinitionMap.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "PropertyFormAdditionalResponseSchemaDefinitionMap.TM.json", + "line": 36, + "cfLine": 16, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSchemaDefinitionNotObject.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSchemaDefinitionNotObject.json new file mode 100644 index 0000000000..70d8bafc6c --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSchemaDefinitionNotObject.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseSchemaDefinitionNotObject.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "PropertyFormAdditionalResponseSchemaDefinitionNotObject.TM.json", + "line": 38, + "cfLine": 9, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSchemaEmpty.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSchemaEmpty.json new file mode 100644 index 0000000000..6a5878a09a --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSchemaEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseSchemaEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "PropertyFormAdditionalResponseSchemaEmpty.TM.json", + "line": 39, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSuccessTrue.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSuccessTrue.json new file mode 100644 index 0000000000..75a1f7ad7f --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseSuccessTrue.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseSuccessTrue.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "PropertyFormAdditionalResponseSuccessTrue.TM.json", + "line": 37, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseUnsupportedProperty.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseUnsupportedProperty.json new file mode 100644 index 0000000000..a8459e85f9 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponseUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponseUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyFormAdditionalResponseUnsupportedProperty.TM.json", + "line": 40, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormAdditionalResponsesMultiple.json b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponsesMultiple.json new file mode 100644 index 0000000000..b496b4a71e --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormAdditionalResponsesMultiple.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormAdditionalResponsesMultiple.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementsPlural", + "filename": "PropertyFormAdditionalResponsesMultiple.TM.json", + "line": 36, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormContentTypeText.json b/codegen2/test/test-cases/failure/PropertyFormContentTypeText.json new file mode 100644 index 0000000000..6c742c98bf --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormContentTypeText.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormContentTypeText.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "PropertyFormContentTypeText.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormHeaderCode.json b/codegen2/test/test-cases/failure/PropertyFormHeaderCode.json new file mode 100644 index 0000000000..63d54232ee --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormHeaderCode.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormHeaderCode.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyFormHeaderCode.TM.json", + "line": 27, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormHeaderInfo.json b/codegen2/test/test-cases/failure/PropertyFormHeaderInfo.json new file mode 100644 index 0000000000..fa94ec2556 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormHeaderInfo.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormHeaderInfo.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyFormHeaderInfo.TM.json", + "line": 28, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormOpInvoke.json b/codegen2/test/test-cases/failure/PropertyFormOpInvoke.json new file mode 100644 index 0000000000..435f7a32e7 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormOpInvoke.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/PropertyFormOpInvoke.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "PropertyFormOpInvoke.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormOpReadAll.json b/codegen2/test/test-cases/failure/PropertyFormOpReadAll.json new file mode 100644 index 0000000000..a747aa7625 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormOpReadAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/PropertyFormOpReadAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "PropertyFormOpReadAll.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormOpReadDuplicate.json b/codegen2/test/test-cases/failure/PropertyFormOpReadDuplicate.json new file mode 100644 index 0000000000..30f9386931 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormOpReadDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormOpReadDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertyFormOpReadDuplicate.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormOpSub.json b/codegen2/test/test-cases/failure/PropertyFormOpSub.json new file mode 100644 index 0000000000..771db9459e --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormOpSub.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/PropertyFormOpSub.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "PropertyFormOpSub.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormOpWriteDuplicate.json b/codegen2/test/test-cases/failure/PropertyFormOpWriteDuplicate.json new file mode 100644 index 0000000000..f772661ce9 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormOpWriteDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormOpWriteDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertyFormOpWriteDuplicate.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormOpWriteMulti.json b/codegen2/test/test-cases/failure/PropertyFormOpWriteMulti.json new file mode 100644 index 0000000000..1cccaad4e2 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormOpWriteMulti.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/PropertyFormOpWriteMulti.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "PropertyFormOpWriteMulti.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormOplessAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.json b/codegen2/test/test-cases/failure/PropertyFormOplessAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.json new file mode 100644 index 0000000000..679b0be147 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormOplessAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormOplessAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "PropertyFormOplessAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json", + "line": 31, + "cfLine": 18, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormOplessWithoutTopicAndNoRootFormReadAll.json b/codegen2/test/test-cases/failure/PropertyFormOplessWithoutTopicAndNoRootFormReadAll.json new file mode 100644 index 0000000000..d11a6f6051 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormOplessWithoutTopicAndNoRootFormReadAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormOplessWithoutTopicAndNoRootFormReadAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "PropertyFormOplessWithoutTopicAndNoRootFormReadAll.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormReadAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.json b/codegen2/test/test-cases/failure/PropertyFormReadAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.json new file mode 100644 index 0000000000..d407dd3e10 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormReadAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormReadAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "PropertyFormReadAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json", + "line": 31, + "cfLine": 19, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormReadWithoutTopicAndNoRootFormReadAll.json b/codegen2/test/test-cases/failure/PropertyFormReadWithoutTopicAndNoRootFormReadAll.json new file mode 100644 index 0000000000..3a6007013a --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormReadWithoutTopicAndNoRootFormReadAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormReadWithoutTopicAndNoRootFormReadAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "PropertyFormReadWithoutTopicAndNoRootFormReadAll.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormReadWithoutTopicWriteWithTopicAndRootFormReadAll.json b/codegen2/test/test-cases/failure/PropertyFormReadWithoutTopicWriteWithTopicAndRootFormReadAll.json new file mode 100644 index 0000000000..96241db2f5 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormReadWithoutTopicWriteWithTopicAndRootFormReadAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormReadWithoutTopicWriteWithTopicAndRootFormReadAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyFormReadWithoutTopicWriteWithTopicAndRootFormReadAll.TM.json", + "line": 25, + "cfLine": 20, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormServiceGroup.json b/codegen2/test/test-cases/failure/PropertyFormServiceGroup.json new file mode 100644 index 0000000000..c1c318ba17 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormServiceGroup.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormServiceGroup.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyFormServiceGroup.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormTopicNoContentType.json b/codegen2/test/test-cases/failure/PropertyFormTopicNoContentType.json new file mode 100644 index 0000000000..09c5d3ad29 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormTopicNoContentType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormTopicNoContentType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyFormTopicNoContentType.TM.json", + "line": 14, + "cfLine": 15, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormTopicTokenExecutorId.json b/codegen2/test/test-cases/failure/PropertyFormTopicTokenExecutorId.json new file mode 100644 index 0000000000..1330a8f961 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormTopicTokenExecutorId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormTopicTokenExecutorId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "PropertyFormTopicTokenExecutorId.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormTopicTokenInvokerId.json b/codegen2/test/test-cases/failure/PropertyFormTopicTokenInvokerId.json new file mode 100644 index 0000000000..0f33856a8a --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormTopicTokenInvokerId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormTopicTokenInvokerId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "PropertyFormTopicTokenInvokerId.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormTopicTokenNoActionWhenNoOpAndNoReadOnly.json b/codegen2/test/test-cases/failure/PropertyFormTopicTokenNoActionWhenNoOpAndNoReadOnly.json new file mode 100644 index 0000000000..f52106d714 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormTopicTokenNoActionWhenNoOpAndNoReadOnly.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormTopicTokenNoActionWhenNoOpAndNoReadOnly.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyFormTopicTokenNoActionWhenNoOpAndNoReadOnly.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormTopicTokenNoActionWhenNoOpAndReadOnlyFalse.json b/codegen2/test/test-cases/failure/PropertyFormTopicTokenNoActionWhenNoOpAndReadOnlyFalse.json new file mode 100644 index 0000000000..1251512ed1 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormTopicTokenNoActionWhenNoOpAndReadOnlyFalse.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormTopicTokenNoActionWhenNoOpAndReadOnlyFalse.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyFormTopicTokenNoActionWhenNoOpAndReadOnlyFalse.TM.json", + "line": 17, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormTopicTokenNoActionWhenReadAndWrite.json b/codegen2/test/test-cases/failure/PropertyFormTopicTokenNoActionWhenReadAndWrite.json new file mode 100644 index 0000000000..5184343d25 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormTopicTokenNoActionWhenReadAndWrite.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormTopicTokenNoActionWhenReadAndWrite.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyFormTopicTokenNoActionWhenReadAndWrite.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormTopicTokenSenderId.json b/codegen2/test/test-cases/failure/PropertyFormTopicTokenSenderId.json new file mode 100644 index 0000000000..d4d12cc9dc --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormTopicTokenSenderId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormTopicTokenSenderId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "PropertyFormTopicTokenSenderId.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormUnsupportedProperty.json b/codegen2/test/test-cases/failure/PropertyFormUnsupportedProperty.json new file mode 100644 index 0000000000..1abfc0f943 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyFormUnsupportedProperty.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormWriteAdditionalResponsesNoTopicAndNoRootFormWriteMultiWithoutAdditionalResponses.json b/codegen2/test/test-cases/failure/PropertyFormWriteAdditionalResponsesNoTopicAndNoRootFormWriteMultiWithoutAdditionalResponses.json new file mode 100644 index 0000000000..127fb03c7d --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormWriteAdditionalResponsesNoTopicAndNoRootFormWriteMultiWithoutAdditionalResponses.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormWriteAdditionalResponsesNoTopicAndNoRootFormWriteMultiWithoutAdditionalResponses.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "PropertyFormWriteAdditionalResponsesNoTopicAndNoRootFormWriteMultiWithoutAdditionalResponses.TM.json", + "line": 51, + "cfLine": 29, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormWriteWithoutTopicAndNoRootFormWriteMulti.json b/codegen2/test/test-cases/failure/PropertyFormWriteWithoutTopicAndNoRootFormWriteMulti.json new file mode 100644 index 0000000000..b4b3035428 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormWriteWithoutTopicAndNoRootFormWriteMulti.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormWriteWithoutTopicAndNoRootFormWriteMulti.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "PropertyFormWriteWithoutTopicAndNoRootFormWriteMulti.TM.json", + "line": 19, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormsEmpty.json b/codegen2/test/test-cases/failure/PropertyFormsEmpty.json new file mode 100644 index 0000000000..934ff31e4c --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormsEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/PropertyFormsEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementMissing", + "filename": "PropertyFormsEmpty.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormsOpReadDuplicate.json b/codegen2/test/test-cases/failure/PropertyFormsOpReadDuplicate.json new file mode 100644 index 0000000000..10ffbd93c3 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormsOpReadDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormsOpReadDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertyFormsOpReadDuplicate.TM.json", + "line": 17, + "cfLine": 22, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormsOpWriteDuplicate.json b/codegen2/test/test-cases/failure/PropertyFormsOpWriteDuplicate.json new file mode 100644 index 0000000000..0053001bb6 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormsOpWriteDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormsOpWriteDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "PropertyFormsOpWriteDuplicate.TM.json", + "line": 17, + "cfLine": 22, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormsOpWriteNoRead.json b/codegen2/test/test-cases/failure/PropertyFormsOpWriteNoRead.json new file mode 100644 index 0000000000..4506910ec7 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormsOpWriteNoRead.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormsOpWriteNoRead.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyFormsOpWriteNoRead.TM.json", + "line": 17, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormsOplessAndRead.json b/codegen2/test/test-cases/failure/PropertyFormsOplessAndRead.json new file mode 100644 index 0000000000..fba10f63bc --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormsOplessAndRead.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormsOplessAndRead.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyFormsOplessAndRead.TM.json", + "line": 19, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyFormsOplessDuplicate.json b/codegen2/test/test-cases/failure/PropertyFormsOplessDuplicate.json new file mode 100644 index 0000000000..ad1321b538 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyFormsOplessDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyFormsOplessDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "PropertyFormsOplessDuplicate.TM.json", + "line": 14, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyIntegerConst.json b/codegen2/test/test-cases/failure/PropertyIntegerConst.json new file mode 100644 index 0000000000..6b509781f8 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyIntegerConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyIntegerConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyIntegerConst.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyMemberOf.json b/codegen2/test/test-cases/failure/PropertyMemberOf.json new file mode 100644 index 0000000000..3ae572e53f --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyMemberOf.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyMemberOf.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyMemberOf.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyNamespaceEmpty.json b/codegen2/test/test-cases/failure/PropertyNamespaceEmpty.json new file mode 100644 index 0000000000..351aed1cba --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyNamespaceEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyNamespaceEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "PropertyNamespaceEmpty.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyNamespaceWithoutAovContext.json b/codegen2/test/test-cases/failure/PropertyNamespaceWithoutAovContext.json new file mode 100644 index 0000000000..34366b1a64 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyNamespaceWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyNamespaceWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "PropertyNamespaceWithoutAovContext.TM.json", + "line": 13, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyNoForms.json b/codegen2/test/test-cases/failure/PropertyNoForms.json new file mode 100644 index 0000000000..19e8ecb28e --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyNoForms.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyNoForms.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "PropertyNoForms.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyNumberConst.json b/codegen2/test/test-cases/failure/PropertyNumberConst.json new file mode 100644 index 0000000000..c9bc60d954 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyNumberConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyNumberConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyNumberConst.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyObjectConst.json b/codegen2/test/test-cases/failure/PropertyObjectConst.json new file mode 100644 index 0000000000..558cfde79a --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyObjectConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyObjectConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyObjectConst.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyObjectErrorMessage.json b/codegen2/test/test-cases/failure/PropertyObjectErrorMessage.json new file mode 100644 index 0000000000..0504370232 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyObjectErrorMessage.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyObjectErrorMessage.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyObjectErrorMessage.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyRefNotFound.json b/codegen2/test/test-cases/failure/PropertyRefNotFound.json new file mode 100644 index 0000000000..81e0a5d1d1 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyRefNotFound.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyRefNotFound.TM.json" ], + "lang": "csharp" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "PropertyRefNotFound.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyStringConst.json b/codegen2/test/test-cases/failure/PropertyStringConst.json new file mode 100644 index 0000000000..6fefcabf27 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyStringConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyStringConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyStringConst.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyTypeNull.json b/codegen2/test/test-cases/failure/PropertyTypeNull.json new file mode 100644 index 0000000000..9500c0a77b --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyTypeNull.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyTypeNull.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "PropertyTypeNull.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/PropertyUnsupportedProperty.json b/codegen2/test/test-cases/failure/PropertyUnsupportedProperty.json new file mode 100644 index 0000000000..3b1775e6e2 --- /dev/null +++ b/codegen2/test/test-cases/failure/PropertyUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/PropertyUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "PropertyUnsupportedProperty.TM.json", + "line": 20, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormAdditionalResponseContentTypeEmpty.json b/codegen2/test/test-cases/failure/RootFormAdditionalResponseContentTypeEmpty.json new file mode 100644 index 0000000000..a2165b5a57 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormAdditionalResponseContentTypeEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormAdditionalResponseContentTypeEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "RootFormAdditionalResponseContentTypeEmpty.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormAdditionalResponseContentTypeText.json b/codegen2/test/test-cases/failure/RootFormAdditionalResponseContentTypeText.json new file mode 100644 index 0000000000..c1aebe8e0f --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormAdditionalResponseContentTypeText.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormAdditionalResponseContentTypeText.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "RootFormAdditionalResponseContentTypeText.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormAdditionalResponseNoSuccess.json b/codegen2/test/test-cases/failure/RootFormAdditionalResponseNoSuccess.json new file mode 100644 index 0000000000..1f22fdacc8 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormAdditionalResponseNoSuccess.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormAdditionalResponseNoSuccess.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "RootFormAdditionalResponseNoSuccess.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormAdditionalResponseSchema.json b/codegen2/test/test-cases/failure/RootFormAdditionalResponseSchema.json new file mode 100644 index 0000000000..6a8a31f6f4 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormAdditionalResponseSchema.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormAdditionalResponseSchema.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "RootFormAdditionalResponseSchema.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormAdditionalResponseSuccessTrue.json b/codegen2/test/test-cases/failure/RootFormAdditionalResponseSuccessTrue.json new file mode 100644 index 0000000000..2f3712635f --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormAdditionalResponseSuccessTrue.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormAdditionalResponseSuccessTrue.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "RootFormAdditionalResponseSuccessTrue.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormAdditionalResponseUnsupportedProperty.json b/codegen2/test/test-cases/failure/RootFormAdditionalResponseUnsupportedProperty.json new file mode 100644 index 0000000000..81412c6d5a --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormAdditionalResponseUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormAdditionalResponseUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "RootFormAdditionalResponseUnsupportedProperty.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormAdditionalResponsesAndSubAll.json b/codegen2/test/test-cases/failure/RootFormAdditionalResponsesAndSubAll.json new file mode 100644 index 0000000000..0785b227c5 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormAdditionalResponsesAndSubAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormAdditionalResponsesAndSubAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "RootFormAdditionalResponsesAndSubAll.TM.json", + "line": 12, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormAdditionalResponsesMultiple.json b/codegen2/test/test-cases/failure/RootFormAdditionalResponsesMultiple.json new file mode 100644 index 0000000000..8c51a8cf5d --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormAdditionalResponsesMultiple.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormAdditionalResponsesMultiple.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementsPlural", + "filename": "RootFormAdditionalResponsesMultiple.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormContentTypeCustom.json b/codegen2/test/test-cases/failure/RootFormContentTypeCustom.json new file mode 100644 index 0000000000..6796de2aa9 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormContentTypeCustom.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormContentTypeCustom.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "RootFormContentTypeCustom.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormContentTypeRaw.json b/codegen2/test/test-cases/failure/RootFormContentTypeRaw.json new file mode 100644 index 0000000000..ef08a2fe3f --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormContentTypeRaw.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormContentTypeRaw.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "RootFormContentTypeRaw.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormContentTypeText.json b/codegen2/test/test-cases/failure/RootFormContentTypeText.json new file mode 100644 index 0000000000..c20c4a082e --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormContentTypeText.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormContentTypeText.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "RootFormContentTypeText.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormHeaderCode.json b/codegen2/test/test-cases/failure/RootFormHeaderCode.json new file mode 100644 index 0000000000..6b42fbda97 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormHeaderCode.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormHeaderCode.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "RootFormHeaderCode.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormHeaderInfo.json b/codegen2/test/test-cases/failure/RootFormHeaderInfo.json new file mode 100644 index 0000000000..21e21a3ded --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormHeaderInfo.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormHeaderInfo.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "RootFormHeaderInfo.TM.json", + "line": 21, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormNoContentType.json b/codegen2/test/test-cases/failure/RootFormNoContentType.json new file mode 100644 index 0000000000..6d5d9b7365 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormNoContentType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormNoContentType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "RootFormNoContentType.TM.json", + "line": 9, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormNoOp.json b/codegen2/test/test-cases/failure/RootFormNoOp.json new file mode 100644 index 0000000000..166fa050ce --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormNoOp.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/RootFormNoOp.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "RootFormNoOp.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormNoTopic.json b/codegen2/test/test-cases/failure/RootFormNoTopic.json new file mode 100644 index 0000000000..3fc4ae7bc7 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormNoTopic.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormNoTopic.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "RootFormNoTopic.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpArrayEmpty.json b/codegen2/test/test-cases/failure/RootFormOpArrayEmpty.json new file mode 100644 index 0000000000..de837ebdb7 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpArrayEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/RootFormOpArrayEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementMissing", + "filename": "RootFormOpArrayEmpty.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpElementEmpty.json b/codegen2/test/test-cases/failure/RootFormOpElementEmpty.json new file mode 100644 index 0000000000..aee528f464 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpElementEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/RootFormOpElementEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "RootFormOpElementEmpty.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpEmpty.json b/codegen2/test/test-cases/failure/RootFormOpEmpty.json new file mode 100644 index 0000000000..a5a30175c8 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/RootFormOpEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "RootFormOpEmpty.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpReadAllAndSubAll.json b/codegen2/test/test-cases/failure/RootFormOpReadAllAndSubAll.json new file mode 100644 index 0000000000..211d94e04d --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpReadAllAndSubAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormOpReadAllAndSubAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "RootFormOpReadAllAndSubAll.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpReadAllDuplicate.json b/codegen2/test/test-cases/failure/RootFormOpReadAllDuplicate.json new file mode 100644 index 0000000000..81abc48bc9 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpReadAllDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormOpReadAllDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "RootFormOpReadAllDuplicate.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpReadOne.json b/codegen2/test/test-cases/failure/RootFormOpReadOne.json new file mode 100644 index 0000000000..b6d254d10f --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpReadOne.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/RootFormOpReadOne.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "RootFormOpReadOne.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpSubAllDuplicate.json b/codegen2/test/test-cases/failure/RootFormOpSubAllDuplicate.json new file mode 100644 index 0000000000..2e23f88d2f --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpSubAllDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormOpSubAllDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "RootFormOpSubAllDuplicate.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpSubOne.json b/codegen2/test/test-cases/failure/RootFormOpSubOne.json new file mode 100644 index 0000000000..aee528f464 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpSubOne.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/RootFormOpElementEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "RootFormOpElementEmpty.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpWiteMultiAndSubAll.json b/codegen2/test/test-cases/failure/RootFormOpWiteMultiAndSubAll.json new file mode 100644 index 0000000000..6b27af3e83 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpWiteMultiAndSubAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormOpWiteMultiAndSubAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "RootFormOpWiteMultiAndSubAll.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpWriteMultiDuplicate.json b/codegen2/test/test-cases/failure/RootFormOpWriteMultiDuplicate.json new file mode 100644 index 0000000000..332a07a471 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpWriteMultiDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormOpWriteMultiDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "RootFormOpWriteMultiDuplicate.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormOpWriteOne.json b/codegen2/test/test-cases/failure/RootFormOpWriteOne.json new file mode 100644 index 0000000000..aee528f464 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormOpWriteOne.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/RootFormOpElementEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "RootFormOpElementEmpty.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormReadAllButNoProperties.json b/codegen2/test/test-cases/failure/RootFormReadAllButNoProperties.json new file mode 100644 index 0000000000..7194248935 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormReadAllButNoProperties.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormReadAllButNoProperties.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "RootFormReadAllButNoProperties.TM.json", + "line": 12, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormServiceGroupEmpty.json b/codegen2/test/test-cases/failure/RootFormServiceGroupEmpty.json new file mode 100644 index 0000000000..cae9eedb9d --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormServiceGroupEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormServiceGroupEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "RootFormServiceGroupEmpty.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormServiceGroupNoSubAll.json b/codegen2/test/test-cases/failure/RootFormServiceGroupNoSubAll.json new file mode 100644 index 0000000000..9df342bcb1 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormServiceGroupNoSubAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormServiceGroupNoSubAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "RootFormServiceGroupNoSubAll.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormSubAllButNoEvents.json b/codegen2/test/test-cases/failure/RootFormSubAllButNoEvents.json new file mode 100644 index 0000000000..1cc129b7ee --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormSubAllButNoEvents.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormSubAllButNoEvents.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "RootFormSubAllButNoEvents.TM.json", + "line": 12, + "cfLine": 19, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicEmpty.json b/codegen2/test/test-cases/failure/RootFormTopicEmpty.json new file mode 100644 index 0000000000..a03dbf6f57 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "RootFormTopicEmpty.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicLevelEmpty.json b/codegen2/test/test-cases/failure/RootFormTopicLevelEmpty.json new file mode 100644 index 0000000000..6a41dbe6d4 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicLevelEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicLevelEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicLevelEmpty.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicLevelInvalid.json b/codegen2/test/test-cases/failure/RootFormTopicLevelInvalid.json new file mode 100644 index 0000000000..cc290ec300 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicLevelInvalid.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicLevelInvalid.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicLevelInvalid.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicReservedStart.json b/codegen2/test/test-cases/failure/RootFormTopicReservedStart.json new file mode 100644 index 0000000000..833ad46d1d --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicReservedStart.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicReservedStart.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicReservedStart.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenActionWhenSubAll.json b/codegen2/test/test-cases/failure/RootFormTopicTokenActionWhenSubAll.json new file mode 100644 index 0000000000..1c65a69715 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenActionWhenSubAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenActionWhenSubAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenActionWhenSubAll.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenConsumerIdWhenSubAll.json b/codegen2/test/test-cases/failure/RootFormTopicTokenConsumerIdWhenSubAll.json new file mode 100644 index 0000000000..938b44c838 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenConsumerIdWhenSubAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenConsumerIdWhenSubAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenConsumerIdWhenSubAll.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenCustomEmpty.json b/codegen2/test/test-cases/failure/RootFormTopicTokenCustomEmpty.json new file mode 100644 index 0000000000..d7a8b61dbb --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenCustomEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenCustomEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenCustomEmpty.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenCustomInvalid.json b/codegen2/test/test-cases/failure/RootFormTopicTokenCustomInvalid.json new file mode 100644 index 0000000000..996cd5c00c --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenCustomInvalid.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenCustomInvalid.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenCustomInvalid.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenEmpty.json b/codegen2/test/test-cases/failure/RootFormTopicTokenEmpty.json new file mode 100644 index 0000000000..2c3f4398ca --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenEmpty.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenExecutorId.json b/codegen2/test/test-cases/failure/RootFormTopicTokenExecutorId.json new file mode 100644 index 0000000000..27849a22f7 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenExecutorId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenExecutorId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenExecutorId.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenInvokerId.json b/codegen2/test/test-cases/failure/RootFormTopicTokenInvokerId.json new file mode 100644 index 0000000000..1f18c38bfc --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenInvokerId.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenInvokerId.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenInvokerId.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenMaintainerIdWhenSubAll.json b/codegen2/test/test-cases/failure/RootFormTopicTokenMaintainerIdWhenSubAll.json new file mode 100644 index 0000000000..31798bc3ef --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenMaintainerIdWhenSubAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenMaintainerIdWhenSubAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenMaintainerIdWhenSubAll.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenNoActionWhenReadAllAndWriteMulti.json b/codegen2/test/test-cases/failure/RootFormTopicTokenNoActionWhenReadAllAndWriteMulti.json new file mode 100644 index 0000000000..126c481af2 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenNoActionWhenReadAllAndWriteMulti.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenNoActionWhenReadAllAndWriteMulti.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "RootFormTopicTokenNoActionWhenReadAllAndWriteMulti.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenSenderIdWhenReadAll.json b/codegen2/test/test-cases/failure/RootFormTopicTokenSenderIdWhenReadAll.json new file mode 100644 index 0000000000..180ca30609 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenSenderIdWhenReadAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenSenderIdWhenReadAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenSenderIdWhenReadAll.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenSenderIdWhenWriteMulti.json b/codegen2/test/test-cases/failure/RootFormTopicTokenSenderIdWhenWriteMulti.json new file mode 100644 index 0000000000..f3bfc67154 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenSenderIdWhenWriteMulti.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenSenderIdWhenWriteMulti.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenSenderIdWhenWriteMulti.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormTopicTokenUnrecognized.json b/codegen2/test/test-cases/failure/RootFormTopicTokenUnrecognized.json new file mode 100644 index 0000000000..00f26381cb --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormTopicTokenUnrecognized.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormTopicTokenUnrecognized.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "RootFormTopicTokenUnrecognized.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormWriteMultiButNoWritableProperties.json b/codegen2/test/test-cases/failure/RootFormWriteMultiButNoWritableProperties.json new file mode 100644 index 0000000000..d7bfd2a91e --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormWriteMultiButNoWritableProperties.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormWriteMultiButNoWritableProperties.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Unusable", + "filename": "RootFormWriteMultiButNoWritableProperties.TM.json", + "line": 17, + "cfLine": 22, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormsEmpty.json b/codegen2/test/test-cases/failure/RootFormsEmpty.json new file mode 100644 index 0000000000..3fec6c6cff --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormsEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/RootFormsEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ElementMissing", + "filename": "RootFormsEmpty.TM.json", + "line": 8, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormsOpReadAllDuplicate.json b/codegen2/test/test-cases/failure/RootFormsOpReadAllDuplicate.json new file mode 100644 index 0000000000..b1eba36f55 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormsOpReadAllDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormsOpReadAllDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "RootFormsOpReadAllDuplicate.TM.json", + "line": 12, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormsOpSubAllDuplicate.json b/codegen2/test/test-cases/failure/RootFormsOpSubAllDuplicate.json new file mode 100644 index 0000000000..11b2c6e322 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormsOpSubAllDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormsOpSubAllDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "RootFormsOpSubAllDuplicate.TM.json", + "line": 12, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormsOpWriteMultiDuplicate.json b/codegen2/test/test-cases/failure/RootFormsOpWriteMultiDuplicate.json new file mode 100644 index 0000000000..c70442ec85 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormsOpWriteMultiDuplicate.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormsOpWriteMultiDuplicate.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "Duplication", + "filename": "RootFormsOpWriteMultiDuplicate.TM.json", + "line": 12, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/RootFormsOpWriteMultiNoReadAll.json b/codegen2/test/test-cases/failure/RootFormsOpWriteMultiNoReadAll.json new file mode 100644 index 0000000000..a3b28003e4 --- /dev/null +++ b/codegen2/test/test-cases/failure/RootFormsOpWriteMultiNoReadAll.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/RootFormsOpWriteMultiNoReadAll.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "RootFormsOpWriteMultiNoReadAll.TM.json", + "line": 12, + "cfLine": 8, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayConst.json new file mode 100644 index 0000000000..cc0b65c728 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionArrayConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionArrayConst.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsNoType.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsNoType.json new file mode 100644 index 0000000000..568d982d6b --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsNoType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionArrayItemsNoType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "SchemaDefinitionArrayItemsNoType.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsNotObject.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsNotObject.json new file mode 100644 index 0000000000..b9cf4c9193 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsNotObject.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionArrayItemsNotObject.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionArrayItemsNotObject.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsObjectConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsObjectConst.json new file mode 100644 index 0000000000..2359b7a916 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsObjectConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionArrayItemsObjectConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionArrayItemsObjectConst.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsRef.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsRef.json new file mode 100644 index 0000000000..68248154c6 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsRef.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionArrayItemsRef.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionArrayItemsRef.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsStringConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsStringConst.json new file mode 100644 index 0000000000..59e1a2a1b1 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsStringConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionArrayItemsStringConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionArrayItemsStringConst.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsTypeNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsTypeNotString.json new file mode 100644 index 0000000000..cdb42a47fc --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsTypeNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionArrayItemsTypeNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionArrayItemsTypeNotString.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsTypeNull.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsTypeNull.json new file mode 100644 index 0000000000..4bd0cc583e --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsTypeNull.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionArrayItemsTypeNull.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionArrayItemsTypeNull.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsTypeUnsupported.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsTypeUnsupported.json new file mode 100644 index 0000000000..0ad6ccb0d5 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayItemsTypeUnsupported.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionArrayItemsTypeUnsupported.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionArrayItemsTypeUnsupported.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionArrayNoItems.json b/codegen2/test/test-cases/failure/SchemaDefinitionArrayNoItems.json new file mode 100644 index 0000000000..0db2c20603 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionArrayNoItems.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionArrayNoItems.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "SchemaDefinitionArrayNoItems.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionBooleanConstValueNotBoolean.json b/codegen2/test/test-cases/failure/SchemaDefinitionBooleanConstValueNotBoolean.json new file mode 100644 index 0000000000..09e31ed714 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionBooleanConstValueNotBoolean.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionBooleanConstValueNotBoolean.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionBooleanConstValueNotBoolean.TM.json", + "line": 11, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionBooleanReadOnly.json b/codegen2/test/test-cases/failure/SchemaDefinitionBooleanReadOnly.json new file mode 100644 index 0000000000..63ae803380 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionBooleanReadOnly.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionBooleanReadOnly.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionBooleanReadOnly.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionBooleanUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionBooleanUnsupportedProperty.json new file mode 100644 index 0000000000..1646c2cc9c --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionBooleanUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionBooleanUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionBooleanUnsupportedProperty.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueAboveMax.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueAboveMax.json new file mode 100644 index 0000000000..7074ec2d6b --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueAboveMax.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionIntegerConstValueAboveMax.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionIntegerConstValueAboveMax.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueBelowMin.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueBelowMin.json new file mode 100644 index 0000000000..8f60b2adda --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueBelowMin.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionIntegerConstValueBelowMin.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionIntegerConstValueBelowMin.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueNotInteger.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueNotInteger.json new file mode 100644 index 0000000000..425756cbea --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueNotInteger.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionIntegerConstValueNotInteger.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionIntegerConstValueNotInteger.TM.json", + "line": 11, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueNotNumeric.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueNotNumeric.json new file mode 100644 index 0000000000..9f620c04f2 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerConstValueNotNumeric.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionIntegerConstValueNotNumeric.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionIntegerConstValueNotNumeric.TM.json", + "line": 11, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMaxNotInteger.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMaxNotInteger.json new file mode 100644 index 0000000000..f76246b742 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMaxNotInteger.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionIntegerMaxNotInteger.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionIntegerMaxNotInteger.TM.json", + "line": 12, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMaxNotNumeric.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMaxNotNumeric.json new file mode 100644 index 0000000000..88e8ddf142 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMaxNotNumeric.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionIntegerMaxNotNumeric.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionIntegerMaxNotNumeric.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMinAboveMax.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMinAboveMax.json new file mode 100644 index 0000000000..f2ec3ccb03 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMinAboveMax.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionIntegerMinAboveMax.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionIntegerMinAboveMax.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMinNotInteger.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMinNotInteger.json new file mode 100644 index 0000000000..5ed84341b4 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMinNotInteger.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionIntegerMinNotInteger.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionIntegerMinNotInteger.TM.json", + "line": 11, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMinNotNumeric.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMinNotNumeric.json new file mode 100644 index 0000000000..d1856f7509 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerMinNotNumeric.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionIntegerMinNotNumeric.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionIntegerMinNotNumeric.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerReadOnly.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerReadOnly.json new file mode 100644 index 0000000000..a29c2b8cfb --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerReadOnly.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionIntegerReadOnly.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionIntegerReadOnly.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionIntegerUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerUnsupportedProperty.json new file mode 100644 index 0000000000..89ae70aec3 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionIntegerUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionIntegerUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionIntegerUnsupportedProperty.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesNoType.json b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesNoType.json new file mode 100644 index 0000000000..c5be62c74b --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesNoType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesNoType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "SchemaDefinitionMapAdditionalPropertiesNoType.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesNotObject.json b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesNotObject.json new file mode 100644 index 0000000000..eef21c0e9e --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesNotObject.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionMapAdditionalPropertiesNotObject.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionMapAdditionalPropertiesNotObject.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesObjectConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesObjectConst.json new file mode 100644 index 0000000000..76e7f42335 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesObjectConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesObjectConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionMapAdditionalPropertiesObjectConst.TM.json", + "line": 18, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesRef.json b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesRef.json new file mode 100644 index 0000000000..55721955f0 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesRef.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesRef.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionMapAdditionalPropertiesRef.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesStringConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesStringConst.json new file mode 100644 index 0000000000..efa2a991cf --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesStringConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesStringConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionMapAdditionalPropertiesStringConst.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesTypeNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesTypeNotString.json new file mode 100644 index 0000000000..4d21f13db1 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesTypeNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionMapAdditionalPropertiesTypeNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionMapAdditionalPropertiesTypeNotString.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesTypeNull.json b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesTypeNull.json new file mode 100644 index 0000000000..0c0ff04686 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesTypeNull.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesTypeNull.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionMapAdditionalPropertiesTypeNull.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesTypeUnsupported.json b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesTypeUnsupported.json new file mode 100644 index 0000000000..e8891020bb --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionMapAdditionalPropertiesTypeUnsupported.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionMapAdditionalPropertiesTypeUnsupported.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionMapAdditionalPropertiesTypeUnsupported.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionMapConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionMapConst.json new file mode 100644 index 0000000000..861f9dfd9f --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionMapConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionMapConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionMapConst.TM.json", + "line": 14, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionNoType.json b/codegen2/test/test-cases/failure/SchemaDefinitionNoType.json new file mode 100644 index 0000000000..6d3cd814a5 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionNoType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionNoType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "SchemaDefinitionNoType.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionNumberConstValueAboveMax.json b/codegen2/test/test-cases/failure/SchemaDefinitionNumberConstValueAboveMax.json new file mode 100644 index 0000000000..a598289886 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionNumberConstValueAboveMax.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionNumberConstValueAboveMax.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionNumberConstValueAboveMax.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionNumberConstValueBelowMin.json b/codegen2/test/test-cases/failure/SchemaDefinitionNumberConstValueBelowMin.json new file mode 100644 index 0000000000..02e473c7c7 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionNumberConstValueBelowMin.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionNumberConstValueBelowMin.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionNumberConstValueBelowMin.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionNumberConstValueNotNumeric.json b/codegen2/test/test-cases/failure/SchemaDefinitionNumberConstValueNotNumeric.json new file mode 100644 index 0000000000..dcbb6ee33b --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionNumberConstValueNotNumeric.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionNumberConstValueNotNumeric.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionNumberConstValueNotNumeric.TM.json", + "line": 11, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionNumberMaxNotNumeric.json b/codegen2/test/test-cases/failure/SchemaDefinitionNumberMaxNotNumeric.json new file mode 100644 index 0000000000..533fee051f --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionNumberMaxNotNumeric.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionNumberMaxNotNumeric.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionNumberMaxNotNumeric.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionNumberMinAboveMax.json b/codegen2/test/test-cases/failure/SchemaDefinitionNumberMinAboveMax.json new file mode 100644 index 0000000000..e1436bbcaa --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionNumberMinAboveMax.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionNumberMinAboveMax.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionNumberMinAboveMax.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionNumberMinNotNumeric.json b/codegen2/test/test-cases/failure/SchemaDefinitionNumberMinNotNumeric.json new file mode 100644 index 0000000000..bdf3578b8b --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionNumberMinNotNumeric.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionNumberMinNotNumeric.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionNumberMinNotNumeric.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionNumberReadOnly.json b/codegen2/test/test-cases/failure/SchemaDefinitionNumberReadOnly.json new file mode 100644 index 0000000000..dcbeb410e1 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionNumberReadOnly.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionNumberReadOnly.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionNumberReadOnly.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionNumberUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionNumberUnsupportedProperty.json new file mode 100644 index 0000000000..65226824ad --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionNumberUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionNumberUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionNumberUnsupportedProperty.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectBothDeterminants.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectBothDeterminants.json new file mode 100644 index 0000000000..df6fc5ad1c --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectBothDeterminants.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectBothDeterminants.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionObjectBothDeterminants.TM.json", + "line": 11, + "cfLine": 16, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstNotObject.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstNotObject.json new file mode 100644 index 0000000000..60100dbe38 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstNotObject.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstNotObject.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionObjectConstNotObject.TM.json", + "line": 16, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyBooleanConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyBooleanConst.json new file mode 100644 index 0000000000..076e67a8ab --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyBooleanConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectConstPropertyBooleanConst.TM.json", + "line": 14, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyBooleanNotBoolean.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyBooleanNotBoolean.json new file mode 100644 index 0000000000..77bbc9bb40 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyBooleanNotBoolean.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanNotBoolean.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionObjectConstPropertyBooleanNotBoolean.TM.json", + "line": 17, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyBooleanUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyBooleanUnsupportedProperty.json new file mode 100644 index 0000000000..7b1216f4c1 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyBooleanUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectConstPropertyBooleanUnsupportedProperty.TM.json", + "line": 14, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerAboveMax.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerAboveMax.json new file mode 100644 index 0000000000..2eb374f8cd --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerAboveMax.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerAboveMax.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionObjectConstPropertyIntegerAboveMax.TM.json", + "line": 18, + "cfLine": 14, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerBelowMin.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerBelowMin.json new file mode 100644 index 0000000000..aca6917bba --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerBelowMin.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerBelowMin.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionObjectConstPropertyIntegerBelowMin.TM.json", + "line": 18, + "cfLine": 14, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerConst.json new file mode 100644 index 0000000000..0b9250953d --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectConstPropertyIntegerConst.TM.json", + "line": 14, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerNotInteger.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerNotInteger.json new file mode 100644 index 0000000000..b7f7bd9872 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerNotInteger.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerNotInteger.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionObjectConstPropertyIntegerNotInteger.TM.json", + "line": 17, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerNotNumeric.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerNotNumeric.json new file mode 100644 index 0000000000..73f5631e75 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerNotNumeric.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerNotNumeric.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionObjectConstPropertyIntegerNotNumeric.TM.json", + "line": 17, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerUnsupportedProperty.json new file mode 100644 index 0000000000..0b1d9436aa --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyIntegerUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectConstPropertyIntegerUnsupportedProperty.TM.json", + "line": 14, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNoType.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNoType.json new file mode 100644 index 0000000000..f27cd1443a --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNoType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyNoType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "SchemaDefinitionObjectConstPropertyNoType.TM.json", + "line": 12, + "cfLine": 15, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNoValue.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNoValue.json new file mode 100644 index 0000000000..5e7b21b574 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNoValue.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyNoValue.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "SchemaDefinitionObjectConstPropertyNoValue.TM.json", + "line": 12, + "cfLine": 16, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberAboveMax.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberAboveMax.json new file mode 100644 index 0000000000..00fc2b07e8 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberAboveMax.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberAboveMax.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionObjectConstPropertyNumberAboveMax.TM.json", + "line": 18, + "cfLine": 14, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberBelowMin.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberBelowMin.json new file mode 100644 index 0000000000..f102a03f67 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberBelowMin.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberBelowMin.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionObjectConstPropertyNumberBelowMin.TM.json", + "line": 18, + "cfLine": 14, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberConst.json new file mode 100644 index 0000000000..77d834e9c9 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectConstPropertyNumberConst.TM.json", + "line": 14, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberNotNumeric.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberNotNumeric.json new file mode 100644 index 0000000000..7ded3adcb3 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberNotNumeric.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberNotNumeric.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionObjectConstPropertyNumberNotNumeric.TM.json", + "line": 17, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberUnsupportedProperty.json new file mode 100644 index 0000000000..a3f64039d7 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyNumberUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectConstPropertyNumberUnsupportedProperty.TM.json", + "line": 14, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyStringConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyStringConst.json new file mode 100644 index 0000000000..ed281d0f78 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyStringConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyStringConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectConstPropertyStringConst.TM.json", + "line": 14, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyStringUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyStringUnsupportedProperty.json new file mode 100644 index 0000000000..57cb8bab29 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyStringUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyStringUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectConstPropertyStringUnsupportedProperty.TM.json", + "line": 14, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyStringValueNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyStringValueNotString.json new file mode 100644 index 0000000000..f47aebba34 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyStringValueNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyStringValueNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionObjectConstPropertyStringValueNotString.TM.json", + "line": 17, + "cfLine": 13, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeArray.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeArray.json new file mode 100644 index 0000000000..d2023ae92a --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeArray.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeArray.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionObjectConstPropertyTypeArray.TM.json", + "line": 13, + "cfLine": 19, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeNull.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeNull.json new file mode 100644 index 0000000000..d72832a618 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeNull.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeNull.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionObjectConstPropertyTypeNull.TM.json", + "line": 13, + "cfLine": 16, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeObject.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeObject.json new file mode 100644 index 0000000000..903f60c937 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeObject.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeObject.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionObjectConstPropertyTypeObject.TM.json", + "line": 13, + "cfLine": 21, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeUnsupported.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeUnsupported.json new file mode 100644 index 0000000000..57e491c4bc --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstPropertyTypeUnsupported.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionObjectConstPropertyTypeUnsupported.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionObjectConstPropertyTypeUnsupported.TM.json", + "line": 13, + "cfLine": 16, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstUnsupportedProperty.json new file mode 100644 index 0000000000..251ac81c5e --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectConstUnsupportedProperty.TM.json", + "line": 16, + "cfLine": 17, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstValueNoSchema.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstValueNoSchema.json new file mode 100644 index 0000000000..c02c8a4a6f --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectConstValueNoSchema.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectConstValueNoSchema.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "SchemaDefinitionObjectConstValueNoSchema.TM.json", + "line": 14, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectErrorMessageNoProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectErrorMessageNoProperty.json new file mode 100644 index 0000000000..49f93590e2 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectErrorMessageNoProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectErrorMessageNoProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "SchemaDefinitionObjectErrorMessageNoProperty.TM.json", + "line": 13, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectErrorMessageNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectErrorMessageNotString.json new file mode 100644 index 0000000000..ed1c834492 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectErrorMessageNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectErrorMessageNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionObjectErrorMessageNotString.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectErrorMessagePropertyNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectErrorMessagePropertyNotString.json new file mode 100644 index 0000000000..d9411ada65 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectErrorMessagePropertyNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectErrorMessagePropertyNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionObjectErrorMessagePropertyNotString.TM.json", + "line": 16, + "cfLine": 12, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectNoDeterminant.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectNoDeterminant.json new file mode 100644 index 0000000000..f3136ae74e --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectNoDeterminant.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectNoDeterminant.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "SchemaDefinitionObjectNoDeterminant.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertiesNotObject.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertiesNotObject.json new file mode 100644 index 0000000000..63a7a7dded --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertiesNotObject.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectPropertiesNotObject.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionObjectPropertiesNotObject.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyNoType.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyNoType.json new file mode 100644 index 0000000000..4a8a37c493 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyNoType.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectPropertyNoType.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyMissing", + "filename": "SchemaDefinitionObjectPropertyNoType.TM.json", + "line": 12, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyObjectConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyObjectConst.json new file mode 100644 index 0000000000..7b235d4d13 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyObjectConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectPropertyObjectConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectPropertyObjectConst.TM.json", + "line": 19, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyRef.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyRef.json new file mode 100644 index 0000000000..dc2ea06770 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyRef.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectPropertyRef.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectPropertyRef.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyStringConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyStringConst.json new file mode 100644 index 0000000000..050e1dc9ab --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyStringConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectPropertyStringConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionObjectPropertyStringConst.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyTypeNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyTypeNotString.json new file mode 100644 index 0000000000..59cc8f72b4 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyTypeNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionObjectPropertyTypeNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionObjectPropertyTypeNotString.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyTypeNull.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyTypeNull.json new file mode 100644 index 0000000000..da692fdcd3 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyTypeNull.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectPropertyTypeNull.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionObjectPropertyTypeNull.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyTypeUnsupported.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyTypeUnsupported.json new file mode 100644 index 0000000000..2bba617ff9 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectPropertyTypeUnsupported.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionObjectPropertyTypeUnsupported.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionObjectPropertyTypeUnsupported.TM.json", + "line": 13, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectRequiredNoProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectRequiredNoProperty.json new file mode 100644 index 0000000000..9cdd01de93 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectRequiredNoProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionObjectRequiredNoProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ItemNotFound", + "filename": "SchemaDefinitionObjectRequiredNoProperty.TM.json", + "line": 13, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionObjectRequiredNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionObjectRequiredNotString.json new file mode 100644 index 0000000000..9580f83465 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionObjectRequiredNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionObjectRequiredNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionObjectRequiredNotString.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionRef.json b/codegen2/test/test-cases/failure/SchemaDefinitionRef.json new file mode 100644 index 0000000000..7b2659673e --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionRef.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionRef.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionRef.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringConstContentEncoding.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstContentEncoding.json new file mode 100644 index 0000000000..ceb6d559bf --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstContentEncoding.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringConstContentEncoding.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringConstContentEncoding.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringConstFormat.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstFormat.json new file mode 100644 index 0000000000..f53eae03e7 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstFormat.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringConstFormat.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringConstFormat.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringConstPattern.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstPattern.json new file mode 100644 index 0000000000..de90f7e50d --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstPattern.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringConstPattern.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringConstPattern.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringConstUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstUnsupportedProperty.json new file mode 100644 index 0000000000..1c9afa2a97 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringConstUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringConstUnsupportedProperty.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringConstValueNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstValueNotString.json new file mode 100644 index 0000000000..c5718e6b47 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringConstValueNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringConstValueNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "TypeMismatch", + "filename": "SchemaDefinitionStringConstValueNotString.TM.json", + "line": 11, + "cfLine": 10, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringContentEncodingNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringContentEncodingNotString.json new file mode 100644 index 0000000000..bad0e5d73e --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringContentEncodingNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionStringContentEncodingNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionStringContentEncodingNotString.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringContentEncodingUnsupportedValue.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringContentEncodingUnsupportedValue.json new file mode 100644 index 0000000000..2b970479a0 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringContentEncodingUnsupportedValue.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringContentEncodingUnsupportedValue.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionStringContentEncodingUnsupportedValue.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumConst.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumConst.json new file mode 100644 index 0000000000..4162361278 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumConst.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringEnumConst.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringEnumConst.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumContentEncoding.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumContentEncoding.json new file mode 100644 index 0000000000..f9093bfaf8 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumContentEncoding.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringEnumContentEncoding.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringEnumContentEncoding.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumElementInvalid.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumElementInvalid.json new file mode 100644 index 0000000000..07870ea913 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumElementInvalid.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringEnumElementInvalid.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionStringEnumElementInvalid.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumFormat.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumFormat.json new file mode 100644 index 0000000000..541d938994 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumFormat.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringEnumFormat.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringEnumFormat.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumPattern.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumPattern.json new file mode 100644 index 0000000000..af87a36653 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumPattern.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringEnumPattern.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringEnumPattern.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumReadOnly.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumReadOnly.json new file mode 100644 index 0000000000..c42c4a12dd --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumReadOnly.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringEnumReadOnly.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringEnumReadOnly.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumUnsupportedProperty.json new file mode 100644 index 0000000000..6873e5d627 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringEnumUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringEnumUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringEnumUnsupportedProperty.TM.json", + "line": 12, + "cfLine": 11, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatAndContentEncoding.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatAndContentEncoding.json new file mode 100644 index 0000000000..13c107fbf8 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatAndContentEncoding.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringFormatAndContentEncoding.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionStringFormatAndContentEncoding.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatAndPattern.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatAndPattern.json new file mode 100644 index 0000000000..c3a5aa6b33 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatAndPattern.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringFormatAndPattern.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionStringFormatAndPattern.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatNotString.json new file mode 100644 index 0000000000..c4549437e4 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionStringFormatNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionStringFormatNotString.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatUnsupportedValue.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatUnsupportedValue.json new file mode 100644 index 0000000000..7e9a2c10f7 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringFormatUnsupportedValue.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringFormatUnsupportedValue.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionStringFormatUnsupportedValue.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternAndContentEncoding.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternAndContentEncoding.json new file mode 100644 index 0000000000..33188d5131 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternAndContentEncoding.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringPatternAndContentEncoding.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "ValuesInconsistent", + "filename": "SchemaDefinitionStringPatternAndContentEncoding.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternNotDurationOrDecimal.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternNotDurationOrDecimal.json new file mode 100644 index 0000000000..9781596e93 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternNotDurationOrDecimal.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringPatternNotDurationOrDecimal.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionStringPatternNotDurationOrDecimal.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternNotString.json new file mode 100644 index 0000000000..b6eaa02a84 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionStringPatternNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionStringPatternNotString.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternRegexInvalid.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternRegexInvalid.json new file mode 100644 index 0000000000..1b46459503 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringPatternRegexInvalid.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringPatternRegexInvalid.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "SchemaDefinitionStringPatternRegexInvalid.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringReadOnly.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringReadOnly.json new file mode 100644 index 0000000000..e4de991f48 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringReadOnly.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringReadOnly.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringReadOnly.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionStringUnsupportedProperty.json b/codegen2/test/test-cases/failure/SchemaDefinitionStringUnsupportedProperty.json new file mode 100644 index 0000000000..82d8b6f90a --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionStringUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionStringUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "SchemaDefinitionStringUnsupportedProperty.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionTypeNotString.json b/codegen2/test/test-cases/failure/SchemaDefinitionTypeNotString.json new file mode 100644 index 0000000000..c895f2c119 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionTypeNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionTypeNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "SchemaDefinitionTypeNotString.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionTypeNull.json b/codegen2/test/test-cases/failure/SchemaDefinitionTypeNull.json new file mode 100644 index 0000000000..ce49c1b227 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionTypeNull.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/SchemaDefinitionTypeNull.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionTypeNull.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/SchemaDefinitionTypeUnsupported.json b/codegen2/test/test-cases/failure/SchemaDefinitionTypeUnsupported.json new file mode 100644 index 0000000000..a36cda4a39 --- /dev/null +++ b/codegen2/test/test-cases/failure/SchemaDefinitionTypeUnsupported.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/SchemaDefinitionTypeUnsupported.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "SchemaDefinitionTypeUnsupported.TM.json", + "line": 10, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/StringDecimalPlaces.json b/codegen2/test/test-cases/failure/StringDecimalPlaces.json new file mode 100644 index 0000000000..508e95db8a --- /dev/null +++ b/codegen2/test/test-cases/failure/StringDecimalPlaces.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/StringDecimalPlaces.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "StringDecimalPlaces.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/StringScaleFactor.json b/codegen2/test/test-cases/failure/StringScaleFactor.json new file mode 100644 index 0000000000..7d4ddd28ea --- /dev/null +++ b/codegen2/test/test-cases/failure/StringScaleFactor.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/StringScaleFactor.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "StringScaleFactor.TM.json", + "line": 14, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/ThingUnsupportedProperty.json b/codegen2/test/test-cases/failure/ThingUnsupportedProperty.json new file mode 100644 index 0000000000..7ac69610e2 --- /dev/null +++ b/codegen2/test/test-cases/failure/ThingUnsupportedProperty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/ThingUnsupportedProperty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyUnsupported", + "filename": "ThingUnsupportedProperty.TM.json", + "line": 8, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/TitleEmpty.json b/codegen2/test/test-cases/failure/TitleEmpty.json new file mode 100644 index 0000000000..e6cef89a30 --- /dev/null +++ b/codegen2/test/test-cases/failure/TitleEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/TitleEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "TitleEmpty.TM.json", + "line": 7, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/TitleInvalid.json b/codegen2/test/test-cases/failure/TitleInvalid.json new file mode 100644 index 0000000000..21566a980a --- /dev/null +++ b/codegen2/test/test-cases/failure/TitleInvalid.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/TitleInvalid.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "TitleInvalid.TM.json", + "line": 7, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/TitleNotString.json b/codegen2/test/test-cases/failure/TitleNotString.json new file mode 100644 index 0000000000..9021ce238c --- /dev/null +++ b/codegen2/test/test-cases/failure/TitleNotString.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/TitleNotString.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "TitleNotString.TM.json", + "line": 7, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/TrailingComma.json b/codegen2/test/test-cases/failure/TrailingComma.json new file mode 100644 index 0000000000..d4eca9587e --- /dev/null +++ b/codegen2/test/test-cases/failure/TrailingComma.json @@ -0,0 +1,23 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/TrailingComma.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "TrailingComma.TM.json", + "line": 11, + "cfLine": 0, + "crossRef": "" + }, + { + "condition": "JsonInvalid", + "filename": "TrailingComma.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/TypeEmpty.json b/codegen2/test/test-cases/failure/TypeEmpty.json new file mode 100644 index 0000000000..4b0ce99be7 --- /dev/null +++ b/codegen2/test/test-cases/failure/TypeEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/TypeEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "TypeEmpty.TM.json", + "line": 6, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/TypeNotThingModel.json b/codegen2/test/test-cases/failure/TypeNotThingModel.json new file mode 100644 index 0000000000..0b90ddba3c --- /dev/null +++ b/codegen2/test/test-cases/failure/TypeNotThingModel.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidThing/TypeNotThingModel.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "TypeNotThingModel.TM.json", + "line": 6, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/TypeRefEmpty.json b/codegen2/test/test-cases/failure/TypeRefEmpty.json new file mode 100644 index 0000000000..fffc75fbd0 --- /dev/null +++ b/codegen2/test/test-cases/failure/TypeRefEmpty.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/TypeRefEmpty.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyEmpty", + "filename": "TypeRefEmpty.TM.json", + "line": 9, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/TypeRefWithoutAovContext.json b/codegen2/test/test-cases/failure/TypeRefWithoutAovContext.json new file mode 100644 index 0000000000..78a45b9711 --- /dev/null +++ b/codegen2/test/test-cases/failure/TypeRefWithoutAovContext.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidAioBinding/TypeRefWithoutAovContext.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "PropertyInvalid", + "filename": "TypeRefWithoutAovContext.TM.json", + "line": 8, + "cfLine": 2, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/UnclosedArray.json b/codegen2/test/test-cases/failure/UnclosedArray.json new file mode 100644 index 0000000000..3709bce8f9 --- /dev/null +++ b/codegen2/test/test-cases/failure/UnclosedArray.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/UnclosedArray.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "UnclosedArray.TM.json", + "line": 16, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/UnclosedObject.json b/codegen2/test/test-cases/failure/UnclosedObject.json new file mode 100644 index 0000000000..73cff08d77 --- /dev/null +++ b/codegen2/test/test-cases/failure/UnclosedObject.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "invalidJson/UnclosedObject.TM.json" ], + "lang": "none" + }, + "errors": [ + { + "condition": "JsonInvalid", + "filename": "UnclosedObject.TM.json", + "line": 15, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/failure/UnsupportedLanguage.json b/codegen2/test/test-cases/failure/UnsupportedLanguage.json new file mode 100644 index 0000000000..e17f8c1a6a --- /dev/null +++ b/codegen2/test/test-cases/failure/UnsupportedLanguage.json @@ -0,0 +1,16 @@ +{ + "success": false, + "commandLine": { + "thingFiles": [ "valid/Noop.TM.json" ], + "lang": "haskell" + }, + "errors": [ + { + "condition": "PropertyUnsupportedValue", + "filename": "", + "line": 0, + "cfLine": 0, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/success/EventContainedInEventNoContains.json b/codegen2/test/test-cases/success/EventContainedInEventNoContains.json new file mode 100644 index 0000000000..d946a85d37 --- /dev/null +++ b/codegen2/test/test-cases/success/EventContainedInEventNoContains.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/EventContainedInEventNoContains.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/EventContainsEventNoContainedIn.json b/codegen2/test/test-cases/success/EventContainsEventNoContainedIn.json new file mode 100644 index 0000000000..751522699f --- /dev/null +++ b/codegen2/test/test-cases/success/EventContainsEventNoContainedIn.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/EventContainsEventNoContainedIn.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/LinkCapability.json b/codegen2/test/test-cases/success/LinkCapability.json new file mode 100644 index 0000000000..5ff8f2e8fc --- /dev/null +++ b/codegen2/test/test-cases/success/LinkCapability.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/LinkCapability.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/LinkComponent.json b/codegen2/test/test-cases/success/LinkComponent.json new file mode 100644 index 0000000000..a2768d7a71 --- /dev/null +++ b/codegen2/test/test-cases/success/LinkComponent.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/LinkComponent.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/LinkExtends.json b/codegen2/test/test-cases/success/LinkExtends.json new file mode 100644 index 0000000000..d8a5c10f3d --- /dev/null +++ b/codegen2/test/test-cases/success/LinkExtends.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/LinkExtends.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/LinkNoRel.json b/codegen2/test/test-cases/success/LinkNoRel.json new file mode 100644 index 0000000000..31808ebea2 --- /dev/null +++ b/codegen2/test/test-cases/success/LinkNoRel.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/LinkNoRel.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/LinkOtherAovRel.json b/codegen2/test/test-cases/success/LinkOtherAovRel.json new file mode 100644 index 0000000000..b220c3b3f9 --- /dev/null +++ b/codegen2/test/test-cases/success/LinkOtherAovRel.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/LinkOtherAovRel.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/LinkOtherRel.json b/codegen2/test/test-cases/success/LinkOtherRel.json new file mode 100644 index 0000000000..187336280d --- /dev/null +++ b/codegen2/test/test-cases/success/LinkOtherRel.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/LinkOtherRel.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/LinkReference.json b/codegen2/test/test-cases/success/LinkReference.json new file mode 100644 index 0000000000..43bd6a51f3 --- /dev/null +++ b/codegen2/test/test-cases/success/LinkReference.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/LinkReference.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/LinkTypedReference.json b/codegen2/test/test-cases/success/LinkTypedReference.json new file mode 100644 index 0000000000..94e756f4a4 --- /dev/null +++ b/codegen2/test/test-cases/success/LinkTypedReference.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/LinkTypedReference.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/ObjectPropertyNamespace.json b/codegen2/test/test-cases/success/ObjectPropertyNamespace.json new file mode 100644 index 0000000000..b5a40567c3 --- /dev/null +++ b/codegen2/test/test-cases/success/ObjectPropertyNamespace.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/ObjectPropertyNamespace.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/ObjectTypeRef.json b/codegen2/test/test-cases/success/ObjectTypeRef.json new file mode 100644 index 0000000000..4d7779462f --- /dev/null +++ b/codegen2/test/test-cases/success/ObjectTypeRef.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/ObjectTypeRef.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/PropertyContainedInPropertyNoContains.json b/codegen2/test/test-cases/success/PropertyContainedInPropertyNoContains.json new file mode 100644 index 0000000000..9aa168456d --- /dev/null +++ b/codegen2/test/test-cases/success/PropertyContainedInPropertyNoContains.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/PropertyContainedInPropertyNoContains.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/PropertyContainsPropertyNoContainedIn.json b/codegen2/test/test-cases/success/PropertyContainsPropertyNoContainedIn.json new file mode 100644 index 0000000000..d129086b21 --- /dev/null +++ b/codegen2/test/test-cases/success/PropertyContainsPropertyNoContainedIn.json @@ -0,0 +1,7 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/PropertyContainsPropertyNoContainedIn.TM.json" ], + "lang": "none" + } +} diff --git a/codegen2/test/test-cases/success/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses.json b/codegen2/test/test-cases/success/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses.json new file mode 100644 index 0000000000..32a63c539a --- /dev/null +++ b/codegen2/test/test-cases/success/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses.json @@ -0,0 +1,15 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses.TM.json" ], + "lang": "none" + }, + "warnings": [ + { + "filename": "RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses.TM.json", + "line": 27, + "cfLine": 41, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/test-cases/success/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses.json b/codegen2/test/test-cases/success/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses.json new file mode 100644 index 0000000000..81bab3539f --- /dev/null +++ b/codegen2/test/test-cases/success/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses.json @@ -0,0 +1,15 @@ +{ + "success": true, + "commandLine": { + "thingFiles": [ "valid/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses.TM.json" ], + "lang": "none" + }, + "warnings": [ + { + "filename": "RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses.TM.json", + "line": 32, + "cfLine": 47, + "crossRef": "" + } + ] +} diff --git a/codegen2/test/thing-models/.vscode/settings.json b/codegen2/test/thing-models/.vscode/settings.json new file mode 100644 index 0000000000..8ce7a1138b --- /dev/null +++ b/codegen2/test/thing-models/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "json.schemaDownload.enable": true, + "json.schemas": [ + { + "fileMatch": [ "**/*.TM.json" ], + "url": "../../schema/aio-tm-json-schema.json" + } + ] +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionContainedIn.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionContainedIn.TM.json new file mode 100644 index 0000000000..82083705ce --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionContainedIn.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "aov:containedIn": [ "somethingElse" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionContains.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionContains.TM.json new file mode 100644 index 0000000000..a5d4d9cb55 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionContains.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "aov:contains": "something", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json new file mode 100644 index 0000000000..e2b9052f5a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Echo", + "actions": { + "echo": { + "input": { + "title": "MyActionSchema", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "output": { + "title": "MyActionSchema", + "type": "object", + "properties": { + "value": { + "type": "integer" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionDuplicateSchemaNamesObjectsDifferentFields.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionDuplicateSchemaNamesObjectsDifferentFields.TM.json new file mode 100644 index 0000000000..28463ae5bd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionDuplicateSchemaNamesObjectsDifferentFields.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Echo", + "actions": { + "echo": { + "input": { + "title": "MyActionSchema", + "type": "object", + "properties": { + "inVal": { + "type": "string" + } + } + }, + "output": { + "title": "MyActionSchema", + "type": "object", + "properties": { + "outVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseContentTypeEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseContentTypeEmpty.TM.json new file mode 100644 index 0000000000..85f1d6db7d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseContentTypeEmpty.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false, + "schema": "Error", + "contentType": "" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseContentTypeText.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseContentTypeText.TM.json new file mode 100644 index 0000000000..5591f9bc35 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseContentTypeText.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false, + "schema": "Error", + "contentType": "application/text" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoMatchingSchemaDefinition.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoMatchingSchemaDefinition.TM.json new file mode 100644 index 0000000000..3259368155 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoMatchingSchemaDefinition.TM.json @@ -0,0 +1,39 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoSchema.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoSchema.TM.json new file mode 100644 index 0000000000..d8857d6f85 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoSchema.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoSchemaDefinitions.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoSchemaDefinitions.TM.json new file mode 100644 index 0000000000..5d2550ce83 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoSchemaDefinitions.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoSuccess.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoSuccess.TM.json new file mode 100644 index 0000000000..fcba5bba27 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseNoSuccess.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "schema": "Error", + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSchemaDefinitionMap.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSchemaDefinitionMap.TM.json new file mode 100644 index 0000000000..24e38ca04b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSchemaDefinitionMap.TM.json @@ -0,0 +1,44 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSchemaDefinitionNotObject.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSchemaDefinitionNotObject.TM.json new file mode 100644 index 0000000000..114e628534 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSchemaDefinitionNotObject.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false, + "schema": "Condition" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSchemaEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSchemaEmpty.TM.json new file mode 100644 index 0000000000..c4acb57611 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSchemaEmpty.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false, + "schema": "" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSuccessTrue.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSuccessTrue.TM.json new file mode 100644 index 0000000000..cfa1d96451 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseSuccessTrue.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": true, + "schema": "Error", + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseUnsupportedProperty.TM.json new file mode 100644 index 0000000000..16f6667928 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponseUnsupportedProperty.TM.json @@ -0,0 +1,48 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false, + "schema": "Error", + "contentType": "application/json", + "foobar": "hello" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponsesMultiple.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponsesMultiple.TM.json new file mode 100644 index 0000000000..d9678a67ff --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormAdditionalResponsesMultiple.TM.json @@ -0,0 +1,51 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "additionalResponses": [ + { + "success": false, + "schema": "Condition" + }, + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormContentTypeText.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormContentTypeText.TM.json new file mode 100644 index 0000000000..ea9b5939da --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormContentTypeText.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/text", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeEmpty.TM.json new file mode 100644 index 0000000000..0819e24270 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeEmpty.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerCode": "" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeNoMatchingSchemaDefinition.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeNoMatchingSchemaDefinition.TM.json new file mode 100644 index 0000000000..e1c9a9a7f7 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeNoMatchingSchemaDefinition.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerCode": "Status" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeNoSchemaDefinitions.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeNoSchemaDefinitions.TM.json new file mode 100644 index 0000000000..957fddde8b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeNoSchemaDefinitions.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerCode": "Status" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeSchemaDefinitionNotEnum.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeSchemaDefinitionNotEnum.TM.json new file mode 100644 index 0000000000..70f7e5ea4c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderCodeSchemaDefinitionNotEnum.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerCode": "Condition" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoContentTypeEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoContentTypeEmpty.TM.json new file mode 100644 index 0000000000..bb6afabc6d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoContentTypeEmpty.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "Error", + "contentType": "" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoContentTypeNotJson.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoContentTypeNotJson.TM.json new file mode 100644 index 0000000000..5e0ad88f55 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoContentTypeNotJson.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "Error", + "contentType": "application/text" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoContentType.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoContentType.TM.json new file mode 100644 index 0000000000..328147629d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoContentType.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoMatchingSchemaDefinition.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoMatchingSchemaDefinition.TM.json new file mode 100644 index 0000000000..ceefc80ee6 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoMatchingSchemaDefinition.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "Error", + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoSchema.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoSchema.TM.json new file mode 100644 index 0000000000..f317946b83 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoSchema.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoSchemaDefinitions.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoSchemaDefinitions.TM.json new file mode 100644 index 0000000000..cad11dba6e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoNoSchemaDefinitions.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "Error", + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSchemaDefinitionMap.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSchemaDefinitionMap.TM.json new file mode 100644 index 0000000000..71404f22b9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSchemaDefinitionMap.TM.json @@ -0,0 +1,45 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "Error", + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSchemaDefinitionNotObject.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSchemaDefinitionNotObject.TM.json new file mode 100644 index 0000000000..61de012243 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSchemaDefinitionNotObject.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "Condition", + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSchemaEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSchemaEmpty.TM.json new file mode 100644 index 0000000000..54c435b383 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSchemaEmpty.TM.json @@ -0,0 +1,48 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "", + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSuccessTrue.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSuccessTrue.TM.json new file mode 100644 index 0000000000..5914be676e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoSuccessTrue.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": true, + "schema": "Error", + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoUnsupportedProperty.TM.json new file mode 100644 index 0000000000..5f2c114535 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfoUnsupportedProperty.TM.json @@ -0,0 +1,48 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "Error", + "contentType": "application/json", + "foobar": "hello" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfosMultiple.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfosMultiple.TM.json new file mode 100644 index 0000000000..c3a5cb6ef9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormHeaderInfosMultiple.TM.json @@ -0,0 +1,53 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:headerInfo": [ + { + "success": false, + "schema": "Condition", + "contentType": "application/json" + }, + { + "success": false, + "schema": "Error", + "contentType": "application/json" + } + ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormOpInvokeDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormOpInvokeDuplicate.TM.json new file mode 100644 index 0000000000..9ba6a62d9b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormOpInvokeDuplicate.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": [ "invokeaction", "invokeaction" ] + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormServiceGroupEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormServiceGroupEmpty.TM.json new file mode 100644 index 0000000000..6d71ca1a90 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormServiceGroupEmpty.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "dtv:serviceGroupId": "" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicNoContentType.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicNoContentType.TM.json new file mode 100644 index 0000000000..fdcbc43e96 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicNoContentType.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenAction.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenAction.TM.json new file mode 100644 index 0000000000..b5a2934f49 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenAction.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{action}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenConsumerId.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenConsumerId.TM.json new file mode 100644 index 0000000000..39b59a6aa8 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenConsumerId.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{consumerClientId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenMaintainerId.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenMaintainerId.TM.json new file mode 100644 index 0000000000..6dad7f6631 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenMaintainerId.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{maintainerId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenSenderId.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenSenderId.TM.json new file mode 100644 index 0000000000..f096f68277 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormTopicTokenSenderId.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{senderId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormUnsupportedProperty.TM.json new file mode 100644 index 0000000000..3d81838500 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormUnsupportedProperty.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction", + "foobar": "hello" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormsMultipleContentTypes.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormsMultipleContentTypes.TM.json new file mode 100644 index 0000000000..734f7e0071 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormsMultipleContentTypes.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + }, + { + "contentType": "application/octet-stream", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormsOpInvokeDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormsOpInvokeDuplicate.TM.json new file mode 100644 index 0000000000..e9edde03ee --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormsOpInvokeDuplicate.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormsOplessAndInvoke.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormsOplessAndInvoke.TM.json new file mode 100644 index 0000000000..fed7bfafdf --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormsOplessAndInvoke.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionFormsOplessDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionFormsOplessDuplicate.TM.json new file mode 100644 index 0000000000..44c4c3012b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionFormsOplessDuplicate.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputMap.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputMap.TM.json new file mode 100644 index 0000000000..a1a95656ca --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputMap.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputNullAndContentTypeJson.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputNullAndContentTypeJson.TM.json new file mode 100644 index 0000000000..334c5510b2 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputNullAndContentTypeJson.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "type": "null" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputNumber.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputNumber.TM.json new file mode 100644 index 0000000000..b5a3dee1db --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputNumber.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "type": "number" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectAndContentTypeCustom.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectAndContentTypeCustom.TM.json new file mode 100644 index 0000000000..51bf656642 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectAndContentTypeCustom.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "type": "object", + "properties": { + "inVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectAndContentTypeRaw.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectAndContentTypeRaw.TM.json new file mode 100644 index 0000000000..b8bc6f3d50 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectAndContentTypeRaw.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "type": "object", + "properties": { + "inVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/octet-stream", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectConst.TM.json new file mode 100644 index 0000000000..755bc30627 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectConst.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "type": "object", + "properties": { + "inVal": { + "type": "string" + } + }, + "const": { + "inVal": "hello" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectErrorMessage.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectErrorMessage.TM.json new file mode 100644 index 0000000000..23acb316d6 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputObjectErrorMessage.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "type": "object", + "properties": { + "inVal": { + "type": "string" + } + }, + "dtv:errorMessage": "inVal" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputRefNotFound.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputRefNotFound.TM.json new file mode 100644 index 0000000000..cd83d95eda --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputRefNotFound.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "dtv:ref": "../../schemas/json-schemas/Nonexistent.json" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputRefString.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputRefString.TM.json new file mode 100644 index 0000000000..f42a0d5bea --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputRefString.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "dtv:ref": "../../schemas/json-schemas/AStringSchema.json" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionInputString.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionInputString.TM.json new file mode 100644 index 0000000000..6f33359261 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionInputString.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "poke": { + "input": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionMemberOfEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionMemberOfEmpty.TM.json new file mode 100644 index 0000000000..6d3e0ab222 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionMemberOfEmpty.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "aov:memberOf": "", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionMemberOfWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionMemberOfWithoutAovContext.TM.json new file mode 100644 index 0000000000..ec04114ad1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionMemberOfWithoutAovContext.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "aov:memberOf": "MyActionGroup", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionNamespaceEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionNamespaceEmpty.TM.json new file mode 100644 index 0000000000..a1c7f5fbde --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionNamespaceEmpty.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "aov:namespace": "", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionNamespaceWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionNamespaceWithoutAovContext.TM.json new file mode 100644 index 0000000000..0589d8de52 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionNamespaceWithoutAovContext.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "aov:namespace": "MyNamespace", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionNoForms.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionNoForms.TM.json new file mode 100644 index 0000000000..6b88915040 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionNoForms.TM.json @@ -0,0 +1,16 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionNoFormsWithTopic.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionNoFormsWithTopic.TM.json new file mode 100644 index 0000000000..c092e27afd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionNoFormsWithTopic.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputMap.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputMap.TM.json new file mode 100644 index 0000000000..b5e60a43a1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputMap.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputNullAndContentTypeJson.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputNullAndContentTypeJson.TM.json new file mode 100644 index 0000000000..1689f35ff6 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputNullAndContentTypeJson.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "type": "null" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputNumber.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputNumber.TM.json new file mode 100644 index 0000000000..76000e4594 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputNumber.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "type": "number" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectAndContentTypeCustom.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectAndContentTypeCustom.TM.json new file mode 100644 index 0000000000..4bf4e305cd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectAndContentTypeCustom.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "type": "object", + "properties": { + "outVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectAndContentTypeRaw.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectAndContentTypeRaw.TM.json new file mode 100644 index 0000000000..8abbbb8b42 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectAndContentTypeRaw.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "type": "object", + "properties": { + "outVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/octet-stream", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectConst.TM.json new file mode 100644 index 0000000000..b042adf03f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectConst.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "type": "object", + "properties": { + "outVal": { + "type": "string" + } + }, + "const": { + "outVal": "hello" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectErrorMessage.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectErrorMessage.TM.json new file mode 100644 index 0000000000..0d45389635 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputObjectErrorMessage.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "type": "object", + "properties": { + "outVal": { + "type": "string" + } + }, + "dtv:errorMessage": "outVal" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputRefNotFound.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputRefNotFound.TM.json new file mode 100644 index 0000000000..f97dcfe8fb --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputRefNotFound.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "dtv:ref": "../../schemas/json-schemas/Nonexistent.json" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputRefString.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputRefString.TM.json new file mode 100644 index 0000000000..283a896323 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputRefString.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "dtv:ref": "../../schemas/json-schemas/AStringSchema.json" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionOutputString.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionOutputString.TM.json new file mode 100644 index 0000000000..e2dd501d03 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionOutputString.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "peek": { + "output": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/peek/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionUnsupportedProperty.TM.json new file mode 100644 index 0000000000..5ab8a9bcb1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionUnsupportedProperty.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ], + "foobar": "hello" + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ActionsTopicDuplication.TM.json b/codegen2/test/thing-models/invalidAioBinding/ActionsTopicDuplication.TM.json new file mode 100644 index 0000000000..e1fabf5938 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ActionsTopicDuplication.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "one": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{executorId}", + "op": "invokeaction" + } + ] + }, + "two": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{executorId}" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ContextAovWrongUri.TM.json b/codegen2/test/thing-models/invalidAioBinding/ContextAovWrongUri.TM.json new file mode 100644 index 0000000000..a41dc443b1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ContextAovWrongUri.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/SomethingcElse/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ContextDtvWrongPrefix.TM.json b/codegen2/test/thing-models/invalidAioBinding/ContextDtvWrongPrefix.TM.json new file mode 100644 index 0000000000..fe86457f29 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ContextDtvWrongPrefix.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "foo": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ContextDtvWrongUri.TM.json b/codegen2/test/thing-models/invalidAioBinding/ContextDtvWrongUri.TM.json new file mode 100644 index 0000000000..6e09c3ea63 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ContextDtvWrongUri.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/SomethingcElse/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ContextMissingDtv.TM.json b/codegen2/test/thing-models/invalidAioBinding/ContextMissingDtv.TM.json new file mode 100644 index 0000000000..2d4c82cd34 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ContextMissingDtv.TM.json @@ -0,0 +1,20 @@ +{ + "@context": "https://www.w3.org/2022/wot/td/v1.1", + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/DuplicateGeneratedNameAcrossThingsInOneFile.TM.json b/codegen2/test/thing-models/invalidAioBinding/DuplicateGeneratedNameAcrossThingsInOneFile.TM.json new file mode 100644 index 0000000000..5fbe322860 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/DuplicateGeneratedNameAcrossThingsInOneFile.TM.json @@ -0,0 +1,58 @@ +[ + { + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "ThingOne", + "events": { + "alpha": { + "data": { + "title": "MyPropSchema", + "type": "object", + "properties": { + "myVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry1", + "op": "subscribeevent" + } + ] + } + } + }, + { + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "ThingTwo", + "events": { + "beta": { + "data": { + "title": "MyPropSchema", + "type": "object", + "properties": { + "myVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry2", + "op": "subscribeevent" + } + ] + } + } + } +] diff --git a/codegen2/test/thing-models/invalidAioBinding/DuplicateThingNameAcrossThingsInOneFile.TM.json b/codegen2/test/thing-models/invalidAioBinding/DuplicateThingNameAcrossThingsInOneFile.TM.json new file mode 100644 index 0000000000..cdddb4752a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/DuplicateThingNameAcrossThingsInOneFile.TM.json @@ -0,0 +1,46 @@ +[ + { + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "MyThing", + "events": { + "alpha": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry1", + "op": "subscribeevent" + } + ] + } + } + }, + { + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "MyThing", + "events": { + "beta": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry2", + "op": "subscribeevent" + } + ] + } + } + } +] diff --git a/codegen2/test/thing-models/invalidAioBinding/DuplicatedGeneratedNameBetweenThingAndJsonSchema.TM.json b/codegen2/test/thing-models/invalidAioBinding/DuplicatedGeneratedNameBetweenThingAndJsonSchema.TM.json new file mode 100644 index 0000000000..be4999982c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/DuplicatedGeneratedNameBetweenThingAndJsonSchema.TM.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Doit", + "actions": { + "doit": { + "input": { + "dtv:ref": "../../schemas/json-schemas/AnObjectSchema.json" + }, + "output": { + "title": "AnObjectSchema", + "type": "object", + "properties": { + "outVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/doit/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventBooleanConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventBooleanConst.TM.json new file mode 100644 index 0000000000..1902d97273 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventBooleanConst.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "boolean", + "const": true + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventContainedInEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventContainedInEmpty.TM.json new file mode 100644 index 0000000000..364cc5b9e1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventContainedInEmpty.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "aov:containedIn": "", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventContainedInEventContainsOther.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventContainedInEventContainsOther.TM.json new file mode 100644 index 0000000000..849e9b8cdd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventContainedInEventContainsOther.TM.json @@ -0,0 +1,53 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "aov:contains": [ "gamma" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "aov:containedIn": "alpha", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + }, + "gamma": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/gamma/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventContainedInNoEvent.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventContainedInNoEvent.TM.json new file mode 100644 index 0000000000..120d89059c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventContainedInNoEvent.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "aov:containedIn": "gamma", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventContainedInWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventContainedInWithoutAovContext.TM.json new file mode 100644 index 0000000000..258236b6a0 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventContainedInWithoutAovContext.TM.json @@ -0,0 +1,39 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "aov:containedIn": "alpha", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventContainsEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventContainsEmpty.TM.json new file mode 100644 index 0000000000..e5c759487e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventContainsEmpty.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "aov:contains": [ ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventContainsEventContainedInOther.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventContainsEventContainedInOther.TM.json new file mode 100644 index 0000000000..d8254ec710 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventContainsEventContainedInOther.TM.json @@ -0,0 +1,53 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "aov:contains": [ "beta" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "aov:containedIn": "gamma", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + }, + "gamma": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/gamma/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventContainsNoEvent.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventContainsNoEvent.TM.json new file mode 100644 index 0000000000..b5de749744 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventContainsNoEvent.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "aov:contains": [ "gamma" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventContainsWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventContainsWithoutAovContext.TM.json new file mode 100644 index 0000000000..146938884c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventContainsWithoutAovContext.TM.json @@ -0,0 +1,39 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "aov:contains": [ "beta" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesDifferentTypes.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesDifferentTypes.TM.json new file mode 100644 index 0000000000..34cf041784 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesDifferentTypes.TM.json @@ -0,0 +1,49 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "title": "MyEventSchema", + "type": "object", + "properties": { + "alertVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + }, + "notice": { + "data": { + "title": "MyEventSchema", + "type": "string", + "enum": [ + "foo", + "bar" + ] + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/notice/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesEnumsDifferentValues.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesEnumsDifferentValues.TM.json new file mode 100644 index 0000000000..c5201cd891 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesEnumsDifferentValues.TM.json @@ -0,0 +1,49 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "title": "MyEventSchema", + "type": "string", + "enum": [ + "foo", + "bar", + "baz" + ] + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + }, + "notice": { + "data": { + "title": "MyEventSchema", + "type": "string", + "enum": [ + "foo", + "bar" + ] + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/notice/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json new file mode 100644 index 0000000000..d31b14af73 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json @@ -0,0 +1,50 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "title": "MyEventSchema", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + }, + "notice": { + "data": { + "title": "MyEventSchema", + "type": "object", + "properties": { + "value": { + "type": "integer" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/notice/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesObjectsDifferentFields.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesObjectsDifferentFields.TM.json new file mode 100644 index 0000000000..0392e9618e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventDuplicateSchemaNamesObjectsDifferentFields.TM.json @@ -0,0 +1,50 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "title": "MyEventSchema", + "type": "object", + "properties": { + "alertVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + }, + "notice": { + "data": { + "title": "MyEventSchema", + "type": "object", + "properties": { + "noticeVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/notice/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormAdditionalResponses.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormAdditionalResponses.TM.json new file mode 100644 index 0000000000..d9f102cc2b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormAdditionalResponses.TM.json @@ -0,0 +1,43 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent", + "additionalResponses": [ + { + "success": false, + "contentType": "application/json", + "schema": "Error" + } + ] + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormContentTypeText.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormContentTypeText.TM.json new file mode 100644 index 0000000000..f690660b3d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormContentTypeText.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/text", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormHeaderCode.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormHeaderCode.TM.json new file mode 100644 index 0000000000..c9f17b9091 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormHeaderCode.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Status": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + } + }, + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent", + "dtv:headerCode": "Status" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormHeaderInfo.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormHeaderInfo.TM.json new file mode 100644 index 0000000000..027c271acd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormHeaderInfo.TM.json @@ -0,0 +1,43 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent", + "dtv:headerInfo": [ + { + "success": false, + "contentType": "application/json", + "schema": "Error" + } + ] + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormOpSubDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormOpSubDuplicate.TM.json new file mode 100644 index 0000000000..d49a22af55 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormOpSubDuplicate.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": [ "subscribeevent", "subscribeevent" ] + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormServiceGroupEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormServiceGroupEmpty.TM.json new file mode 100644 index 0000000000..2dec919aa3 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormServiceGroupEmpty.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent", + "dtv:serviceGroupId": "" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormTopicNoContentType.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicNoContentType.TM.json new file mode 100644 index 0000000000..a2baf7f9b4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicNoContentType.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenAction.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenAction.TM.json new file mode 100644 index 0000000000..e5aea7d66a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenAction.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/{action}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenConsumerId.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenConsumerId.TM.json new file mode 100644 index 0000000000..7abd4d0f93 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenConsumerId.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/{consumerClientId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenExecutorId.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenExecutorId.TM.json new file mode 100644 index 0000000000..01d1fc07dd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenExecutorId.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/{executorId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenInvokerId.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenInvokerId.TM.json new file mode 100644 index 0000000000..06db7612ff --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenInvokerId.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/{invokerClientId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenMaintainerId.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenMaintainerId.TM.json new file mode 100644 index 0000000000..3bad12286c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormTopicTokenMaintainerId.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/{maintainerId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormUnsupportedProperty.TM.json new file mode 100644 index 0000000000..b889b791f7 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormUnsupportedProperty.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent", + "foobar": "hello" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormsOpSubDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormsOpSubDuplicate.TM.json new file mode 100644 index 0000000000..19f323032b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormsOpSubDuplicate.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormsOplessAndSub.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormsOplessAndSub.TM.json new file mode 100644 index 0000000000..c580427efe --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormsOplessAndSub.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventFormsOplessDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventFormsOplessDuplicate.TM.json new file mode 100644 index 0000000000..a076a0cfb8 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventFormsOplessDuplicate.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventIntegerConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventIntegerConst.TM.json new file mode 100644 index 0000000000..527b720ccd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventIntegerConst.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "integer", + "const": 78 + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventMemberOf.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventMemberOf.TM.json new file mode 100644 index 0000000000..f8d7f0964d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventMemberOf.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "aov:memberOf": "MyPropertyGroup", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventNamespaceEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventNamespaceEmpty.TM.json new file mode 100644 index 0000000000..2ffab44326 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventNamespaceEmpty.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "aov:namespace": "", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventNamespaceWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventNamespaceWithoutAovContext.TM.json new file mode 100644 index 0000000000..53fcf3f2aa --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventNamespaceWithoutAovContext.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "aov:namespace": "MyNamespace", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventNoForms.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventNoForms.TM.json new file mode 100644 index 0000000000..9ca8d6c459 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventNoForms.TM.json @@ -0,0 +1,19 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + } + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventNoFormsWithTopicAndNoRootFormSubAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventNoFormsWithTopicAndNoRootFormSubAll.TM.json new file mode 100644 index 0000000000..4c2059b0f5 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventNoFormsWithTopicAndNoRootFormSubAll.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventNullAndContentTypeJson.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventNullAndContentTypeJson.TM.json new file mode 100644 index 0000000000..ad81096bf2 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventNullAndContentTypeJson.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "null" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventNumberConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventNumberConst.TM.json new file mode 100644 index 0000000000..b87cdf8385 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventNumberConst.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "number", + "const": 3.14 + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventObjectAndContentTypeCustom.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventObjectAndContentTypeCustom.TM.json new file mode 100644 index 0000000000..b93e9f7244 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventObjectAndContentTypeCustom.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "object", + "properties": { + "val": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventObjectAndContentTypeRaw.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventObjectAndContentTypeRaw.TM.json new file mode 100644 index 0000000000..c399aee72c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventObjectAndContentTypeRaw.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "object", + "properties": { + "val": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/octet-stream", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventObjectConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventObjectConst.TM.json new file mode 100644 index 0000000000..e0767b5ea5 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventObjectConst.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "object", + "properties": { + "val": { + "type": "string" + } + }, + "const": { + "val": "hello" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventObjectErrorMessage.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventObjectErrorMessage.TM.json new file mode 100644 index 0000000000..4a6a19a0d3 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventObjectErrorMessage.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "object", + "properties": { + "val": { + "type": "string" + } + }, + "dtv:errorMessage": "val" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventRefNotFound.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventRefNotFound.TM.json new file mode 100644 index 0000000000..2457204f09 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventRefNotFound.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "dtv:ref": "../../schemas/json-schemas/Nonexistent.json" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventStringConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventStringConst.TM.json new file mode 100644 index 0000000000..5cd7d78e89 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventStringConst.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string", + "const": "hello" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventUnsupportedProperty.TM.json new file mode 100644 index 0000000000..a3085c1c4b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventUnsupportedProperty.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeevent" + } + ], + "foobar": "hello" + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/EventsTopicDuplication.TM.json b/codegen2/test/thing-models/invalidAioBinding/EventsTopicDuplication.TM.json new file mode 100644 index 0000000000..6f3b493c1a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/EventsTopicDuplication.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "one": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/dup/TestThing", + "op": "subscribeevent" + } + ] + }, + "two": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/dup/TestThing" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/IntegerConstDecimalPlaces.TM.json b/codegen2/test/thing-models/invalidAioBinding/IntegerConstDecimalPlaces.TM.json new file mode 100644 index 0000000000..8f3f57250b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/IntegerConstDecimalPlaces.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "const": 50, + "aov:decimalPlaces": 2 + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "integer", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/IntegerConstScaleFactor.TM.json b/codegen2/test/thing-models/invalidAioBinding/IntegerConstScaleFactor.TM.json new file mode 100644 index 0000000000..0c78d049ac --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/IntegerConstScaleFactor.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "const": 50, + "aov:scaleFactor": 2 + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "number", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/IntegerDecimalPlacesNotInteger.TM.json b/codegen2/test/thing-models/invalidAioBinding/IntegerDecimalPlacesNotInteger.TM.json new file mode 100644 index 0000000000..bc0eb71e10 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/IntegerDecimalPlacesNotInteger.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "integer", + "aov:decimalPlaces": 2.2, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/IntegerDecimalPlacesWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/IntegerDecimalPlacesWithoutAovContext.TM.json new file mode 100644 index 0000000000..1df2152db5 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/IntegerDecimalPlacesWithoutAovContext.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "integer", + "aov:decimalPlaces": 2, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/IntegerScaleFactorNotInteger.TM.json b/codegen2/test/thing-models/invalidAioBinding/IntegerScaleFactorNotInteger.TM.json new file mode 100644 index 0000000000..c2c3594cba --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/IntegerScaleFactorNotInteger.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "integer", + "aov:scaleFactor": 2.2, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/IntegerScaleFactorWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/IntegerScaleFactorWithoutAovContext.TM.json new file mode 100644 index 0000000000..423fc03c3f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/IntegerScaleFactorWithoutAovContext.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "integer", + "aov:scaleFactor": 2, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/IsCompositeAndIsEvent.TM.json b/codegen2/test/thing-models/invalidAioBinding/IsCompositeAndIsEvent.TM.json new file mode 100644 index 0000000000..c23f413ec6 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/IsCompositeAndIsEvent.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "aov:isComposite": true, + "aov:isEvent": true, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/IsCompositeWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/IsCompositeWithoutAovContext.TM.json new file mode 100644 index 0000000000..d831ebe0da --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/IsCompositeWithoutAovContext.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "aov:isComposite": true, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/IsEventWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/IsEventWithoutAovContext.TM.json new file mode 100644 index 0000000000..741da9281f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/IsEventWithoutAovContext.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "aov:isEvent": true, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkCapabilityTypeJsonNotTm.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkCapabilityTypeJsonNotTm.TM.json new file mode 100644 index 0000000000..7d885bdad5 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkCapabilityTypeJsonNotTm.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:capability", + "href": "http://example.com/BasicOnOffTM", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkCapabilityWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkCapabilityWithoutAovContext.TM.json new file mode 100644 index 0000000000..d31aa2acf7 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkCapabilityWithoutAovContext.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:capability", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkComponentTypeJsonNotTm.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkComponentTypeJsonNotTm.TM.json new file mode 100644 index 0000000000..3dba5b3cf2 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkComponentTypeJsonNotTm.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:component", + "href": "http://example.com/BasicOnOffTM", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkComponentWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkComponentWithoutAovContext.TM.json new file mode 100644 index 0000000000..644f764e92 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkComponentWithoutAovContext.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:component", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkExtendsTypeJsonNotTm.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkExtendsTypeJsonNotTm.TM.json new file mode 100644 index 0000000000..40c26473b1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkExtendsTypeJsonNotTm.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "tm:extends", + "href": "http://example.com/BasicOnOffTM", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkNamingEmptyHref.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkNamingEmptyHref.TM.json new file mode 100644 index 0000000000..5faa1659f4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkNamingEmptyHref.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "dtv:naming", + "href": "", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkNamingEmptyType.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkNamingEmptyType.TM.json new file mode 100644 index 0000000000..0e436accc8 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkNamingEmptyType.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "dtv:naming", + "href": "../../name-rules/DefaultSchemaNames.json", + "type": "" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkNamingNoHref.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkNamingNoHref.TM.json new file mode 100644 index 0000000000..07b7619c30 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkNamingNoHref.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "dtv:naming", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkNamingNoType.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkNamingNoType.TM.json new file mode 100644 index 0000000000..a493f8f15f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkNamingNoType.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "dtv:naming", + "href": "../../name-rules/DefaultSchemaNames.json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkNamingNonJsonFile.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkNamingNonJsonFile.TM.json new file mode 100644 index 0000000000..e52b5d7b7c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkNamingNonJsonFile.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "dtv:naming", + "href": "../../Azure.Iot.Operations.ProtocolCompiler.UnitTests/TestCase.cs", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkNamingNonexistentFile.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkNamingNonexistentFile.TM.json new file mode 100644 index 0000000000..9e591bbb11 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkNamingNonexistentFile.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "dtv:naming", + "href": "../../Nonexistent.json", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkNamingTypeNotJson.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkNamingTypeNotJson.TM.json new file mode 100644 index 0000000000..cb0e13f852 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkNamingTypeNotJson.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "dtv:naming", + "href": "../../name-rules/DefaultSchemaNames.json", + "type": "application/text" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkNamingUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkNamingUnsupportedProperty.TM.json new file mode 100644 index 0000000000..5b778f297c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkNamingUnsupportedProperty.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "dtv:naming", + "href": "../../name-rules/DefaultSchemaNames.json", + "type": "application/json", + "foo": "bar" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkReferenceRefType.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkReferenceRefType.TM.json new file mode 100644 index 0000000000..94547d64b9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkReferenceRefType.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:reference", + "aov:refType": "MyReferenceType", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkReferenceTypeJsonNotTm.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkReferenceTypeJsonNotTm.TM.json new file mode 100644 index 0000000000..ab6ba545da --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkReferenceTypeJsonNotTm.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:reference", + "href": "http://example.com/BasicOnOffTM", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkReferenceWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkReferenceWithoutAovContext.TM.json new file mode 100644 index 0000000000..63732a226b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkReferenceWithoutAovContext.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:reference", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceNoRefType.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceNoRefType.TM.json new file mode 100644 index 0000000000..844eb5a294 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceNoRefType.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:typedReference", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceRefTypeEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceRefTypeEmpty.TM.json new file mode 100644 index 0000000000..f712c1f7c4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceRefTypeEmpty.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:typedReference", + "aov:refType": "", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceTypeJsonNotTm.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceTypeJsonNotTm.TM.json new file mode 100644 index 0000000000..b5ac7464bf --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceTypeJsonNotTm.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:typedReference", + "aov:refType": "MyReferenceType", + "href": "http://example.com/BasicOnOffTM", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceWithoutAovContext.TM.json new file mode 100644 index 0000000000..b11b69f09f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/LinkTypedReferenceWithoutAovContext.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:typedReference", + "aov:refType": "MyReferenceType", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/MultipleLinksNaming.TM.json b/codegen2/test/thing-models/invalidAioBinding/MultipleLinksNaming.TM.json new file mode 100644 index 0000000000..9bbba93738 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/MultipleLinksNaming.TM.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "dtv:naming", + "href": "../../name-rules/DefaultSchemaNames.json", + "type": "application/json" + }, + { + "rel": "dtv:naming", + "href": "../../name-rules/DefaultSchemaNames.json", + "type": "application/json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/NoTitle.TM.json b/codegen2/test/thing-models/invalidAioBinding/NoTitle.TM.json new file mode 100644 index 0000000000..9ab4f8a9b4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/NoTitle.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/NumberConstDecimalPlaces.TM.json b/codegen2/test/thing-models/invalidAioBinding/NumberConstDecimalPlaces.TM.json new file mode 100644 index 0000000000..e7c29ea078 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/NumberConstDecimalPlaces.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "const": 50, + "aov:decimalPlaces": 2 + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "number", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/NumberConstScaleFactor.TM.json b/codegen2/test/thing-models/invalidAioBinding/NumberConstScaleFactor.TM.json new file mode 100644 index 0000000000..ad4ec397c6 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/NumberConstScaleFactor.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "const": 50, + "aov:scaleFactor": 2 + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "number", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/NumberDecimalPlacesNotInteger.TM.json b/codegen2/test/thing-models/invalidAioBinding/NumberDecimalPlacesNotInteger.TM.json new file mode 100644 index 0000000000..0b473612e4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/NumberDecimalPlacesNotInteger.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "number", + "aov:decimalPlaces": 2.2, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/NumberDecimalPlacesWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/NumberDecimalPlacesWithoutAovContext.TM.json new file mode 100644 index 0000000000..1ee664f4dc --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/NumberDecimalPlacesWithoutAovContext.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "number", + "aov:decimalPlaces": 2, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/NumberScaleFactorWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/NumberScaleFactorWithoutAovContext.TM.json new file mode 100644 index 0000000000..f18092c2f3 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/NumberScaleFactorWithoutAovContext.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "number", + "aov:scaleFactor": 2.2, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ObjectPropertyNamespaceEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ObjectPropertyNamespaceEmpty.TM.json new file mode 100644 index 0000000000..689c2d0c2e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ObjectPropertyNamespaceEmpty.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Echo", + "actions": { + "echo": { + "input": { + "type": "object", + "properties": { + "value": { + "aov:namespace": "", + "type": "string" + } + } + }, + "output": { + "type": "object", + "properties": { + "value": { + "type": "integer" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ObjectPropertyNamespaceWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/ObjectPropertyNamespaceWithoutAovContext.TM.json new file mode 100644 index 0000000000..da578a739e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ObjectPropertyNamespaceWithoutAovContext.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Echo", + "actions": { + "echo": { + "input": { + "type": "object", + "properties": { + "value": { + "aov:namespace": "MyNamespace", + "type": "string" + } + } + }, + "output": { + "type": "object", + "properties": { + "value": { + "type": "integer" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ObjectTypeRefEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ObjectTypeRefEmpty.TM.json new file mode 100644 index 0000000000..b6d15233fc --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ObjectTypeRefEmpty.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Echo", + "actions": { + "echo": { + "input": { + "aov:typeRef": "", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "output": { + "type": "object", + "properties": { + "value": { + "type": "integer" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ObjectTypeRefWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/ObjectTypeRefWithoutAovContext.TM.json new file mode 100644 index 0000000000..a13bcb2ed2 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ObjectTypeRefWithoutAovContext.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Echo", + "actions": { + "echo": { + "input": { + "aov:typeRef": "SomeOtherItem", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "output": { + "type": "object", + "properties": { + "value": { + "type": "integer" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertiesReadTopicDuplication.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertiesReadTopicDuplication.TM.json new file mode 100644 index 0000000000..8b82b4f3a9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertiesReadTopicDuplication.TM.json @@ -0,0 +1,69 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "one": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{maintainerId}/read", + "op": "readproperty" + } + ] + }, + "two": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{maintainerId}/{action}", + "op": "readproperty" + } + ] + }, + "three": { + "type": "string", + "readOnly": true, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{maintainerId}/{action}" + } + ] + }, + "four": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{maintainerId}/{action}", + "op": [ "readproperty", "writeproperty" ] + } + ] + }, + "five": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/five/{maintainerId}/{action}", + "op": "writeproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{maintainerId}/{action}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertiesWriteTopicDuplication.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertiesWriteTopicDuplication.TM.json new file mode 100644 index 0000000000..290622eb30 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertiesWriteTopicDuplication.TM.json @@ -0,0 +1,53 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "one": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/one/{maintainerId}/read", + "op": "readproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{maintainerId}/write", + "op": "writeproperty" + } + ] + }, + "two": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/two/{maintainerId}/{action}", + "op": "readproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{maintainerId}/{action}", + "op": "writeproperty" + } + ] + }, + "three": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/dup/{maintainerId}/{action}" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyBooleanConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyBooleanConst.TM.json new file mode 100644 index 0000000000..56211fdcc8 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyBooleanConst.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "boolean", + "const": true, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInEmpty.TM.json new file mode 100644 index 0000000000..9252f1e43f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInEmpty.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "aov:containedIn": "", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInNoProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInNoProperty.TM.json new file mode 100644 index 0000000000..7702f337fc --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInNoProperty.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "aov:containedIn": "gamma", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInPropertyContainsOther.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInPropertyContainsOther.TM.json new file mode 100644 index 0000000000..65a70e292c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInPropertyContainsOther.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:contains": [ "gamma" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "aov:containedIn": "alpha", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + }, + "gamma": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/gamma/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInWithoutAovContext.TM.json new file mode 100644 index 0000000000..e84833ec71 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyContainedInWithoutAovContext.TM.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "aov:containedIn": "alpha", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyContainsEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyContainsEmpty.TM.json new file mode 100644 index 0000000000..6569cb4f1b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyContainsEmpty.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:contains": [ ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyContainsNoProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyContainsNoProperty.TM.json new file mode 100644 index 0000000000..c4a1b41b49 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyContainsNoProperty.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:contains": [ "gamma" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyContainsPropertyContainedInOther.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyContainsPropertyContainedInOther.TM.json new file mode 100644 index 0000000000..d56de22682 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyContainsPropertyContainedInOther.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:contains": [ "beta" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "aov:containedIn": "gamma", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + }, + "gamma": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/gamma/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyContainsWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyContainsWithoutAovContext.TM.json new file mode 100644 index 0000000000..5e1cbd28d4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyContainsWithoutAovContext.TM.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:contains": [ "beta" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesDifferentTypes.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesDifferentTypes.TM.json new file mode 100644 index 0000000000..8137290718 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesDifferentTypes.TM.json @@ -0,0 +1,45 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "title": "MyPropSchema", + "type": "object", + "properties": { + "alphaVal": { + "type": "string" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "title": "MyPropSchema", + "type": "string", + "enum": [ + "foo", + "bar" + ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesEnumsDifferentValues.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesEnumsDifferentValues.TM.json new file mode 100644 index 0000000000..2c0b64474d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesEnumsDifferentValues.TM.json @@ -0,0 +1,45 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "title": "MyPropSchema", + "type": "string", + "enum": [ + "foo", + "bar", + "baz" + ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "title": "MyPropSchema", + "type": "string", + "enum": [ + "foo", + "bar" + ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json new file mode 100644 index 0000000000..fd0c0c154c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesObjectsDifferentFieldValues.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "title": "MyPropSchema", + "type": "object", + "properties": { + "value": { + "type": "string" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "title": "MyPropSchema", + "type": "object", + "properties": { + "value": { + "type": "integer" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesObjectsDifferentFields.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesObjectsDifferentFields.TM.json new file mode 100644 index 0000000000..87fd722f9b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyDuplicateSchemaNamesObjectsDifferentFields.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "title": "MyPropSchema", + "type": "object", + "properties": { + "alphaVal": { + "type": "string" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "title": "MyPropSchema", + "type": "object", + "properties": { + "betaVal": { + "type": "string" + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseContentTypeEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseContentTypeEmpty.TM.json new file mode 100644 index 0000000000..e08570a60b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseContentTypeEmpty.TM.json @@ -0,0 +1,48 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error", + "contentType": "" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseContentTypeText.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseContentTypeText.TM.json new file mode 100644 index 0000000000..56e1e4d08f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseContentTypeText.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/text", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoMatchingSchemaDefinition.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoMatchingSchemaDefinition.TM.json new file mode 100644 index 0000000000..90604a5460 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoMatchingSchemaDefinition.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoSchema.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoSchema.TM.json new file mode 100644 index 0000000000..4abe79148c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoSchema.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoSchemaDefinitions.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoSchemaDefinitions.TM.json new file mode 100644 index 0000000000..e8f2dd98bd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoSchemaDefinitions.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoSuccess.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoSuccess.TM.json new file mode 100644 index 0000000000..d9a4829ac4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseNoSuccess.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "schema": "Error", + "contentType": "application/json" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSchemaDefinitionMap.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSchemaDefinitionMap.TM.json new file mode 100644 index 0000000000..e3f30eb514 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSchemaDefinitionMap.TM.json @@ -0,0 +1,45 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSchemaDefinitionNotObject.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSchemaDefinitionNotObject.TM.json new file mode 100644 index 0000000000..ddd91ff94b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSchemaDefinitionNotObject.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Condition" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSchemaEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSchemaEmpty.TM.json new file mode 100644 index 0000000000..4a1442d8c0 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSchemaEmpty.TM.json @@ -0,0 +1,48 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSuccessTrue.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSuccessTrue.TM.json new file mode 100644 index 0000000000..089464f610 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseSuccessTrue.TM.json @@ -0,0 +1,48 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": true, + "schema": "Error", + "contentType": "application/json" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseUnsupportedProperty.TM.json new file mode 100644 index 0000000000..1be2e2de47 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponseUnsupportedProperty.TM.json @@ -0,0 +1,49 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error", + "contentType": "application/json", + "foobar": "hello" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponsesMultiple.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponsesMultiple.TM.json new file mode 100644 index 0000000000..e0b1f73072 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormAdditionalResponsesMultiple.TM.json @@ -0,0 +1,52 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Condition": { + "type": "object", + "properties": { + "condition": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Condition" + }, + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormContentTypeText.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormContentTypeText.TM.json new file mode 100644 index 0000000000..3c6d81ceca --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormContentTypeText.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/text", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormHeaderCode.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormHeaderCode.TM.json new file mode 100644 index 0000000000..1cc3f99dc6 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormHeaderCode.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Status": { + "type": "string", + "enum": [ + "Success", + "Failure" + ] + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "dtv:headerCode": "Status" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormHeaderInfo.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormHeaderInfo.TM.json new file mode 100644 index 0000000000..428fea2de4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormHeaderInfo.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "dtv:headerInfo": [ + { + "success": false, + "contentType": "application/json", + "schema": "Error" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormOpReadDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormOpReadDuplicate.TM.json new file mode 100644 index 0000000000..36c47d8d16 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormOpReadDuplicate.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": [ "readproperty", "readproperty" ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormOpWriteDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormOpWriteDuplicate.TM.json new file mode 100644 index 0000000000..62bf967b68 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormOpWriteDuplicate.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": [ "readproperty", "writeproperty", "readproperty" ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormOplessAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormOplessAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json new file mode 100644 index 0000000000..d200ebf7ac --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormOplessAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json @@ -0,0 +1,45 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "forms": [ + { + "dtv:topic": "sample/all/events", + "contentType": "application/json", + "op": "readallproperties" + } + ], + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormOplessWithoutTopicAndNoRootFormReadAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormOplessWithoutTopicAndNoRootFormReadAll.TM.json new file mode 100644 index 0000000000..3aa9597242 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormOplessWithoutTopicAndNoRootFormReadAll.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormReadAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormReadAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json new file mode 100644 index 0000000000..3a0a2242bf --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormReadAdditionalResponsesNoTopicAndRootFormReadAllWithoutAdditionalResponses.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "forms": [ + { + "dtv:topic": "sample/all/events", + "contentType": "application/json", + "op": "readallproperties" + } + ], + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormReadWithoutTopicAndNoRootFormReadAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormReadWithoutTopicAndNoRootFormReadAll.TM.json new file mode 100644 index 0000000000..c0e2dda341 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormReadWithoutTopicAndNoRootFormReadAll.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormReadWithoutTopicWriteWithTopicAndRootFormReadAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormReadWithoutTopicWriteWithTopicAndRootFormReadAll.TM.json new file mode 100644 index 0000000000..f127127b1c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormReadWithoutTopicWriteWithTopicAndRootFormReadAll.TM.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/all/events", + "contentType": "application/json", + "op": "readallproperties" + } + ], + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "op": "readproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/write", + "op": "writeproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormServiceGroup.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormServiceGroup.TM.json new file mode 100644 index 0000000000..fb37abebc9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormServiceGroup.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "dtv:serviceGroupId": "MyGroup" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicNoContentType.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicNoContentType.TM.json new file mode 100644 index 0000000000..0cb5283c68 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicNoContentType.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenExecutorId.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenExecutorId.TM.json new file mode 100644 index 0000000000..20a26b5b2e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenExecutorId.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{executorId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenInvokerId.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenInvokerId.TM.json new file mode 100644 index 0000000000..e536826b43 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenInvokerId.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{invokerClientId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenNoActionWhenNoOpAndNoReadOnly.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenNoActionWhenNoOpAndNoReadOnly.TM.json new file mode 100644 index 0000000000..c932572622 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenNoActionWhenNoOpAndNoReadOnly.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenNoActionWhenNoOpAndReadOnlyFalse.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenNoActionWhenNoOpAndReadOnlyFalse.TM.json new file mode 100644 index 0000000000..7f52b6079f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenNoActionWhenNoOpAndReadOnlyFalse.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "readOnly": false, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenNoActionWhenReadAndWrite.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenNoActionWhenReadAndWrite.TM.json new file mode 100644 index 0000000000..1b4f594a0b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenNoActionWhenReadAndWrite.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha", + "op": [ "readproperty", "writeproperty" ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenSenderId.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenSenderId.TM.json new file mode 100644 index 0000000000..11228f9d81 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormTopicTokenSenderId.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormUnsupportedProperty.TM.json new file mode 100644 index 0000000000..9843d16484 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormUnsupportedProperty.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/read", + "op": "readproperty", + "foobar": "hello" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormWriteAdditionalResponsesNoTopicAndNoRootFormWriteMultiWithoutAdditionalResponses.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormWriteAdditionalResponsesNoTopicAndNoRootFormWriteMultiWithoutAdditionalResponses.TM.json new file mode 100644 index 0000000000..6dd743fe97 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormWriteAdditionalResponsesNoTopicAndNoRootFormWriteMultiWithoutAdditionalResponses.TM.json @@ -0,0 +1,66 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "forms": [ + { + "dtv:topic": "sample/all/events/read", + "contentType": "application/json", + "op": "readallproperties", + "additionalResponses": [ + { + "success": false + } + ] + }, + { + "dtv:topic": "sample/all/events/write", + "contentType": "application/json", + "op": "writemultipleproperties" + } + ], + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + }, + { + "contentType": "application/json", + "op": "writeproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormWriteWithoutTopicAndNoRootFormWriteMulti.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormWriteWithoutTopicAndNoRootFormWriteMulti.TM.json new file mode 100644 index 0000000000..94c0061a1f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormWriteWithoutTopicAndNoRootFormWriteMulti.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/read", + "op": "readproperty" + }, + { + "contentType": "application/json", + "op": "writeproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOpReadDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOpReadDuplicate.TM.json new file mode 100644 index 0000000000..9df2479ece --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOpReadDuplicate.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": [ "readproperty", "writeproperty" ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOpWriteDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOpWriteDuplicate.TM.json new file mode 100644 index 0000000000..b88a907ee6 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOpWriteDuplicate.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "writeproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": [ "readproperty", "writeproperty" ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOpWriteNoRead.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOpWriteNoRead.TM.json new file mode 100644 index 0000000000..e699dcdf2d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOpWriteNoRead.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/write", + "op": "writeproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOplessAndRead.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOplessAndRead.TM.json new file mode 100644 index 0000000000..8e4553c268 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOplessAndRead.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOplessDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOplessDuplicate.TM.json new file mode 100644 index 0000000000..85ba571b36 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyFormsOplessDuplicate.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyIntegerConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyIntegerConst.TM.json new file mode 100644 index 0000000000..6f1d550769 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyIntegerConst.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "integer", + "const": 78, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyMemberOf.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyMemberOf.TM.json new file mode 100644 index 0000000000..8a29a34f30 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyMemberOf.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:memberOf": "MyPropertyGroup", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyNamespaceEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyNamespaceEmpty.TM.json new file mode 100644 index 0000000000..83419ad6f8 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyNamespaceEmpty.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:namespace": "", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyNamespaceWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyNamespaceWithoutAovContext.TM.json new file mode 100644 index 0000000000..813e86e51b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyNamespaceWithoutAovContext.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:namespace": "MyNamespace", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyNoForms.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyNoForms.TM.json new file mode 100644 index 0000000000..db2aef10f9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyNoForms.TM.json @@ -0,0 +1,17 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string" + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyNumberConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyNumberConst.TM.json new file mode 100644 index 0000000000..e58f6a2966 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyNumberConst.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "number", + "const": 3.14, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyObjectConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyObjectConst.TM.json new file mode 100644 index 0000000000..668e6521f5 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyObjectConst.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "object", + "properties": { + "val": { + "type": "string" + } + }, + "const": { + "val": "hello" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyObjectErrorMessage.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyObjectErrorMessage.TM.json new file mode 100644 index 0000000000..f61da2b488 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyObjectErrorMessage.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "object", + "properties": { + "val": { + "type": "string" + } + }, + "dtv:errorMessage": "val", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyRefNotFound.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyRefNotFound.TM.json new file mode 100644 index 0000000000..50d3e0380c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyRefNotFound.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "dtv:ref": "../../schemas/json-schemas/Nonexistent.json", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyStringConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyStringConst.TM.json new file mode 100644 index 0000000000..23adbaac25 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyStringConst.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "const": "hello", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyTypeNull.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyTypeNull.TM.json new file mode 100644 index 0000000000..f20ff73568 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyTypeNull.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "null", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/PropertyUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/PropertyUnsupportedProperty.TM.json new file mode 100644 index 0000000000..7c309ef08d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/PropertyUnsupportedProperty.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/read", + "op": "readproperty" + } + ], + "foobar": "hello" + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseContentTypeEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseContentTypeEmpty.TM.json new file mode 100644 index 0000000000..e8827ae0c4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseContentTypeEmpty.TM.json @@ -0,0 +1,52 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/read", + "contentType": "application/json", + "additionalResponses": [ + { + "contentType": "", + "success": false + } + ], + "op": [ "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseContentTypeText.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseContentTypeText.TM.json new file mode 100644 index 0000000000..0be2614790 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseContentTypeText.TM.json @@ -0,0 +1,52 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/read", + "contentType": "application/json", + "additionalResponses": [ + { + "contentType": "application/text", + "success": false + } + ], + "op": [ "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseNoSuccess.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseNoSuccess.TM.json new file mode 100644 index 0000000000..004b5d3daf --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseNoSuccess.TM.json @@ -0,0 +1,51 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/read", + "contentType": "application/json", + "additionalResponses": [ + { + "contentType": "application/json" + } + ], + "op": [ "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseSchema.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseSchema.TM.json new file mode 100644 index 0000000000..9166837f77 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseSchema.TM.json @@ -0,0 +1,58 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "myResponse": { + "type": "string" + } + }, + "forms": [ + { + "dtv:topic": "sample/TestThing/read", + "contentType": "application/json", + "additionalResponses": [ + { + "contentType": "application/json", + "success": false, + "schema": "myResponse" + } + ], + "op": [ "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseSuccessTrue.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseSuccessTrue.TM.json new file mode 100644 index 0000000000..c41e006d4a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseSuccessTrue.TM.json @@ -0,0 +1,52 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/read", + "contentType": "application/json", + "additionalResponses": [ + { + "contentType": "application/json", + "success": true + } + ], + "op": [ "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseUnsupportedProperty.TM.json new file mode 100644 index 0000000000..e550294e7c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponseUnsupportedProperty.TM.json @@ -0,0 +1,53 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/read", + "contentType": "application/json", + "additionalResponses": [ + { + "contentType": "application/json", + "success": false, + "foobar": "hello" + } + ], + "op": [ "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponsesAndSubAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponsesAndSubAll.TM.json new file mode 100644 index 0000000000..9920a18b9a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponsesAndSubAll.TM.json @@ -0,0 +1,51 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "additionalResponses": [ + { + "contentType": "application/json" + } + ], + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponsesMultiple.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponsesMultiple.TM.json new file mode 100644 index 0000000000..75ae9a1ff1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormAdditionalResponsesMultiple.TM.json @@ -0,0 +1,56 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/read", + "contentType": "application/json", + "additionalResponses": [ + { + "contentType": "application/json", + "success": false + }, + { + "contentType": "application/json", + "success": false + } + ], + "op": [ "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormContentTypeCustom.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormContentTypeCustom.TM.json new file mode 100644 index 0000000000..a74e145271 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormContentTypeCustom.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormContentTypeRaw.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormContentTypeRaw.TM.json new file mode 100644 index 0000000000..deb158a273 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormContentTypeRaw.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/octet-stream", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormContentTypeText.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormContentTypeText.TM.json new file mode 100644 index 0000000000..46acdb59f3 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormContentTypeText.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/text", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormHeaderCode.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormHeaderCode.TM.json new file mode 100644 index 0000000000..4bf5a9982b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormHeaderCode.TM.json @@ -0,0 +1,56 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "myHeader": { + "type": "string", + "enum": [ + "Hello", + "Goodbye" + ] + } + }, + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "dtv:headerCode": "myHeader", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormHeaderInfo.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormHeaderInfo.TM.json new file mode 100644 index 0000000000..54e67e4a41 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormHeaderInfo.TM.json @@ -0,0 +1,61 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "myHeader": { + "type": "string", + "enum": [ + "Hello", + "Goodbye" + ] + } + }, + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "dtv:headerInfo": [ + { + "contentType": "application/json", + "schema": "myHeader" + } + ], + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormNoContentType.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormNoContentType.TM.json new file mode 100644 index 0000000000..3640b0a4d1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormNoContentType.TM.json @@ -0,0 +1,45 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormNoTopic.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormNoTopic.TM.json new file mode 100644 index 0000000000..86f5d2de67 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormNoTopic.TM.json @@ -0,0 +1,45 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormOpReadAllAndSubAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormOpReadAllAndSubAll.TM.json new file mode 100644 index 0000000000..ff7334104c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormOpReadAllAndSubAll.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "op": [ "readallproperties", "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormOpReadAllDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormOpReadAllDuplicate.TM.json new file mode 100644 index 0000000000..8fd2cdc353 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormOpReadAllDuplicate.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "op": [ "readallproperties", "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormOpSubAllDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormOpSubAllDuplicate.TM.json new file mode 100644 index 0000000000..9d53ba76d4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormOpSubAllDuplicate.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "op": [ "subscribeallevents", "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormOpWiteMultiAndSubAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormOpWiteMultiAndSubAll.TM.json new file mode 100644 index 0000000000..19de8a9f4a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormOpWiteMultiAndSubAll.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "op": [ "writemultipleproperties", "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormOpWriteMultiDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormOpWriteMultiDuplicate.TM.json new file mode 100644 index 0000000000..25bf971c72 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormOpWriteMultiDuplicate.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "op": [ "writemultipleproperties", "writemultipleproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormReadAllButNoProperties.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormReadAllButNoProperties.TM.json new file mode 100644 index 0000000000..29f1848a42 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormReadAllButNoProperties.TM.json @@ -0,0 +1,21 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/all/events", + "contentType": "application/json", + "op": "readallproperties" + } + ], + "actions": { + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormServiceGroupEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormServiceGroupEmpty.TM.json new file mode 100644 index 0000000000..1b8e0eaefb --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormServiceGroupEmpty.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "dtv:serviceGroupId": "", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormServiceGroupNoSubAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormServiceGroupNoSubAll.TM.json new file mode 100644 index 0000000000..9b46425990 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormServiceGroupNoSubAll.TM.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/read", + "contentType": "application/json", + "dtv:serviceGroupId": "MyGroup", + "op": [ "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormSubAllButNoEvents.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormSubAllButNoEvents.TM.json new file mode 100644 index 0000000000..68de693517 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormSubAllButNoEvents.TM.json @@ -0,0 +1,21 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/all/events", + "contentType": "application/json", + "op": "subscribeallevents" + } + ], + "actions": { + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicEmpty.TM.json new file mode 100644 index 0000000000..e390d98362 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicEmpty.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicLevelEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicLevelEmpty.TM.json new file mode 100644 index 0000000000..baca2d67c6 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicLevelEmpty.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample//events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicLevelInvalid.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicLevelInvalid.TM.json new file mode 100644 index 0000000000..47dc5c000e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicLevelInvalid.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/foo+bar/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicReservedStart.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicReservedStart.TM.json new file mode 100644 index 0000000000..b62bafcb9a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicReservedStart.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "$sample/TestThing/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenActionWhenSubAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenActionWhenSubAll.TM.json new file mode 100644 index 0000000000..2d28981624 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenActionWhenSubAll.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{action}/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenConsumerIdWhenSubAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenConsumerIdWhenSubAll.TM.json new file mode 100644 index 0000000000..1f2d95ddae --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenConsumerIdWhenSubAll.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{consumerClientId}/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenCustomEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenCustomEmpty.TM.json new file mode 100644 index 0000000000..a4ab3da93e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenCustomEmpty.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{ex:}/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenCustomInvalid.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenCustomInvalid.TM.json new file mode 100644 index 0000000000..82b7cba714 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenCustomInvalid.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{ex:foo1}/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenEmpty.TM.json new file mode 100644 index 0000000000..a8b2b80e0a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenEmpty.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{}/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenExecutorId.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenExecutorId.TM.json new file mode 100644 index 0000000000..6ce40f674f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenExecutorId.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{executorId}/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenInvokerId.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenInvokerId.TM.json new file mode 100644 index 0000000000..9d20dfa7c4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenInvokerId.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{invokerClientId}/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenMaintainerIdWhenSubAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenMaintainerIdWhenSubAll.TM.json new file mode 100644 index 0000000000..fbfad76277 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenMaintainerIdWhenSubAll.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{maintainerId}/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenNoActionWhenReadAllAndWriteMulti.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenNoActionWhenReadAllAndWriteMulti.TM.json new file mode 100644 index 0000000000..376c6b3d87 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenNoActionWhenReadAllAndWriteMulti.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "op": [ "readallproperties", "writemultipleproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenSenderIdWhenReadAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenSenderIdWhenReadAll.TM.json new file mode 100644 index 0000000000..150b475548 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenSenderIdWhenReadAll.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{senderId}/events", + "contentType": "application/json", + "op": [ "readallproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenSenderIdWhenWriteMulti.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenSenderIdWhenWriteMulti.TM.json new file mode 100644 index 0000000000..22ecd416f1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenSenderIdWhenWriteMulti.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{senderId}/events", + "contentType": "application/json", + "op": [ "writemultipleproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenUnrecognized.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenUnrecognized.TM.json new file mode 100644 index 0000000000..20ae24b0de --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormTopicTokenUnrecognized.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/{foobar}/events", + "contentType": "application/json", + "op": [ "subscribeallevents" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormWriteMultiButNoWritableProperties.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormWriteMultiButNoWritableProperties.TM.json new file mode 100644 index 0000000000..d6e83e124c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormWriteMultiButNoWritableProperties.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/all/events/read", + "contentType": "application/json", + "op": "readallproperties" + }, + { + "dtv:topic": "sample/all/events/write", + "contentType": "application/json", + "op": "writemultipleproperties" + } + ], + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "readOnly": true, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}" + } + ] + }, + "beta": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormsOpReadAllDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormsOpReadAllDuplicate.TM.json new file mode 100644 index 0000000000..9e05591149 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormsOpReadAllDuplicate.TM.json @@ -0,0 +1,51 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/read", + "contentType": "application/json", + "op": "readallproperties" + }, + { + "dtv:topic": "sample/{action}/props", + "contentType": "application/json", + "op": [ "readallproperties", "writemultipleproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormsOpSubAllDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormsOpSubAllDuplicate.TM.json new file mode 100644 index 0000000000..7a4bd31941 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormsOpSubAllDuplicate.TM.json @@ -0,0 +1,51 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/events", + "contentType": "application/json", + "op": "subscribeallevents" + }, + { + "dtv:topic": "sample/all/events", + "contentType": "application/json", + "op": "subscribeallevents" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormsOpWriteMultiDuplicate.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormsOpWriteMultiDuplicate.TM.json new file mode 100644 index 0000000000..42939701af --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormsOpWriteMultiDuplicate.TM.json @@ -0,0 +1,51 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/write", + "contentType": "application/json", + "op": "writemultipleproperties" + }, + { + "dtv:topic": "sample/{action}/props", + "contentType": "application/json", + "op": [ "readallproperties", "writemultipleproperties" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/RootFormsOpWriteMultiNoReadAll.TM.json b/codegen2/test/thing-models/invalidAioBinding/RootFormsOpWriteMultiNoReadAll.TM.json new file mode 100644 index 0000000000..bd0fa682af --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/RootFormsOpWriteMultiNoReadAll.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "dtv:topic": "sample/TestThing/write", + "contentType": "application/json", + "op": "writemultipleproperties" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayConst.TM.json new file mode 100644 index 0000000000..03a0d3649a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayConst.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array", + "items": { + "type": "string" + }, + "const": [ "foo", "bar" ] + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsNoType.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsNoType.TM.json new file mode 100644 index 0000000000..7495a821e3 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsNoType.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array", + "items": { + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsObjectConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsObjectConst.TM.json new file mode 100644 index 0000000000..2a58f4fd03 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsObjectConst.TM.json @@ -0,0 +1,39 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "const": { + "bar": "hello" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsRef.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsRef.TM.json new file mode 100644 index 0000000000..d5989e4371 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsRef.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array", + "items": { + "dtv:ref": "../../schemas/json-schemas/AnObjectSchema.json" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsStringConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsStringConst.TM.json new file mode 100644 index 0000000000..980967f710 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsStringConst.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array", + "items": { + "type": "string", + "const": "hello" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsTypeNull.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsTypeNull.TM.json new file mode 100644 index 0000000000..dd827a6ad0 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayItemsTypeNull.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array", + "items": { + "type": "null" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayNoItems.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayNoItems.TM.json new file mode 100644 index 0000000000..496d934d25 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionArrayNoItems.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionBooleanConstValueNotBoolean.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionBooleanConstValueNotBoolean.TM.json new file mode 100644 index 0000000000..6f66703b55 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionBooleanConstValueNotBoolean.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "boolean", + "const": "false" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionBooleanReadOnly.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionBooleanReadOnly.TM.json new file mode 100644 index 0000000000..ed3665f37f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionBooleanReadOnly.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "boolean", + "readOnly": true + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionBooleanUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionBooleanUnsupportedProperty.TM.json new file mode 100644 index 0000000000..57158305cf --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionBooleanUnsupportedProperty.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "boolean", + "foo": "hello" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueAboveMax.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueAboveMax.TM.json new file mode 100644 index 0000000000..10f95f3e45 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueAboveMax.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "maximum": 10, + "const": 15 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueBelowMin.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueBelowMin.TM.json new file mode 100644 index 0000000000..3a4a63e118 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueBelowMin.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "minimum": 10, + "const": 5 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueNotInteger.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueNotInteger.TM.json new file mode 100644 index 0000000000..b797bc69ff --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueNotInteger.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "const": 2.345 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueNotNumeric.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueNotNumeric.TM.json new file mode 100644 index 0000000000..5f5faff42e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerConstValueNotNumeric.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "const": "two" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerMaxNotInteger.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerMaxNotInteger.TM.json new file mode 100644 index 0000000000..fa315fcb49 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerMaxNotInteger.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "minimum": 10, + "maximum": 49.5 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerMinAboveMax.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerMinAboveMax.TM.json new file mode 100644 index 0000000000..805352f8d7 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerMinAboveMax.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "minimum": 10, + "maximum": 5 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerMinNotInteger.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerMinNotInteger.TM.json new file mode 100644 index 0000000000..6a97347528 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerMinNotInteger.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "minimum": 10.5, + "maximum": 50 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerReadOnly.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerReadOnly.TM.json new file mode 100644 index 0000000000..c34baf48f3 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerReadOnly.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "readOnly": true + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerUnsupportedProperty.TM.json new file mode 100644 index 0000000000..484a62c67f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionIntegerUnsupportedProperty.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "foo": "hello" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesNoType.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesNoType.TM.json new file mode 100644 index 0000000000..7f976d0d21 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesNoType.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "dtv:additionalProperties": { + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesObjectConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesObjectConst.TM.json new file mode 100644 index 0000000000..98568086a4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesObjectConst.TM.json @@ -0,0 +1,39 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "dtv:additionalProperties": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "const": { + "bar": "hello" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesRef.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesRef.TM.json new file mode 100644 index 0000000000..39309a9f74 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesRef.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "dtv:additionalProperties": { + "dtv:ref": "../../schemas/json-schemas/AnObjectSchema.json" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesStringConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesStringConst.TM.json new file mode 100644 index 0000000000..8cfa4281cc --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesStringConst.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "dtv:additionalProperties": { + "type": "string", + "const": "hello" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesTypeNull.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesTypeNull.TM.json new file mode 100644 index 0000000000..da8b97572b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapAdditionalPropertiesTypeNull.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "dtv:additionalProperties": { + "type": "null" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapConst.TM.json new file mode 100644 index 0000000000..1eb454045c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionMapConst.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "dtv:additionalProperties": { + "type": "string" + }, + "const": { + "foo": "hello" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNoType.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNoType.TM.json new file mode 100644 index 0000000000..8219246baa --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNoType.TM.json @@ -0,0 +1,27 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberConstValueAboveMax.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberConstValueAboveMax.TM.json new file mode 100644 index 0000000000..a900cda691 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberConstValueAboveMax.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "maximum": 10, + "const": 15 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberConstValueBelowMin.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberConstValueBelowMin.TM.json new file mode 100644 index 0000000000..e87f67bead --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberConstValueBelowMin.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "minimum": 10, + "const": 5 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberConstValueNotNumeric.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberConstValueNotNumeric.TM.json new file mode 100644 index 0000000000..a743c33985 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberConstValueNotNumeric.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "const": "two" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberMinAboveMax.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberMinAboveMax.TM.json new file mode 100644 index 0000000000..bd6fefd63e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberMinAboveMax.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "minimum": 10, + "maximum": 5 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberReadOnly.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberReadOnly.TM.json new file mode 100644 index 0000000000..29456cf262 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberReadOnly.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "readOnly": true + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberUnsupportedProperty.TM.json new file mode 100644 index 0000000000..5263a274a8 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionNumberUnsupportedProperty.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "foo": "hello" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectBothDeterminants.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectBothDeterminants.TM.json new file mode 100644 index 0000000000..6abb404ff3 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectBothDeterminants.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "dtv:additionalProperties": { + "type": "string" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstNotObject.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstNotObject.TM.json new file mode 100644 index 0000000000..d94a7b4e18 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstNotObject.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "const": "hello" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanConst.TM.json new file mode 100644 index 0000000000..4b7acdac74 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanConst.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "boolean", + "const": true + } + }, + "const": { + "foo": true + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanNotBoolean.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanNotBoolean.TM.json new file mode 100644 index 0000000000..e8eccd5a37 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanNotBoolean.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "boolean" + } + }, + "const": { + "foo": "true" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanUnsupportedProperty.TM.json new file mode 100644 index 0000000000..9327d4b25f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyBooleanUnsupportedProperty.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "boolean", + "foo": "bar" + } + }, + "const": { + "foo": true + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerAboveMax.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerAboveMax.TM.json new file mode 100644 index 0000000000..3047e01aa5 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerAboveMax.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "maximum": 10 + } + }, + "const": { + "foo": 15 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerBelowMin.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerBelowMin.TM.json new file mode 100644 index 0000000000..b272abe421 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerBelowMin.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "minimum": 10 + } + }, + "const": { + "foo": 5 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerConst.TM.json new file mode 100644 index 0000000000..cc260dab1f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerConst.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "const": 616 + } + }, + "const": { + "foo": 616 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerNotInteger.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerNotInteger.TM.json new file mode 100644 index 0000000000..1823eddead --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerNotInteger.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "integer" + } + }, + "const": { + "foo": 3.33 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerNotNumeric.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerNotNumeric.TM.json new file mode 100644 index 0000000000..875e8aa29e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerNotNumeric.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "integer" + } + }, + "const": { + "foo": "three" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerUnsupportedProperty.TM.json new file mode 100644 index 0000000000..321a6bab2a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyIntegerUnsupportedProperty.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "integer", + "foo": "bar" + } + }, + "const": { + "foo": 98052 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNoType.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNoType.TM.json new file mode 100644 index 0000000000..cbdecaed3c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNoType.TM.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + } + }, + "const": { + "foo": "hello" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNoValue.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNoValue.TM.json new file mode 100644 index 0000000000..15c4708b2d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNoValue.TM.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "const": { + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberAboveMax.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberAboveMax.TM.json new file mode 100644 index 0000000000..a4a8bdd7fe --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberAboveMax.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "number", + "maximum": 10 + } + }, + "const": { + "foo": 15 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberBelowMin.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberBelowMin.TM.json new file mode 100644 index 0000000000..8a3cf70273 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberBelowMin.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "number", + "minimum": 10 + } + }, + "const": { + "foo": 5 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberConst.TM.json new file mode 100644 index 0000000000..296a71394b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberConst.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "number", + "const": 616 + } + }, + "const": { + "foo": 616 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberNotNumeric.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberNotNumeric.TM.json new file mode 100644 index 0000000000..a1ab42ecbf --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberNotNumeric.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "number" + } + }, + "const": { + "foo": "three" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberUnsupportedProperty.TM.json new file mode 100644 index 0000000000..2477907c91 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyNumberUnsupportedProperty.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "number", + "foo": "bar" + } + }, + "const": { + "foo": 98052 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyStringConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyStringConst.TM.json new file mode 100644 index 0000000000..38540434f9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyStringConst.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string", + "const": "hello" + } + }, + "const": { + "foo": "hello" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyStringUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyStringUnsupportedProperty.TM.json new file mode 100644 index 0000000000..e94d5aaa4b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyStringUnsupportedProperty.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string", + "format": "date" + } + }, + "const": { + "foo": "7/8/2010" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyStringValueNotString.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyStringValueNotString.TM.json new file mode 100644 index 0000000000..480d7df36c --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyStringValueNotString.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "const": { + "foo": 538 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeArray.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeArray.TM.json new file mode 100644 index 0000000000..eb99bb0dc9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeArray.TM.json @@ -0,0 +1,39 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "const": { + "foo": [ "hello", "goodbye" ] + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeNull.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeNull.TM.json new file mode 100644 index 0000000000..946af0be90 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeNull.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "null" + } + }, + "const": { + "foo": "hello" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeObject.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeObject.TM.json new file mode 100644 index 0000000000..b30e17e190 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstPropertyTypeObject.TM.json @@ -0,0 +1,43 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + } + } + }, + "const": { + "foo": { + "bar": "hello" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstUnsupportedProperty.TM.json new file mode 100644 index 0000000000..b1f32f7a99 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstUnsupportedProperty.TM.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ "foo" ], + "const": { + "foo": "hello" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstValueNoSchema.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstValueNoSchema.TM.json new file mode 100644 index 0000000000..6d4d077a5f --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectConstValueNoSchema.TM.json @@ -0,0 +1,33 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + }, + "const": { + "foo": "hello" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectErrorMessageNoProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectErrorMessageNoProperty.TM.json new file mode 100644 index 0000000000..88f174b1ed --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectErrorMessageNoProperty.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + }, + "dtv:errorMessage": "foo" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectErrorMessageNotString.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectErrorMessageNotString.TM.json new file mode 100644 index 0000000000..13bc2bd3ee --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectErrorMessageNotString.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "dtv:errorMessage": 555 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectErrorMessagePropertyNotString.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectErrorMessagePropertyNotString.TM.json new file mode 100644 index 0000000000..20313acdf9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectErrorMessagePropertyNotString.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "integer" + } + }, + "dtv:errorMessage": "foo" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectNoDeterminant.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectNoDeterminant.TM.json new file mode 100644 index 0000000000..e9404c21e8 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectNoDeterminant.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertiesNotObject.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertiesNotObject.TM.json new file mode 100644 index 0000000000..4649cb4c4d --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertiesNotObject.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": [ "foo" ] + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyNoType.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyNoType.TM.json new file mode 100644 index 0000000000..ea4f12e60b --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyNoType.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyObjectConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyObjectConst.TM.json new file mode 100644 index 0000000000..6494aef209 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyObjectConst.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "const": { + "bar": "hello" + } + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyRef.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyRef.TM.json new file mode 100644 index 0000000000..93de32a1a0 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyRef.TM.json @@ -0,0 +1,33 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "dtv:ref": "../../schemas/json-schemas/AnObjectSchema.json" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyStringConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyStringConst.TM.json new file mode 100644 index 0000000000..e16c9ff967 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyStringConst.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string", + "const": "hello" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyTypeNull.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyTypeNull.TM.json new file mode 100644 index 0000000000..6c876edd40 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectPropertyTypeNull.TM.json @@ -0,0 +1,33 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "null" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectRequiredNoProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectRequiredNoProperty.TM.json new file mode 100644 index 0000000000..ecd9c3e467 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionObjectRequiredNoProperty.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + }, + "required": [ "foo" ] + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionRef.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionRef.TM.json new file mode 100644 index 0000000000..511769c054 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionRef.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "dtv:ref": "../../schemas/json-schemas/AnObjectSchema.json" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstContentEncoding.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstContentEncoding.TM.json new file mode 100644 index 0000000000..a597f96775 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstContentEncoding.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "const": "foo", + "contentEncoding": "base64" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstFormat.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstFormat.TM.json new file mode 100644 index 0000000000..25b16185c9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstFormat.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "const": "2/2/2022", + "format": "date" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstPattern.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstPattern.TM.json new file mode 100644 index 0000000000..18ec3a4d62 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstPattern.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "const": "222", + "pattern": "^(?:\\+|-)?(?:[1-9][0-9]*|0)(?:\\.[0-9]*)?$" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstUnsupportedProperty.TM.json new file mode 100644 index 0000000000..7e3724ac01 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstUnsupportedProperty.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "const": "bar", + "foo": "hello" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstValueNotString.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstValueNotString.TM.json new file mode 100644 index 0000000000..3b05555ddc --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringConstValueNotString.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "const": 678 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringContentEncodingUnsupportedValue.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringContentEncodingUnsupportedValue.TM.json new file mode 100644 index 0000000000..ea265ef90e --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringContentEncodingUnsupportedValue.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "contentEncoding": "foobar" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumConst.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumConst.TM.json new file mode 100644 index 0000000000..bd40859e50 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumConst.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "enum": [ "foo", "bar" ], + "const": "bar" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumContentEncoding.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumContentEncoding.TM.json new file mode 100644 index 0000000000..44c2431798 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumContentEncoding.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "enum": [ "foo", "bar" ], + "contentEncoding": "base64" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumElementInvalid.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumElementInvalid.TM.json new file mode 100644 index 0000000000..f766b2f0cc --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumElementInvalid.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "enum": [ 222, "bar" ] + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumFormat.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumFormat.TM.json new file mode 100644 index 0000000000..55711e46a0 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumFormat.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "enum": [ "foo", "bar" ], + "format": "date" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumPattern.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumPattern.TM.json new file mode 100644 index 0000000000..a8c8bf3136 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumPattern.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "enum": [ "foo", "bar" ], + "pattern": "^(?:\\+|-)?(?:[1-9][0-9]*|0)(?:\\.[0-9]*)?$" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumReadOnly.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumReadOnly.TM.json new file mode 100644 index 0000000000..d19ec362d4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumReadOnly.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "enum": [ "foo", "bar" ], + "readOnly": true + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumUnsupportedProperty.TM.json new file mode 100644 index 0000000000..5565edec24 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringEnumUnsupportedProperty.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "enum": [ "foo", "bar" ], + "foo": "hello" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringFormatAndContentEncoding.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringFormatAndContentEncoding.TM.json new file mode 100644 index 0000000000..923b965bbf --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringFormatAndContentEncoding.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "format": "date", + "contentEncoding": "base64" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringFormatAndPattern.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringFormatAndPattern.TM.json new file mode 100644 index 0000000000..ac97ff24af --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringFormatAndPattern.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "format": "date", + "pattern": "^(?:\\+|-)?(?:[1-9][0-9]*|0)(?:\\.[0-9]*)?$" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringFormatUnsupportedValue.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringFormatUnsupportedValue.TM.json new file mode 100644 index 0000000000..bee67fe6df --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringFormatUnsupportedValue.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "format": "foobar" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringPatternAndContentEncoding.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringPatternAndContentEncoding.TM.json new file mode 100644 index 0000000000..3ddea039e4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringPatternAndContentEncoding.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "pattern": "^(?:\\+|-)?(?:[1-9][0-9]*|0)(?:\\.[0-9]*)?$", + "contentEncoding": "base64" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringPatternNotDurationOrDecimal.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringPatternNotDurationOrDecimal.TM.json new file mode 100644 index 0000000000..d752c93ec4 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringPatternNotDurationOrDecimal.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "pattern": "^a+$" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringPatternRegexInvalid.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringPatternRegexInvalid.TM.json new file mode 100644 index 0000000000..dd49659682 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringPatternRegexInvalid.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "pattern": "*??" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringReadOnly.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringReadOnly.TM.json new file mode 100644 index 0000000000..4c3d56c4fd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringReadOnly.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "readOnly": true + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringUnsupportedProperty.TM.json new file mode 100644 index 0000000000..71e176f0d1 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionStringUnsupportedProperty.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "foo": "hello" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionTypeNull.TM.json b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionTypeNull.TM.json new file mode 100644 index 0000000000..9a5bdb88fc --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/SchemaDefinitionTypeNull.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "null" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/StringDecimalPlaces.TM.json b/codegen2/test/thing-models/invalidAioBinding/StringDecimalPlaces.TM.json new file mode 100644 index 0000000000..f44c1965cc --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/StringDecimalPlaces.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:decimalPlaces": 2, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/StringScaleFactor.TM.json b/codegen2/test/thing-models/invalidAioBinding/StringScaleFactor.TM.json new file mode 100644 index 0000000000..fca2e3afbd --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/StringScaleFactor.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:scaleFactor": 2, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/ThingUnsupportedProperty.TM.json b/codegen2/test/thing-models/invalidAioBinding/ThingUnsupportedProperty.TM.json new file mode 100644 index 0000000000..684ed9984a --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/ThingUnsupportedProperty.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "foobar": "hello", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/TitleEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/TitleEmpty.TM.json new file mode 100644 index 0000000000..0d75c0e177 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/TitleEmpty.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/TitleInvalid.TM.json b/codegen2/test/thing-models/invalidAioBinding/TitleInvalid.TM.json new file mode 100644 index 0000000000..253399d824 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/TitleInvalid.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Not codegen legal", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/TypeRefEmpty.TM.json b/codegen2/test/thing-models/invalidAioBinding/TypeRefEmpty.TM.json new file mode 100644 index 0000000000..40eb5a31b9 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/TypeRefEmpty.TM.json @@ -0,0 +1,25 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "aov:typeRef": "", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidAioBinding/TypeRefWithoutAovContext.TM.json b/codegen2/test/thing-models/invalidAioBinding/TypeRefWithoutAovContext.TM.json new file mode 100644 index 0000000000..a2fc6e68a7 --- /dev/null +++ b/codegen2/test/thing-models/invalidAioBinding/TypeRefWithoutAovContext.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "aov:typeRef": "SomeOtherModel", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/ArrayKey.TM.json b/codegen2/test/thing-models/invalidJson/ArrayKey.TM.json new file mode 100644 index 0000000000..d46866781a --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/ArrayKey.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + [ "contentType" ]: "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/BooleanKey.TM.json b/codegen2/test/thing-models/invalidJson/BooleanKey.TM.json new file mode 100644 index 0000000000..3d238780ad --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/BooleanKey.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + true: "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/DuplicateKey.TM.json b/codegen2/test/thing-models/invalidJson/DuplicateKey.TM.json new file mode 100644 index 0000000000..29239df868 --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/DuplicateKey.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "contentType": "application/text", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/MissingColon.TM.json b/codegen2/test/thing-models/invalidJson/MissingColon.TM.json new file mode 100644 index 0000000000..814da81651 --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/MissingColon.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType" "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/MissingComma.TM.json b/codegen2/test/thing-models/invalidJson/MissingComma.TM.json new file mode 100644 index 0000000000..30756fdeba --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/MissingComma.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json" + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/MissingElement.TM.json b/codegen2/test/thing-models/invalidJson/MissingElement.TM.json new file mode 100644 index 0000000000..17e690bc6c --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/MissingElement.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + }, + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/MissingKey.TM.json b/codegen2/test/thing-models/invalidJson/MissingKey.TM.json new file mode 100644 index 0000000000..d3d6943553 --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/MissingKey.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + : "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/MissingValue.TM.json b/codegen2/test/thing-models/invalidJson/MissingValue.TM.json new file mode 100644 index 0000000000..316e419c2a --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/MissingValue.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": , + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/NumericKey.TM.json b/codegen2/test/thing-models/invalidJson/NumericKey.TM.json new file mode 100644 index 0000000000..42641bd6a5 --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/NumericKey.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + 626: "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/ObjectKey.TM.json b/codegen2/test/thing-models/invalidJson/ObjectKey.TM.json new file mode 100644 index 0000000000..414478f81c --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/ObjectKey.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + { "contentType": true }: "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/TrailingComma.TM.json b/codegen2/test/thing-models/invalidJson/TrailingComma.TM.json new file mode 100644 index 0000000000..799834361e --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/TrailingComma.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction", + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/UnclosedArray.TM.json b/codegen2/test/thing-models/invalidJson/UnclosedArray.TM.json new file mode 100644 index 0000000000..c55cc55fa3 --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/UnclosedArray.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidJson/UnclosedObject.TM.json b/codegen2/test/thing-models/invalidJson/UnclosedObject.TM.json new file mode 100644 index 0000000000..7aa17db02c --- /dev/null +++ b/codegen2/test/thing-models/invalidJson/UnclosedObject.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/ActionFormOpRead.TM.json b/codegen2/test/thing-models/invalidThing/ActionFormOpRead.TM.json new file mode 100644 index 0000000000..bf811bf3de --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/ActionFormOpRead.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "readproperty" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/ActionFormOpSub.TM.json b/codegen2/test/thing-models/invalidThing/ActionFormOpSub.TM.json new file mode 100644 index 0000000000..d0e8141de1 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/ActionFormOpSub.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "subscribeevent" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/ActionFormOpWrite.TM.json b/codegen2/test/thing-models/invalidThing/ActionFormOpWrite.TM.json new file mode 100644 index 0000000000..da9982d7fa --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/ActionFormOpWrite.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/noop/{executorId}", + "op": "writeproperty" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/ActionFormsEmpty.TM.json b/codegen2/test/thing-models/invalidThing/ActionFormsEmpty.TM.json new file mode 100644 index 0000000000..7b5788d72e --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/ActionFormsEmpty.TM.json @@ -0,0 +1,18 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/ContextMissingWot.TM.json b/codegen2/test/thing-models/invalidThing/ContextMissingWot.TM.json new file mode 100644 index 0000000000..80124e3b98 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/ContextMissingWot.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/ContextWrongType.TM.json b/codegen2/test/thing-models/invalidThing/ContextWrongType.TM.json new file mode 100644 index 0000000000..ddf652eeef --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/ContextWrongType.TM.json @@ -0,0 +1,20 @@ +{ + "@context": 404, + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/EventFormOpInvoke.TM.json b/codegen2/test/thing-models/invalidThing/EventFormOpInvoke.TM.json new file mode 100644 index 0000000000..132d98d03a --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/EventFormOpInvoke.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "invokeaction" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/EventFormOpRead.TM.json b/codegen2/test/thing-models/invalidThing/EventFormOpRead.TM.json new file mode 100644 index 0000000000..78e998834e --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/EventFormOpRead.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "readproperty" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/EventFormOpSubAll.TM.json b/codegen2/test/thing-models/invalidThing/EventFormOpSubAll.TM.json new file mode 100644 index 0000000000..39eabe2cb8 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/EventFormOpSubAll.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "subscribeallevents" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/EventFormOpWrite.TM.json b/codegen2/test/thing-models/invalidThing/EventFormOpWrite.TM.json new file mode 100644 index 0000000000..e2cc6c03aa --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/EventFormOpWrite.TM.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alert/TestThing", + "op": "writeproperty" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/EventFormsEmpty.TM.json b/codegen2/test/thing-models/invalidThing/EventFormsEmpty.TM.json new file mode 100644 index 0000000000..bfca41fcf6 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/EventFormsEmpty.TM.json @@ -0,0 +1,21 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/NoContext.TM.json b/codegen2/test/thing-models/invalidThing/NoContext.TM.json new file mode 100644 index 0000000000..9bad55b494 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/NoContext.TM.json @@ -0,0 +1,19 @@ +{ + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/NoType.TM.json b/codegen2/test/thing-models/invalidThing/NoType.TM.json new file mode 100644 index 0000000000..cc8f0372e9 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/NoType.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/PropertyFormOpInvoke.TM.json b/codegen2/test/thing-models/invalidThing/PropertyFormOpInvoke.TM.json new file mode 100644 index 0000000000..90529f1eb3 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/PropertyFormOpInvoke.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "invokeaction" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/PropertyFormOpReadAll.TM.json b/codegen2/test/thing-models/invalidThing/PropertyFormOpReadAll.TM.json new file mode 100644 index 0000000000..0ef06523fc --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/PropertyFormOpReadAll.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readallproperties" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/PropertyFormOpSub.TM.json b/codegen2/test/thing-models/invalidThing/PropertyFormOpSub.TM.json new file mode 100644 index 0000000000..c0487197fa --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/PropertyFormOpSub.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "subscribeevent" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/PropertyFormOpWriteMulti.TM.json b/codegen2/test/thing-models/invalidThing/PropertyFormOpWriteMulti.TM.json new file mode 100644 index 0000000000..a514f4eb2d --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/PropertyFormOpWriteMulti.TM.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "writemultipleproperties" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/PropertyFormsEmpty.TM.json b/codegen2/test/thing-models/invalidThing/PropertyFormsEmpty.TM.json new file mode 100644 index 0000000000..9028028ddf --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/PropertyFormsEmpty.TM.json @@ -0,0 +1,19 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/RootFormNoOp.TM.json b/codegen2/test/thing-models/invalidThing/RootFormNoOp.TM.json new file mode 100644 index 0000000000..50b6746311 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/RootFormNoOp.TM.json @@ -0,0 +1,45 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/read" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/RootFormOpArrayEmpty.TM.json b/codegen2/test/thing-models/invalidThing/RootFormOpArrayEmpty.TM.json new file mode 100644 index 0000000000..3458569c63 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/RootFormOpArrayEmpty.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/read", + "op": [ ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/RootFormOpElementEmpty.TM.json b/codegen2/test/thing-models/invalidThing/RootFormOpElementEmpty.TM.json new file mode 100644 index 0000000000..8079a08a9f --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/RootFormOpElementEmpty.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/read", + "op": [ "" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/RootFormOpEmpty.TM.json b/codegen2/test/thing-models/invalidThing/RootFormOpEmpty.TM.json new file mode 100644 index 0000000000..89efa3a387 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/RootFormOpEmpty.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/read", + "op": "" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/RootFormOpReadOne.TM.json b/codegen2/test/thing-models/invalidThing/RootFormOpReadOne.TM.json new file mode 100644 index 0000000000..244f8b047f --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/RootFormOpReadOne.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/read", + "op": [ "readproperty" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/RootFormOpSubOne.TM.json b/codegen2/test/thing-models/invalidThing/RootFormOpSubOne.TM.json new file mode 100644 index 0000000000..303fe1b440 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/RootFormOpSubOne.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/evens", + "op": [ "subscribeevent" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/RootFormOpWriteOne.TM.json b/codegen2/test/thing-models/invalidThing/RootFormOpWriteOne.TM.json new file mode 100644 index 0000000000..ce413d37a0 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/RootFormOpWriteOne.TM.json @@ -0,0 +1,46 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/write", + "op": [ "writeproperty" ] + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/RootFormsEmpty.TM.json b/codegen2/test/thing-models/invalidThing/RootFormsEmpty.TM.json new file mode 100644 index 0000000000..b5f1c09593 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/RootFormsEmpty.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "forms": [ + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + } + ] + } + }, + "events": { + "alert": { + "data": { + "type": "string" + }, + "forms": [ + { + } + ] + } + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionArrayItemsNotObject.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionArrayItemsNotObject.TM.json new file mode 100644 index 0000000000..08d040b0b7 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionArrayItemsNotObject.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array", + "items": "string" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionArrayItemsTypeNotString.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionArrayItemsTypeNotString.TM.json new file mode 100644 index 0000000000..d1890a1166 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionArrayItemsTypeNotString.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array", + "items": { + "type": 666 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionArrayItemsTypeUnsupported.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionArrayItemsTypeUnsupported.TM.json new file mode 100644 index 0000000000..ca6ba69505 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionArrayItemsTypeUnsupported.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "array", + "items": { + "type": "foobar" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionIntegerMaxNotNumeric.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionIntegerMaxNotNumeric.TM.json new file mode 100644 index 0000000000..18b9a9fc7a --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionIntegerMaxNotNumeric.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "maximum": "100" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionIntegerMinNotNumeric.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionIntegerMinNotNumeric.TM.json new file mode 100644 index 0000000000..cde5b1973b --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionIntegerMinNotNumeric.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "integer", + "minimum": "zero" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionMapAdditionalPropertiesNotObject.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionMapAdditionalPropertiesNotObject.TM.json new file mode 100644 index 0000000000..2d7d650a4f --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionMapAdditionalPropertiesNotObject.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "dtv:additionalProperties": "string" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionMapAdditionalPropertiesTypeNotString.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionMapAdditionalPropertiesTypeNotString.TM.json new file mode 100644 index 0000000000..e3f057d71e --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionMapAdditionalPropertiesTypeNotString.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "dtv:additionalProperties": { + "type": 666 + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionMapAdditionalPropertiesTypeUnsupported.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionMapAdditionalPropertiesTypeUnsupported.TM.json new file mode 100644 index 0000000000..4a0245046d --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionMapAdditionalPropertiesTypeUnsupported.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "dtv:additionalProperties": { + "type": "foobar" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionNumberMaxNotNumeric.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionNumberMaxNotNumeric.TM.json new file mode 100644 index 0000000000..8dbcc10e76 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionNumberMaxNotNumeric.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "maximum": "100" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionNumberMinNotNumeric.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionNumberMinNotNumeric.TM.json new file mode 100644 index 0000000000..93cea13775 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionNumberMinNotNumeric.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "number", + "minimum": "zero" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectConstPropertyTypeUnsupported.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectConstPropertyTypeUnsupported.TM.json new file mode 100644 index 0000000000..2d3181130b --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectConstPropertyTypeUnsupported.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "foobar" + } + }, + "const": { + "foo": "hello" + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectPropertyTypeNotString.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectPropertyTypeNotString.TM.json new file mode 100644 index 0000000000..46ca98158f --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectPropertyTypeNotString.TM.json @@ -0,0 +1,33 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": 888 + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectPropertyTypeUnsupported.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectPropertyTypeUnsupported.TM.json new file mode 100644 index 0000000000..7361b79103 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectPropertyTypeUnsupported.TM.json @@ -0,0 +1,33 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "foobar" + } + } + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectRequiredNotString.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectRequiredNotString.TM.json new file mode 100644 index 0000000000..f73bb2213c --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionObjectRequiredNotString.TM.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ 555 ] + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionStringContentEncodingNotString.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionStringContentEncodingNotString.TM.json new file mode 100644 index 0000000000..9c64279ac9 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionStringContentEncodingNotString.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "contentEncoding": 444 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionStringFormatNotString.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionStringFormatNotString.TM.json new file mode 100644 index 0000000000..7ca9d2cc5a --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionStringFormatNotString.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "format": 444 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionStringPatternNotString.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionStringPatternNotString.TM.json new file mode 100644 index 0000000000..7962ab7c16 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionStringPatternNotString.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "string", + "pattern": 444 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionTypeNotString.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionTypeNotString.TM.json new file mode 100644 index 0000000000..091252e6f6 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionTypeNotString.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": 767 + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/SchemaDefinitionTypeUnsupported.TM.json b/codegen2/test/thing-models/invalidThing/SchemaDefinitionTypeUnsupported.TM.json new file mode 100644 index 0000000000..ed3df528eb --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/SchemaDefinitionTypeUnsupported.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "someSchema": { + "type": "foobar" + } + }, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/TitleNotString.TM.json b/codegen2/test/thing-models/invalidThing/TitleNotString.TM.json new file mode 100644 index 0000000000..7ec7cfba57 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/TitleNotString.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": 444, + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/TypeEmpty.TM.json b/codegen2/test/thing-models/invalidThing/TypeEmpty.TM.json new file mode 100644 index 0000000000..5d681bcb69 --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/TypeEmpty.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/invalidThing/TypeNotThingModel.TM.json b/codegen2/test/thing-models/invalidThing/TypeNotThingModel.TM.json new file mode 100644 index 0000000000..e8f48267cc --- /dev/null +++ b/codegen2/test/thing-models/invalidThing/TypeNotThingModel.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "Something", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/EventContainedInEventNoContains.TM.json b/codegen2/test/thing-models/valid/EventContainedInEventNoContains.TM.json new file mode 100644 index 0000000000..100bbdf0c3 --- /dev/null +++ b/codegen2/test/thing-models/valid/EventContainedInEventNoContains.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "aov:containedIn": "alpha", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/valid/EventContainsEventNoContainedIn.TM.json b/codegen2/test/thing-models/valid/EventContainsEventNoContainedIn.TM.json new file mode 100644 index 0000000000..f96160c107 --- /dev/null +++ b/codegen2/test/thing-models/valid/EventContainsEventNoContainedIn.TM.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + }, + "events": { + "alpha": { + "data": { + "type": "string" + }, + "aov:contains": [ "beta" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{senderId}", + "op": "subscribeevent" + } + ] + }, + "beta": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{senderId}", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/valid/LinkCapability.TM.json b/codegen2/test/thing-models/valid/LinkCapability.TM.json new file mode 100644 index 0000000000..8625bbc16e --- /dev/null +++ b/codegen2/test/thing-models/valid/LinkCapability.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:capability", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/LinkComponent.TM.json b/codegen2/test/thing-models/valid/LinkComponent.TM.json new file mode 100644 index 0000000000..8355496c0a --- /dev/null +++ b/codegen2/test/thing-models/valid/LinkComponent.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:component", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/LinkExtends.TM.json b/codegen2/test/thing-models/valid/LinkExtends.TM.json new file mode 100644 index 0000000000..a4fee4721e --- /dev/null +++ b/codegen2/test/thing-models/valid/LinkExtends.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "tm:extends", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/LinkNoRel.TM.json b/codegen2/test/thing-models/valid/LinkNoRel.TM.json new file mode 100644 index 0000000000..d3384c7adf --- /dev/null +++ b/codegen2/test/thing-models/valid/LinkNoRel.TM.json @@ -0,0 +1,29 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "href": "../../TestCase.cs", + "type": "application/text" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/LinkOtherAovRel.TM.json b/codegen2/test/thing-models/valid/LinkOtherAovRel.TM.json new file mode 100644 index 0000000000..b802afb59a --- /dev/null +++ b/codegen2/test/thing-models/valid/LinkOtherAovRel.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:something", + "href": "../../TestCase.cs", + "type": "application/text" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/LinkOtherRel.TM.json b/codegen2/test/thing-models/valid/LinkOtherRel.TM.json new file mode 100644 index 0000000000..a5e5a6bf4c --- /dev/null +++ b/codegen2/test/thing-models/valid/LinkOtherRel.TM.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "service-doc", + "href": "../../TestCase.cs", + "type": "application/text" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/LinkReference.TM.json b/codegen2/test/thing-models/valid/LinkReference.TM.json new file mode 100644 index 0000000000..bd4a7688c4 --- /dev/null +++ b/codegen2/test/thing-models/valid/LinkReference.TM.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:reference", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/LinkTypedReference.TM.json b/codegen2/test/thing-models/valid/LinkTypedReference.TM.json new file mode 100644 index 0000000000..e2d7e22bd8 --- /dev/null +++ b/codegen2/test/thing-models/valid/LinkTypedReference.TM.json @@ -0,0 +1,32 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "links": [ + { + "rel": "aov:typedReference", + "aov:refType": "MyReferenceType", + "href": "http://example.com/BasicOnOffTM", + "type": "application/tm+json" + } + ], + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/Noop.TM.json b/codegen2/test/thing-models/valid/Noop.TM.json new file mode 100644 index 0000000000..02be4cdb81 --- /dev/null +++ b/codegen2/test/thing-models/valid/Noop.TM.json @@ -0,0 +1,23 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + "noop": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/TestThing/command/noop", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/ObjectPropertyNamespace.TM.json b/codegen2/test/thing-models/valid/ObjectPropertyNamespace.TM.json new file mode 100644 index 0000000000..6ee4edb403 --- /dev/null +++ b/codegen2/test/thing-models/valid/ObjectPropertyNamespace.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Echo", + "actions": { + "echo": { + "input": { + "type": "object", + "properties": { + "value": { + "aov:namespace": "MyNamespace", + "type": "string" + } + } + }, + "output": { + "type": "object", + "properties": { + "value": { + "type": "integer" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/ObjectTypeRef.TM.json b/codegen2/test/thing-models/valid/ObjectTypeRef.TM.json new file mode 100644 index 0000000000..2821180e5c --- /dev/null +++ b/codegen2/test/thing-models/valid/ObjectTypeRef.TM.json @@ -0,0 +1,41 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Echo", + "actions": { + "echo": { + "input": { + "aov:typeRef": "SomeOtherItem", + "type": "object", + "properties": { + "value": { + "type": "string" + } + } + }, + "output": { + "type": "object", + "properties": { + "value": { + "type": "integer" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/poke/{executorId}", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/PropertyContainedInPropertyNoContains.TM.json b/codegen2/test/thing-models/valid/PropertyContainedInPropertyNoContains.TM.json new file mode 100644 index 0000000000..144b1681d7 --- /dev/null +++ b/codegen2/test/thing-models/valid/PropertyContainedInPropertyNoContains.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "aov:containedIn": "alpha", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/PropertyContainsPropertyNoContainedIn.TM.json b/codegen2/test/thing-models/valid/PropertyContainsPropertyNoContainedIn.TM.json new file mode 100644 index 0000000000..02d0b00a17 --- /dev/null +++ b/codegen2/test/thing-models/valid/PropertyContainsPropertyNoContainedIn.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" }, + { "aov": "http://azure.com/IoT/operations/tm#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "aov:contains": [ "beta" ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}", + "op": "readproperty" + } + ] + }, + "beta": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/beta/{maintainerId}", + "op": "readproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses.TM.json b/codegen2/test/thing-models/valid/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses.TM.json new file mode 100644 index 0000000000..575a3eedef --- /dev/null +++ b/codegen2/test/thing-models/valid/RootFormReadAllWithAdditionalResponsesButNoReadablePropertyAdditionalResponses.TM.json @@ -0,0 +1,57 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "forms": [ + { + "dtv:topic": "sample/all/events", + "contentType": "application/json", + "op": "readallproperties", + "additionalResponses": [ + { + "success": false + } + ] + } + ], + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty" + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "writeproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses.TM.json b/codegen2/test/thing-models/valid/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses.TM.json new file mode 100644 index 0000000000..39869628e1 --- /dev/null +++ b/codegen2/test/thing-models/valid/RootFormWriteMultiWithAdditionalResponsesButNoWritablePropertyAdditionalResponses.TM.json @@ -0,0 +1,62 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Noop", + "schemaDefinitions": { + "Error": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "forms": [ + { + "dtv:topic": "sample/all/events/read", + "contentType": "application/json", + "op": "readallproperties" + }, + { + "dtv:topic": "sample/all/events/write", + "contentType": "application/json", + "op": "writemultipleproperties", + "additionalResponses": [ + { + "success": false + } + ] + } + ], + "actions": { + }, + "properties": { + "alpha": { + "type": "string", + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "readproperty", + "additionalResponses": [ + { + "success": false, + "schema": "Error" + } + ] + }, + { + "contentType": "application/json", + "dtv:topic": "sample/alpha/{maintainerId}/{action}", + "op": "writeproperty" + } + ] + } + }, + "events": { + } +} diff --git a/codegen2/test/thing-models/valid/ThingOneNamedMyThing.TM.json b/codegen2/test/thing-models/valid/ThingOneNamedMyThing.TM.json new file mode 100644 index 0000000000..5ebd092023 --- /dev/null +++ b/codegen2/test/thing-models/valid/ThingOneNamedMyThing.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "MyThing", + "events": { + "alpha": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry1", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/valid/ThingOneWithGeneratedNameMyPropSchema.TM.json b/codegen2/test/thing-models/valid/ThingOneWithGeneratedNameMyPropSchema.TM.json new file mode 100644 index 0000000000..b966a4fef8 --- /dev/null +++ b/codegen2/test/thing-models/valid/ThingOneWithGeneratedNameMyPropSchema.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "ThingOne", + "events": { + "alpha": { + "data": { + "title": "MyPropSchema", + "type": "object", + "properties": { + "myVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry1", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/valid/ThingTwoNamedMyThing.TM.json b/codegen2/test/thing-models/valid/ThingTwoNamedMyThing.TM.json new file mode 100644 index 0000000000..54a4f2cc95 --- /dev/null +++ b/codegen2/test/thing-models/valid/ThingTwoNamedMyThing.TM.json @@ -0,0 +1,22 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "MyThing", + "events": { + "beta": { + "data": { + "type": "string" + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry2", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/codegen2/test/thing-models/valid/ThingTwoWithGeneratedNameMyPropSchema.TM.json b/codegen2/test/thing-models/valid/ThingTwoWithGeneratedNameMyPropSchema.TM.json new file mode 100644 index 0000000000..b87630900b --- /dev/null +++ b/codegen2/test/thing-models/valid/ThingTwoWithGeneratedNameMyPropSchema.TM.json @@ -0,0 +1,28 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "ThingTwo", + "events": { + "beta": { + "data": { + "title": "MyPropSchema", + "type": "object", + "properties": { + "myVal": { + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "sample/{senderId}/telemetry2", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/dotnet/samples/Protocol/CloudEvents/ValueExtractor.cs b/dotnet/samples/Protocol/CloudEvents/ValueExtractor.cs index 33167c315c..6a615a0af9 100644 --- a/dotnet/samples/Protocol/CloudEvents/ValueExtractor.cs +++ b/dotnet/samples/Protocol/CloudEvents/ValueExtractor.cs @@ -10,8 +10,8 @@ namespace CloudEvents internal static class ValueExtractor { #pragma warning disable IDE0030 // Null check can be simplified - public static T Value(this T obj) - where T : class => obj; + public static T Value(this T? obj) + where T : class => obj!; public static T Value(this T? val) where T : struct => val.HasValue ? val.Value : default(T); diff --git a/dotnet/samples/Protocol/ReadCloudEvents/ValueExtractor.cs b/dotnet/samples/Protocol/ReadCloudEvents/ValueExtractor.cs index dde2348061..b0dbcee5b3 100644 --- a/dotnet/samples/Protocol/ReadCloudEvents/ValueExtractor.cs +++ b/dotnet/samples/Protocol/ReadCloudEvents/ValueExtractor.cs @@ -10,8 +10,8 @@ namespace ReadCloudEvents internal static class ValueExtractor { #pragma warning disable IDE0030 // Null check can be simplified - public static T Value(this T obj) - where T : class => obj; + public static T Value(this T? obj) + where T : class => obj!; public static T Value(this T? val) where T : struct => val.HasValue ? val.Value : default(T); diff --git a/dotnet/test/Azure.Iot.Operations.Protocol.UnitTests/Serializers/common/ValueExtractor.cs b/dotnet/test/Azure.Iot.Operations.Protocol.UnitTests/Serializers/common/ValueExtractor.cs index 6e61a15a03..e99e1b279a 100644 --- a/dotnet/test/Azure.Iot.Operations.Protocol.UnitTests/Serializers/common/ValueExtractor.cs +++ b/dotnet/test/Azure.Iot.Operations.Protocol.UnitTests/Serializers/common/ValueExtractor.cs @@ -10,8 +10,8 @@ namespace Azure.Iot.Operations.Protocol.UnitTests.Serializers.common internal static class ValueExtractor { #pragma warning disable IDE0030 // Null check can be simplified - public static T Value(this T obj) - where T : class => obj; + public static T Value(this T? obj) + where T : class => obj!; public static T Value(this T? val) where T : struct => val.HasValue ? val.Value : default(T); diff --git a/eng/dtdl/AdrBaseService.TM.json b/eng/dtdl/AdrBaseService.TM.json new file mode 100644 index 0000000000..3c7ff83fad --- /dev/null +++ b/eng/dtdl/AdrBaseService.TM.json @@ -0,0 +1,4810 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "AdrBaseService", + "links": [ + { + "rel": "dtv:naming", + "href": "SchemaNames.json", + "type": "application/json" + } + ], + "schemaDefinitions": { + "AkriServiceError": { + "title": "AkriServiceError", + "type": "object", + "required": [ "code", "message", "timestamp" ], + "dtv:errorMessage": "message", + "properties": { + "code": { + "description": "The error code that identifies the error.", + "title": "CodeSchema", + "type": "string", + "enum": [ + "BadRequest", + "InternalError", + "KubeError", + "SerializationError", + "Unauthorized" + ] + }, + "message": { + "description": "A human-readable description of the error.", + "type": "string" + }, + "timestamp": { + "description": "The timestamp (in UTC) when the error occurred.", + "type": "string", + "format": "date-time" + } + } + } + }, + "actions": { + "getDevice": { + "description": "Retrieve a device by device name and inbound endpoint name.", + "output": { + "description": "Response containing the device resource or error details if the device could not be retrieved.", + "type": "object", + "required": [ "device" ], + "properties": { + "device": { + "description": "The device resource, containing the specific inbound endpoint details as specified by the request.", + "title": "Device", + "type": "object", + "properties": { + "attributes": { + "description": "A set of key-value pairs that contain custom attributes set by the customer.", + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + }, + "discoveredDeviceRef": { + "description": "Reference to a device. Populated only if the device had been created from discovery flow. Discovered device name must be provided.", + "type": "string" + }, + "enabled": { + "description": "Indicates if the resource and identity are enabled or not. A disabled device cannot authenticate with Microsoft Entra ID.", + "type": "boolean" + }, + "endpoints": { + "description": "Connection endpoint url a device can use to connect to a service.", + "title": "DeviceEndpointsSchema", + "type": "object", + "properties": { + "inbound": { + "type": "object", + "dtv:additionalProperties": { + "title": "InboundSchemaMapValueSchema", + "type": "object", + "required": [ "address", "endpointType" ], + "properties": { + "additionalConfiguration": { + "description": "Stringified JSON that contains connectivity type specific further configuration (e.g. OPC UA, ONVIF).", + "type": "string" + }, + "address": { + "description": "The endpoint address & port. This can be either an IP address (e.g., 192.168.1.1) or a fully qualified domain name (FQDN, e.g., server.example.com).", + "type": "string" + }, + "authentication": { + "description": "Defines the client authentication mechanism to the server.", + "title": "AuthenticationSchema", + "type": "object", + "required": [ "method" ], + "properties": { + "method": { + "description": "Defines the method to authenticate the user of the client at the server.", + "title": "MethodSchema", + "type": "string", + "enum": [ + "Anonymous", + "Certificate", + "UsernamePassword" + ] + }, + "usernamePasswordCredentials": { + "description": "The credentials for authentication mode UsernamePassword.", + "title": "UsernamePasswordCredentialsSchema", + "type": "object", + "required": [ "usernameSecretName", "passwordSecretName" ], + "properties": { + "usernameSecretName": { + "description": "The name of the secret containing the username.", + "type": "string" + }, + "passwordSecretName": { + "description": "The name of the secret containing the password.", + "type": "string" + } + } + }, + "x509Credentials": { + "description": "The x509 certificate for authentication mode Certificate.", + "title": "X509CredentialsSchema", + "type": "object", + "required": [ "certificateSecretName" ], + "properties": { + "certificateSecretName": { + "description": "The name of the secret containing the certificate and private key (e.g. stored as .der/.pem or .der/.pfx).", + "type": "string" + }, + "intermediateCertificatesSecretName": { + "description": "A reference to the secret containing the combined intermediate certificates in PEM format.", + "type": "string" + }, + "keySecretName": { + "description": "A reference to the secret containing the certificate private key in PEM or DER format.", + "type": "string" + } + } + } + } + }, + "endpointType": { + "description": "Type of connection endpoint.", + "type": "string" + }, + "trustSettings": { + "description": "Defines server trust settings for the endpoint.", + "title": "TrustSettingsSchema", + "type": "object", + "properties": { + "trustList": { + "description": "Secret reference to certificates list to trust.", + "type": "string" + } + } + }, + "version": { + "description": "Version associated with device endpoint.", + "type": "string" + } + } + } + }, + "outbound": { + "description": "Set of endpoints for device to connect to.", + "title": "OutboundSchema", + "type": "object", + "required": [ "assigned" ], + "properties": { + "assigned": { + "description": "Device messaging endpoint model.", + "type": "object", + "dtv:additionalProperties": { + "title": "DeviceOutboundEndpoint", + "type": "object", + "required": [ "address" ], + "properties": { + "address": { + "description": "The endpoint address to connect to.", + "type": "string" + }, + "endpointType": { + "description": "Type of connection used for the messaging endpoint.", + "type": "string" + } + } + } + }, + "unassigned": { + "description": "Device messaging endpoint model.", + "type": "object", + "dtv:additionalProperties": { + "title": "DeviceOutboundEndpoint", + "type": "object", + "required": [ "address" ], + "properties": { + "address": { + "description": "The endpoint address to connect to.", + "type": "string" + }, + "endpointType": { + "description": "Type of connection used for the messaging endpoint.", + "type": "string" + } + } + } + } + } + } + } + }, + "externalDeviceId": { + "description": "The Device ID provided by the customer.", + "type": "string" + }, + "lastTransitionTime": { + "description": "A timestamp (in UTC) that is updated each time the resource is modified.", + "type": "string", + "format": "date-time" + }, + "manufacturer": { + "description": "Device manufacturer.", + "type": "string" + }, + "model": { + "description": "Device model.", + "type": "string" + }, + "operatingSystem": { + "description": "Device operating system.", + "type": "string" + }, + "operatingSystemVersion": { + "description": "Device operating system version.", + "type": "string" + }, + "uuid": { + "description": "Gets a unique identifier for this resource.", + "type": "string" + }, + "version": { + "description": "An integer that is incremented each time the resource is modified.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getDevice", + "op": "invokeaction" + } + ] + }, + "getDeviceStatus": { + "description": "Retrieve the status of a device by device name and inbound endpoint name.", + "output": { + "description": "Response containing the device status or error details if the status could not be retrieved.", + "type": "object", + "required": [ "deviceStatus" ], + "properties": { + "deviceStatus": { + "description": "The device status, containing the specific inbound endpoint status as specified by the request.", + "title": "DeviceStatus", + "type": "object", + "properties": { + "config": { + "description": "The configuration status of the device.", + "title": "ConfigStatus", + "type": "object", + "properties": { + "error": { + "description": "The last error that occurred while processing the configuration.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "lastTransitionTime": { + "description": "A read only timestamp indicating the last time the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud.", + "type": "string", + "format": "date-time" + }, + "version": { + "description": "A read only incremental counter indicating the number of times the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud. In steady state, this should equal version.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "endpoints": { + "description": "Defines the device status for inbound/outbound endpoints.", + "title": "DeviceStatusEndpointSchema", + "type": "object", + "properties": { + "inbound": { + "type": "object", + "dtv:additionalProperties": { + "title": "DeviceStatusInboundEndpointSchemaMapValueSchema", + "type": "object", + "properties": { + "error": { + "description": "The last error that occurred while processing the endpoint.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getDeviceStatus", + "op": "invokeaction" + } + ] + }, + "getAsset": { + "description": "Retrieve an asset by asset name.", + "input": { + "description": "The name of the asset to retrieve.", + "type": "object", + "required": [ "assetName" ], + "properties": { + "assetName": { + "type": "string" + } + } + }, + "output": { + "description": "Response containing the asset resource or error details if the asset could not be retrieved.", + "type": "object", + "required": [ "asset" ], + "properties": { + "asset": { + "description": "The asset resource", + "title": "Asset", + "type": "object", + "required": [ "deviceRef" ], + "properties": { + "assetTypeRefs": { + "description": "URIs or type definition IDs.", + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "description": "A set of key-value pairs that contain custom attributes set by the customer.", + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + }, + "datasets": { + "description": "Array of data sets that are part of the asset. Each data set describes the data points that make up the set.", + "type": "array", + "items": { + "title": "AssetDatasetSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "dataPoints": { + "type": "array", + "items": { + "title": "AssetDatasetDataPointSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "dataPointConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the data point.", + "type": "string" + }, + "dataSource": { + "description": "The address of the source of the data in the asset (e.g. URL) so that a client can access the data source on the asset.", + "type": "string" + }, + "name": { + "description": "The name of the data point.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "dataSource": { + "description": "Reference to a data source for a given dataset.", + "type": "string" + }, + "datasetConfiguration": { + "description": "Stringified JSON that contains connector-specific JSON string that describes configuration for the specific dataset.", + "type": "string" + }, + "destinations": { + "description": "Destinations for a dataset.", + "type": "array", + "items": { + "title": "DatasetDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "DatasetTarget", + "type": "string", + "enum": [ + "BrokerStateStore", + "Mqtt", + "Storage" + ] + } + } + } + }, + "name": { + "description": "Name of the dataset.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "defaultDatasetsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all datasets. Each dataset can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultDatasetsDestinations": { + "description": "Default destinations for a dataset.", + "type": "array", + "items": { + "title": "DatasetDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "DatasetTarget", + "type": "string", + "enum": [ + "BrokerStateStore", + "Mqtt", + "Storage" + ] + } + } + } + }, + "defaultEventsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all events. Each event can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultEventsDestinations": { + "description": "Default destinations for an event.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "defaultManagementGroupsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all management groups. Each management group can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultStreamsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all streams. Each stream can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultStreamsDestinations": { + "description": "Default destinations for a stream.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "description": { + "description": "Human-readable description of the asset.", + "type": "string" + }, + "deviceRef": { + "description": "Reference to the device that provides data for this asset. Must provide device name & endpoint on the device to use.", + "title": "AssetDeviceRef", + "type": "object", + "required": [ "deviceName", "endpointName" ], + "properties": { + "deviceName": { + "description": "Name of the device resource.", + "type": "string" + }, + "endpointName": { + "description": "The name of endpoint to use.", + "type": "string" + } + } + }, + "discoveredAssetRefs": { + "description": "Reference to a list of discovered assets. Populated only if the asset has been created from discovery flow. Discovered asset names must be provided.", + "type": "array", + "items": { + "type": "string" + } + }, + "displayName": { + "description": "Human-readable display name.", + "type": "string" + }, + "documentationUri": { + "description": "Asset documentation reference.", + "type": "string" + }, + "enabled": { + "description": "Enabled/Disabled status of the asset.", + "type": "boolean" + }, + "eventGroups": { + "description": "Array of events that are part of the asset. Each event can have per-event configuration.", + "type": "array", + "items": { + "title": "AssetEventGroupSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "dataSource": { + "description": "The address of the notifier of the event in the asset (e.g. URL) so that a client can access the event on the asset.", + "type": "string" + }, + "defaultEventsDestinations": { + "description": "Default destinations for an event.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "eventGroupConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the event. For OPC UA, this could include configuration like, publishingInterval, samplingInterval, and queueSize.", + "type": "string" + }, + "events": { + "description": "Array of events that are part of the asset. Each event can have per-event configuration.", + "type": "array", + "items": { + "title": "AssetEventSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "dataSource": { + "description": "Reference to a data source for a given event.", + "type": "string" + }, + "destinations": { + "description": "Destinations for an event.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "eventConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the specific event.", + "type": "string" + }, + "name": { + "description": "The name of the event.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "name": { + "description": "Name of the event group.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "externalAssetId": { + "description": "Asset ID provided by the customer.", + "type": "string" + }, + "hardwareRevision": { + "description": "Asset hardware revision number.", + "type": "string" + }, + "lastTransitionTime": { + "description": "A timestamp (in UTC) that is updated each time the resource is modified.", + "type": "string", + "format": "date-time" + }, + "managementGroups": { + "description": "Array of management groups that are part of the asset.", + "type": "array", + "items": { + "title": "AssetManagementGroupSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "actions": { + "description": "Array of actions that are part of the management group. Each action can have an individual configuration.", + "type": "array", + "items": { + "title": "AssetManagementGroupActionSchemaElementSchema", + "type": "object", + "required": [ "actionType", "name", "targetUri" ], + "properties": { + "actionConfiguration": { + "description": "Configuration for the action.", + "type": "string" + }, + "actionType": { + "description": "Type of the action.", + "title": "AssetManagementGroupActionType", + "type": "string", + "enum": [ + "Call", + "Read", + "Write" + ] + }, + "name": { + "description": "Name of the action.", + "type": "string" + }, + "targetUri": { + "description": "The target URI on which a client can invoke the specific action.", + "type": "string" + }, + "timeoutInSeconds": { + "description": "Response timeout for the action.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + }, + "topic": { + "description": "The MQTT topic path on which a client will receive the request for the action.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "dataSource": { + "description": "Reference to a data source for a given management group.", + "type": "string" + }, + "defaultTimeoutInSeconds": { + "description": "Default response timeout for all actions that are part of the management group.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + }, + "defaultTopic": { + "description": "Default MQTT topic path on which a client will receive the request for all actions that are part of the management group.", + "type": "string" + }, + "managementGroupConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the management group.", + "type": "string" + }, + "name": { + "description": "Name of the management group.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "manufacturer": { + "description": "Asset manufacturer.", + "type": "string" + }, + "manufacturerUri": { + "description": "Asset manufacturer URI.", + "type": "string" + }, + "model": { + "description": "Asset model.", + "type": "string" + }, + "productCode": { + "description": "Asset product code.", + "type": "string" + }, + "serialNumber": { + "description": "Asset serial number.", + "type": "string" + }, + "softwareRevision": { + "description": "Asset software revision number.", + "type": "string" + }, + "streams": { + "description": "Array of streams that are part of the asset. Each stream can have per-stream configuration.", + "type": "array", + "items": { + "title": "AssetStreamSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "destinations": { + "description": "Destinations for a Stream.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "name": { + "description": "Name of the stream definition.", + "type": "string" + }, + "streamConfiguration": { + "description": "Stringified JSON that contains connector-specific JSON string that describes configuration for the specific stream.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "uuid": { + "description": "Globally unique, immutable, non-reusable id.", + "type": "string" + }, + "version": { + "description": "A read-only integer that is incremented each time the resource is modified the cloud.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getAsset", + "op": "invokeaction" + } + ] + }, + "getAssetStatus": { + "description": "Retrieve the status of an asset by asset name.", + "input": { + "description": "The name of the asset to retrieve the status for.", + "type": "object", + "required": [ "assetName" ], + "properties": { + "assetName": { + "type": "string" + } + } + }, + "output": { + "description": "Response containing the asset status or error details if the status could not be retrieved.", + "type": "object", + "required": [ "assetStatus" ], + "properties": { + "assetStatus": { + "description": "The asset status", + "title": "AssetStatus", + "type": "object", + "properties": { + "config": { + "description": "The configuration status of the asset.", + "title": "ConfigStatus", + "type": "object", + "properties": { + "error": { + "description": "The last error that occurred while processing the configuration.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "lastTransitionTime": { + "description": "A read only timestamp indicating the last time the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud.", + "type": "string", + "format": "date-time" + }, + "version": { + "description": "A read only incremental counter indicating the number of times the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud. In steady state, this should equal version.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "datasets": { + "description": "Array of dataset statuses that describe the status of each dataset.", + "type": "array", + "items": { + "title": "AssetDatasetEventStreamStatus", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the dataset/event/stream.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "messageSchemaReference": { + "description": "The message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "name": { + "description": "The name of the dataset/event/stream. Must be unique within the status.datasets[i]/events[i]/streams[i] array. This name is used to correlate between the spec and status dataset/event/stream information.", + "type": "string" + } + } + } + }, + "eventGroups": { + "description": "Array of event group statuses that describe the status of each event group.", + "type": "array", + "items": { + "title": "AssetEventGroupStatusSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "events": { + "description": "Array of event statuses that describe the status of each event in the event group.", + "type": "array", + "items": { + "title": "AssetDatasetEventStreamStatus", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the dataset/event/stream.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "messageSchemaReference": { + "description": "The message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "name": { + "description": "The name of the dataset/event/stream. Must be unique within the status.datasets[i]/events[i]/streams[i] array. This name is used to correlate between the spec and status dataset/event/stream information.", + "type": "string" + } + } + } + }, + "name": { + "description": "The name of the event group. Must be unique within the status.eventGroups array. This name is used to correlate between the spec and status event group information.", + "type": "string" + } + } + } + }, + "managementGroups": { + "description": "Array of management group statuses that describe the status of each management group.", + "type": "array", + "items": { + "title": "AssetManagementGroupStatusSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "actions": { + "description": "Array of action statuses that describe the status of each action.", + "type": "array", + "items": { + "title": "AssetManagementGroupActionStatusSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the action.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "name": { + "description": "The name of the action. Must be unique within the status.managementGroup[i].actions array. This name is used to correlate between the spec and status management group action information.", + "type": "string" + }, + "requestMessageSchemaReference": { + "description": "The request message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "responseMessageSchemaReference": { + "description": "The response message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + } + } + } + }, + "name": { + "description": "The name of the managementgroup. Must be unique within the status.managementGroup array. This name is used to correlate between the spec and status management group information.", + "type": "string" + } + } + } + }, + "streams": { + "description": "Array of stream statuses that describe the status of each stream.", + "type": "array", + "items": { + "title": "AssetDatasetEventStreamStatus", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the dataset/event/stream.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "messageSchemaReference": { + "description": "The message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "name": { + "description": "The name of the dataset/event/stream. Must be unique within the status.datasets[i]/events[i]/streams[i] array. This name is used to correlate between the spec and status dataset/event/stream information.", + "type": "string" + } + } + } + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getAssetStatus", + "op": "invokeaction" + } + ] + }, + "updateDeviceStatus": { + "description": "Update the status of a device by device name and inbound endpoint name.", + "input": { + "description": "The device status to update. Fields omitted in the request will be removed. The specified inbound endpoint status will be added or updated in the inbound endpoints map.", + "type": "object", + "required": [ "deviceStatusUpdate" ], + "properties": { + "deviceStatusUpdate": { + "title": "DeviceStatus", + "type": "object", + "properties": { + "config": { + "description": "The configuration status of the device.", + "title": "ConfigStatus", + "type": "object", + "properties": { + "error": { + "description": "The last error that occurred while processing the configuration.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "lastTransitionTime": { + "description": "A read only timestamp indicating the last time the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud.", + "type": "string", + "format": "date-time" + }, + "version": { + "description": "A read only incremental counter indicating the number of times the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud. In steady state, this should equal version.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "endpoints": { + "description": "Defines the device status for inbound/outbound endpoints.", + "title": "DeviceStatusEndpointSchema", + "type": "object", + "properties": { + "inbound": { + "type": "object", + "dtv:additionalProperties": { + "title": "DeviceStatusInboundEndpointSchemaMapValueSchema", + "type": "object", + "properties": { + "error": { + "description": "The last error that occurred while processing the endpoint.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "output": { + "description": "Response containing the updated device status or error details if the update failed.", + "type": "object", + "required": [ "updatedDeviceStatus" ], + "properties": { + "updatedDeviceStatus": { + "description": "The updated device status containing the specific inbound endpoint status as specified by the request.", + "title": "DeviceStatus", + "type": "object", + "properties": { + "config": { + "description": "The configuration status of the device.", + "title": "ConfigStatus", + "type": "object", + "properties": { + "error": { + "description": "The last error that occurred while processing the configuration.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "lastTransitionTime": { + "description": "A read only timestamp indicating the last time the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud.", + "type": "string", + "format": "date-time" + }, + "version": { + "description": "A read only incremental counter indicating the number of times the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud. In steady state, this should equal version.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "endpoints": { + "description": "Defines the device status for inbound/outbound endpoints.", + "title": "DeviceStatusEndpointSchema", + "type": "object", + "properties": { + "inbound": { + "type": "object", + "dtv:additionalProperties": { + "title": "DeviceStatusInboundEndpointSchemaMapValueSchema", + "type": "object", + "properties": { + "error": { + "description": "The last error that occurred while processing the endpoint.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/updateDeviceStatus", + "op": "invokeaction" + } + ] + }, + "updateAssetStatus": { + "description": "Update the status of an asset by asset name.", + "input": { + "description": "The asset status update request.", + "type": "object", + "required": [ "assetStatusUpdate" ], + "properties": { + "assetStatusUpdate": { + "title": "UpdateAssetStatusRequestSchema", + "type": "object", + "required": [ "assetName", "assetStatus" ], + "properties": { + "assetName": { + "description": "The name of the asset to update the status for.", + "type": "string" + }, + "assetStatus": { + "description": "The asset status to update. Fields omitted in the request will be removed.", + "title": "AssetStatus", + "type": "object", + "properties": { + "config": { + "description": "The configuration status of the asset.", + "title": "ConfigStatus", + "type": "object", + "properties": { + "error": { + "description": "The last error that occurred while processing the configuration.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "lastTransitionTime": { + "description": "A read only timestamp indicating the last time the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud.", + "type": "string", + "format": "date-time" + }, + "version": { + "description": "A read only incremental counter indicating the number of times the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud. In steady state, this should equal version.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "datasets": { + "description": "Array of dataset statuses that describe the status of each dataset.", + "type": "array", + "items": { + "title": "AssetDatasetEventStreamStatus", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the dataset/event/stream.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "messageSchemaReference": { + "description": "The message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "name": { + "description": "The name of the dataset/event/stream. Must be unique within the status.datasets[i]/events[i]/streams[i] array. This name is used to correlate between the spec and status dataset/event/stream information.", + "type": "string" + } + } + } + }, + "eventGroups": { + "description": "Array of event group statuses that describe the status of each event group.", + "type": "array", + "items": { + "title": "AssetEventGroupStatusSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "events": { + "description": "Array of event statuses that describe the status of each event in the event group.", + "type": "array", + "items": { + "title": "AssetDatasetEventStreamStatus", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the dataset/event/stream.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "messageSchemaReference": { + "description": "The message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "name": { + "description": "The name of the dataset/event/stream. Must be unique within the status.datasets[i]/events[i]/streams[i] array. This name is used to correlate between the spec and status dataset/event/stream information.", + "type": "string" + } + } + } + }, + "name": { + "description": "The name of the event group. Must be unique within the status.eventGroups array. This name is used to correlate between the spec and status event group information.", + "type": "string" + } + } + } + }, + "managementGroups": { + "description": "Array of management group statuses that describe the status of each management group.", + "type": "array", + "items": { + "title": "AssetManagementGroupStatusSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "actions": { + "description": "Array of action statuses that describe the status of each action.", + "type": "array", + "items": { + "title": "AssetManagementGroupActionStatusSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the action.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "name": { + "description": "The name of the action. Must be unique within the status.managementGroup[i].actions array. This name is used to correlate between the spec and status management group action information.", + "type": "string" + }, + "requestMessageSchemaReference": { + "description": "The request message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "responseMessageSchemaReference": { + "description": "The response message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + } + } + } + }, + "name": { + "description": "The name of the managementgroup. Must be unique within the status.managementGroup array. This name is used to correlate between the spec and status management group information.", + "type": "string" + } + } + } + }, + "streams": { + "description": "Array of stream statuses that describe the status of each stream.", + "type": "array", + "items": { + "title": "AssetDatasetEventStreamStatus", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the dataset/event/stream.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "messageSchemaReference": { + "description": "The message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "name": { + "description": "The name of the dataset/event/stream. Must be unique within the status.datasets[i]/events[i]/streams[i] array. This name is used to correlate between the spec and status dataset/event/stream information.", + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "output": { + "description": "Response containing the updated asset status or error details if the update failed.", + "type": "object", + "required": [ "updatedAssetStatus" ], + "properties": { + "updatedAssetStatus": { + "description": "The updated asset status.", + "title": "AssetStatus", + "type": "object", + "properties": { + "config": { + "description": "The configuration status of the asset.", + "title": "ConfigStatus", + "type": "object", + "properties": { + "error": { + "description": "The last error that occurred while processing the configuration.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "lastTransitionTime": { + "description": "A read only timestamp indicating the last time the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud.", + "type": "string", + "format": "date-time" + }, + "version": { + "description": "A read only incremental counter indicating the number of times the configuration has been modified from the perspective of the current actual (Edge) state of the CRD. Edge would be the only writer of this value and would sync back up to the cloud. In steady state, this should equal version.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "datasets": { + "description": "Array of dataset statuses that describe the status of each dataset.", + "type": "array", + "items": { + "title": "AssetDatasetEventStreamStatus", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the dataset/event/stream.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "messageSchemaReference": { + "description": "The message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "name": { + "description": "The name of the dataset/event/stream. Must be unique within the status.datasets[i]/events[i]/streams[i] array. This name is used to correlate between the spec and status dataset/event/stream information.", + "type": "string" + } + } + } + }, + "eventGroups": { + "description": "Array of event group statuses that describe the status of each event group.", + "type": "array", + "items": { + "title": "AssetEventGroupStatusSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "events": { + "description": "Array of event statuses that describe the status of each event in the event group.", + "type": "array", + "items": { + "title": "AssetDatasetEventStreamStatus", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the dataset/event/stream.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "messageSchemaReference": { + "description": "The message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "name": { + "description": "The name of the dataset/event/stream. Must be unique within the status.datasets[i]/events[i]/streams[i] array. This name is used to correlate between the spec and status dataset/event/stream information.", + "type": "string" + } + } + } + }, + "name": { + "description": "The name of the event group. Must be unique within the status.eventGroups array. This name is used to correlate between the spec and status event group information.", + "type": "string" + } + } + } + }, + "managementGroups": { + "description": "Array of management group statuses that describe the status of each management group.", + "type": "array", + "items": { + "title": "AssetManagementGroupStatusSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "actions": { + "description": "Array of action statuses that describe the status of each action.", + "type": "array", + "items": { + "title": "AssetManagementGroupActionStatusSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the action.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "name": { + "description": "The name of the action. Must be unique within the status.managementGroup[i].actions array. This name is used to correlate between the spec and status management group action information.", + "type": "string" + }, + "requestMessageSchemaReference": { + "description": "The request message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "responseMessageSchemaReference": { + "description": "The response message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + } + } + } + }, + "name": { + "description": "The name of the managementgroup. Must be unique within the status.managementGroup array. This name is used to correlate between the spec and status management group information.", + "type": "string" + } + } + } + }, + "streams": { + "description": "Array of stream statuses that describe the status of each stream.", + "type": "array", + "items": { + "title": "AssetDatasetEventStreamStatus", + "type": "object", + "required": [ "name" ], + "properties": { + "error": { + "description": "The last error that occurred while processing the dataset/event/stream.", + "title": "ConfigError", + "type": "object", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "string" + }, + "details": { + "description": "Array of error details that describe the status of each error.", + "type": "array", + "items": { + "title": "DetailsSchemaElementSchema", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (ex: 400.200.100.432).", + "type": "string" + }, + "correlationId": { + "description": "Unique identifier for the transaction to aid in debugging.", + "type": "string" + }, + "info": { + "description": "Human readable helpful detailed text context for debugging (ex: “The following mechanisms are supported...”).", + "type": "string" + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “Authentication method not supported”).", + "type": "string" + } + } + } + }, + "message": { + "description": "Human readable helpful error message to provide additional context for error (ex: “capability Id ''foo'' does not exist”).", + "type": "string" + } + } + }, + "messageSchemaReference": { + "description": "The message schema reference object.", + "title": "MessageSchemaReference", + "type": "object", + "required": [ "schemaName", "schemaRegistryNamespace", "schemaVersion" ], + "properties": { + "schemaName": { + "description": "The reference to the message schema name.", + "type": "string" + }, + "schemaRegistryNamespace": { + "description": "The reference to the message schema registry namespace.", + "type": "string" + }, + "schemaVersion": { + "description": "The reference to the message schema version.", + "type": "string" + } + } + }, + "name": { + "description": "The name of the dataset/event/stream. Must be unique within the status.datasets[i]/events[i]/streams[i] array. This name is used to correlate between the spec and status dataset/event/stream information.", + "type": "string" + } + } + } + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/updateAssetStatus", + "op": "invokeaction" + } + ] + }, + "setNotificationPreferenceForDeviceUpdates": { + "description": "Set the notification preference for device updates. This command allows the connector application to subscribe to per-device inbound endpoint updates.", + "input": { + "description": "The request to set the notification preference for device updates.", + "type": "object", + "required": [ "notificationPreferenceRequest" ], + "properties": { + "notificationPreferenceRequest": { + "title": "NotificationPreference", + "type": "string", + "enum": [ + "Off", + "On" + ] + } + } + }, + "output": { + "description": "Response containing the result of setting the notification preference for device updates or error details if the operation failed.", + "type": "object", + "required": [ "responsePayload" ], + "properties": { + "responsePayload": { + "description": "The response payload indicating that the operation was successful.", + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/setNotificationPreferenceForDeviceUpdates", + "op": "invokeaction" + } + ] + }, + "setNotificationPreferenceForAssetUpdates": { + "description": "Set the notification preference for asset updates. This command allows the connector application to subscribe to per-asset updates.", + "input": { + "description": "The request to set the notification preference for asset updates.", + "type": "object", + "required": [ "notificationPreferenceRequest" ], + "properties": { + "notificationPreferenceRequest": { + "title": "SetNotificationPreferenceForAssetUpdatesRequestSchema", + "type": "object", + "required": [ "assetName", "notificationPreference" ], + "properties": { + "assetName": { + "description": "The name of the asset to set the notification preference for.", + "type": "string" + }, + "notificationPreference": { + "description": "The notification preference to set for the asset updates.", + "title": "NotificationPreference", + "type": "string", + "enum": [ + "Off", + "On" + ] + } + } + } + } + }, + "output": { + "description": "Response containing the result of setting the notification preference for asset updates or error details if the operation failed.", + "type": "object", + "required": [ "responsePayload" ], + "properties": { + "responsePayload": { + "description": "The response payload indicating that the operation was successful.", + "type": "string" + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/setNotificationPreferenceForAssetUpdates", + "op": "invokeaction" + } + ] + }, + "createOrUpdateDiscoveredAsset": { + "description": "Create or update a discovered asset resource.", + "input": { + "description": "The request to create or update a discovered asset.", + "type": "object", + "required": [ "discoveredAssetRequest" ], + "properties": { + "discoveredAssetRequest": { + "title": "CreateOrUpdateDiscoveredAssetRequestSchema", + "type": "object", + "required": [ "discoveredAssetName", "discoveredAsset" ], + "properties": { + "discoveredAssetName": { + "description": "The name of the discovered asset to create or update. This field is used to perform deduplication of discovered assets.", + "type": "string" + }, + "discoveredAsset": { + "description": "The discovered asset resource to create or update. Fields omitted in the request will be removed.", + "title": "DiscoveredAsset", + "type": "object", + "required": [ "deviceRef" ], + "properties": { + "assetTypeRefs": { + "description": "URIs or type definition IDs for the asset type.", + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "description": "A set of key-value pairs that contain custom attributes.", + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + }, + "datasets": { + "description": "Array of datasets that are part of the asset. Each data set spec describes the data points that make up the set.", + "type": "array", + "items": { + "title": "DiscoveredAssetDataset", + "type": "object", + "required": [ "name" ], + "properties": { + "dataPoints": { + "description": "Array of data points that are part of the dataset. Each data point can have per-data-point configuration.", + "type": "array", + "items": { + "title": "DiscoveredAssetDatasetDataPoint", + "type": "object", + "required": [ "name" ], + "properties": { + "dataPointConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the data point.", + "type": "string" + }, + "dataSource": { + "description": "The address of the source of the data in the discovered asset (e.g. URL) so that a client can access the data source on the asset.", + "type": "string" + }, + "lastUpdatedOn": { + "description": "UTC timestamp indicating when the data point was added or modified.", + "type": "string", + "format": "date-time" + }, + "name": { + "description": "The name of the data point.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "dataSource": { + "description": "Name of the data source within a dataset.", + "type": "string" + }, + "datasetConfiguration": { + "description": "Stringified JSON that contains connector-specific properties that describes configuration for the specific dataset.", + "type": "string" + }, + "destinations": { + "description": "Destinations for a dataset.", + "type": "array", + "items": { + "title": "DatasetDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "DatasetTarget", + "type": "string", + "enum": [ + "BrokerStateStore", + "Mqtt", + "Storage" + ] + } + } + } + }, + "lastUpdatedOn": { + "description": "Timestamp (in UTC) indicating when the dataset was added or modified.", + "type": "string", + "format": "date-time" + }, + "name": { + "description": "Name of the dataset.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "defaultDatasetsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all datasets. Each dataset can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultDatasetsDestinations": { + "description": "Default destinations for a dataset.", + "type": "array", + "items": { + "title": "DatasetDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "DatasetTarget", + "type": "string", + "enum": [ + "BrokerStateStore", + "Mqtt", + "Storage" + ] + } + } + } + }, + "defaultEventsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all events. Each event can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultEventsDestinations": { + "description": "Default destinations for an event.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "defaultManagementGroupsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all management groups. Each management group can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultStreamsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all streams. Each stream can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultStreamsDestinations": { + "description": "Default destinations for a stream.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "description": { + "description": "Human-readable description of the asset.", + "type": "string" + }, + "deviceRef": { + "description": "Reference to the device that provides data for this asset. Must provide device name & endpoint on the device to use.", + "title": "AssetDeviceRef", + "type": "object", + "required": [ "deviceName", "endpointName" ], + "properties": { + "deviceName": { + "description": "Name of the device resource.", + "type": "string" + }, + "endpointName": { + "description": "The name of endpoint to use.", + "type": "string" + } + } + }, + "displayName": { + "description": "Human-readable display name.", + "type": "string" + }, + "documentationUri": { + "description": "Asset documentation reference.", + "type": "string" + }, + "eventGroups": { + "description": "Array of events that are part of the asset. Each event can have per-event configuration.", + "type": "array", + "items": { + "title": "DiscoveredAssetEventGroup", + "type": "object", + "required": [ "name" ], + "properties": { + "dataSource": { + "description": "The address of the notifier of the event in the asset (e.g. URL) so that a client can access the event on the asset.", + "type": "string" + }, + "defaultEventsDestinations": { + "description": "Default destinations for an event.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "eventGroupConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the event. For OPC UA, this could include configuration like, publishingInterval, samplingInterval, and queueSize.", + "type": "string" + }, + "events": { + "description": "Array of events that are part of the asset. Each event can have per-event configuration.", + "type": "array", + "items": { + "title": "DiscoveredAssetEvent", + "type": "object", + "required": [ "name" ], + "properties": { + "dataSource": { + "description": "Reference to a data source for a given event.", + "type": "string" + }, + "destinations": { + "description": "Destinations for an event.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "eventConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the specific event.", + "type": "string" + }, + "lastUpdatedOn": { + "description": "UTC timestamp indicating when the event was added or modified.", + "type": "string", + "format": "date-time" + }, + "name": { + "description": "The name of the event.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "name": { + "description": "Name of the event group.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "externalAssetId": { + "description": "Asset ID provided by the customer.", + "type": "string" + }, + "hardwareRevision": { + "description": "Asset hardware revision number.", + "type": "string" + }, + "managementGroups": { + "description": "Array of management groups that are part of the asset. Each management group can have a per-group configuration.", + "type": "array", + "items": { + "title": "DiscoveredAssetManagementGroup", + "type": "object", + "required": [ "name" ], + "properties": { + "actions": { + "description": "Array of actions that are part of the management group. Each action can have an individual configuration.", + "type": "array", + "items": { + "title": "DiscoveredAssetManagementGroupAction", + "type": "object", + "required": [ "actionType", "name", "targetUri" ], + "properties": { + "actionConfiguration": { + "description": "Configuration for the action.", + "type": "string" + }, + "actionType": { + "description": "Type of the action.", + "title": "AssetManagementGroupActionType", + "type": "string", + "enum": [ + "Call", + "Read", + "Write" + ] + }, + "lastUpdatedOn": { + "description": "Timestamp (in UTC) indicating when the management action was added or modified.", + "type": "string", + "format": "date-time" + }, + "name": { + "description": "Name of the action.", + "type": "string" + }, + "targetUri": { + "description": "The target URI on which a client can invoke the specific action.", + "type": "string" + }, + "timeoutInSeconds": { + "description": "Response timeout for the action.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + }, + "topic": { + "description": "The MQTT topic path on which a client will receive the request for the action.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "dataSource": { + "description": "Reference to a data source for a given management group.", + "type": "string" + }, + "defaultTimeoutInSeconds": { + "description": "Default response timeout for all actions that are part of the management group.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + }, + "defaultTopic": { + "description": "Default MQTT topic path on which a client will receive the request for all actions that are part of the management group.", + "type": "string" + }, + "lastUpdatedOn": { + "description": "Timestamp (in UTC) indicating when the management group was added or modified.", + "type": "string", + "format": "date-time" + }, + "managementGroupConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the management group.", + "type": "string" + }, + "name": { + "description": "Name of the management group.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "manufacturer": { + "description": "Asset manufacturer.", + "type": "string" + }, + "manufacturerUri": { + "description": "Asset manufacturer URI.", + "type": "string" + }, + "model": { + "description": "Asset model.", + "type": "string" + }, + "productCode": { + "description": "Asset product code.", + "type": "string" + }, + "serialNumber": { + "description": "Asset serial number.", + "type": "string" + }, + "softwareRevision": { + "description": "Asset software revision number.", + "type": "string" + }, + "streams": { + "description": "Array of streams that are part of the asset. Each stream can have per-stream configuration.", + "type": "array", + "items": { + "title": "DiscoveredAssetStream", + "type": "object", + "required": [ "name" ], + "properties": { + "destinations": { + "description": "Destinations for a stream.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "lastUpdatedOn": { + "description": "Timestamp (in UTC) indicating when the stream was added or modified.", + "type": "string", + "format": "date-time" + }, + "name": { + "description": "Name of the stream definition.", + "type": "string" + }, + "streamConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration that describes configuration for the specific stream.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "output": { + "description": "Response containing the discovered asset response or error details if the operation failed.", + "type": "object", + "required": [ "discoveredAssetResponse" ], + "properties": { + "discoveredAssetResponse": { + "description": "The discovered asset response.", + "title": "DiscoveredAssetResponseSchema", + "type": "object", + "required": [ "discoveryId", "version" ], + "properties": { + "discoveryId": { + "description": "The unique identifier for the discovered asset.", + "type": "string" + }, + "version": { + "description": "The version of the discovered asset resource.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/createOrUpdateDiscoveredAsset", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + "deviceUpdateEvent": { + "description": "Telemetry event emitted when a device is updated, containing the relevant inbound endpoint details as specified in the topic.", + "data": { + "title": "DeviceUpdateEventSchema", + "type": "object", + "required": [ "deviceName", "device" ], + "properties": { + "deviceName": { + "description": "The name of the device that was updated.", + "type": "string" + }, + "device": { + "description": "The updated device resource, containing the specific inbound endpoint details as specified in the topic.", + "title": "Device", + "type": "object", + "properties": { + "attributes": { + "description": "A set of key-value pairs that contain custom attributes set by the customer.", + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + }, + "discoveredDeviceRef": { + "description": "Reference to a device. Populated only if the device had been created from discovery flow. Discovered device name must be provided.", + "type": "string" + }, + "enabled": { + "description": "Indicates if the resource and identity are enabled or not. A disabled device cannot authenticate with Microsoft Entra ID.", + "type": "boolean" + }, + "endpoints": { + "description": "Connection endpoint url a device can use to connect to a service.", + "title": "DeviceEndpointsSchema", + "type": "object", + "properties": { + "inbound": { + "type": "object", + "dtv:additionalProperties": { + "title": "InboundSchemaMapValueSchema", + "type": "object", + "required": [ "address", "endpointType" ], + "properties": { + "additionalConfiguration": { + "description": "Stringified JSON that contains connectivity type specific further configuration (e.g. OPC UA, ONVIF).", + "type": "string" + }, + "address": { + "description": "The endpoint address & port. This can be either an IP address (e.g., 192.168.1.1) or a fully qualified domain name (FQDN, e.g., server.example.com).", + "type": "string" + }, + "authentication": { + "description": "Defines the client authentication mechanism to the server.", + "title": "AuthenticationSchema", + "type": "object", + "required": [ "method" ], + "properties": { + "method": { + "description": "Defines the method to authenticate the user of the client at the server.", + "title": "MethodSchema", + "type": "string", + "enum": [ + "Anonymous", + "Certificate", + "UsernamePassword" + ] + }, + "usernamePasswordCredentials": { + "description": "The credentials for authentication mode UsernamePassword.", + "title": "UsernamePasswordCredentialsSchema", + "type": "object", + "required": [ "usernameSecretName", "passwordSecretName" ], + "properties": { + "usernameSecretName": { + "description": "The name of the secret containing the username.", + "type": "string" + }, + "passwordSecretName": { + "description": "The name of the secret containing the password.", + "type": "string" + } + } + }, + "x509Credentials": { + "description": "The x509 certificate for authentication mode Certificate.", + "title": "X509CredentialsSchema", + "type": "object", + "required": [ "certificateSecretName" ], + "properties": { + "certificateSecretName": { + "description": "The name of the secret containing the certificate and private key (e.g. stored as .der/.pem or .der/.pfx).", + "type": "string" + }, + "intermediateCertificatesSecretName": { + "description": "A reference to the secret containing the combined intermediate certificates in PEM format.", + "type": "string" + }, + "keySecretName": { + "description": "A reference to the secret containing the certificate private key in PEM or DER format.", + "type": "string" + } + } + } + } + }, + "endpointType": { + "description": "Type of connection endpoint.", + "type": "string" + }, + "trustSettings": { + "description": "Defines server trust settings for the endpoint.", + "title": "TrustSettingsSchema", + "type": "object", + "properties": { + "trustList": { + "description": "Secret reference to certificates list to trust.", + "type": "string" + } + } + }, + "version": { + "description": "Version associated with device endpoint.", + "type": "string" + } + } + } + }, + "outbound": { + "description": "Set of endpoints for device to connect to.", + "title": "OutboundSchema", + "type": "object", + "required": [ "assigned" ], + "properties": { + "assigned": { + "description": "Device messaging endpoint model.", + "type": "object", + "dtv:additionalProperties": { + "title": "DeviceOutboundEndpoint", + "type": "object", + "required": [ "address" ], + "properties": { + "address": { + "description": "The endpoint address to connect to.", + "type": "string" + }, + "endpointType": { + "description": "Type of connection used for the messaging endpoint.", + "type": "string" + } + } + } + }, + "unassigned": { + "description": "Device messaging endpoint model.", + "type": "object", + "dtv:additionalProperties": { + "title": "DeviceOutboundEndpoint", + "type": "object", + "required": [ "address" ], + "properties": { + "address": { + "description": "The endpoint address to connect to.", + "type": "string" + }, + "endpointType": { + "description": "Type of connection used for the messaging endpoint.", + "type": "string" + } + } + } + } + } + } + } + }, + "externalDeviceId": { + "description": "The Device ID provided by the customer.", + "type": "string" + }, + "lastTransitionTime": { + "description": "A timestamp (in UTC) that is updated each time the resource is modified.", + "type": "string", + "format": "date-time" + }, + "manufacturer": { + "description": "Device manufacturer.", + "type": "string" + }, + "model": { + "description": "Device model.", + "type": "string" + }, + "operatingSystem": { + "description": "Device operating system.", + "type": "string" + }, + "operatingSystemVersion": { + "description": "Device operating system version.", + "type": "string" + }, + "uuid": { + "description": "Gets a unique identifier for this resource.", + "type": "string" + }, + "version": { + "description": "An integer that is incremented each time the resource is modified.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "akri/connector/resources/telemetry/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/deviceUpdateEvent", + "op": "subscribeevent" + } + ] + }, + "assetUpdateEvent": { + "description": "Telemetry event emitted when an asset is updated.", + "data": { + "title": "AssetUpdateEventSchema", + "type": "object", + "required": [ "assetName", "asset" ], + "properties": { + "assetName": { + "description": "The name of the asset that was updated.", + "type": "string" + }, + "asset": { + "description": "The updated asset resource.", + "title": "Asset", + "type": "object", + "required": [ "deviceRef" ], + "properties": { + "assetTypeRefs": { + "description": "URIs or type definition IDs.", + "type": "array", + "items": { + "type": "string" + } + }, + "attributes": { + "description": "A set of key-value pairs that contain custom attributes set by the customer.", + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + }, + "datasets": { + "description": "Array of data sets that are part of the asset. Each data set describes the data points that make up the set.", + "type": "array", + "items": { + "title": "AssetDatasetSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "dataPoints": { + "type": "array", + "items": { + "title": "AssetDatasetDataPointSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "dataPointConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the data point.", + "type": "string" + }, + "dataSource": { + "description": "The address of the source of the data in the asset (e.g. URL) so that a client can access the data source on the asset.", + "type": "string" + }, + "name": { + "description": "The name of the data point.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "dataSource": { + "description": "Reference to a data source for a given dataset.", + "type": "string" + }, + "datasetConfiguration": { + "description": "Stringified JSON that contains connector-specific JSON string that describes configuration for the specific dataset.", + "type": "string" + }, + "destinations": { + "description": "Destinations for a dataset.", + "type": "array", + "items": { + "title": "DatasetDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "DatasetTarget", + "type": "string", + "enum": [ + "BrokerStateStore", + "Mqtt", + "Storage" + ] + } + } + } + }, + "name": { + "description": "Name of the dataset.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "defaultDatasetsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all datasets. Each dataset can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultDatasetsDestinations": { + "description": "Default destinations for a dataset.", + "type": "array", + "items": { + "title": "DatasetDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "DatasetTarget", + "type": "string", + "enum": [ + "BrokerStateStore", + "Mqtt", + "Storage" + ] + } + } + } + }, + "defaultEventsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all events. Each event can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultEventsDestinations": { + "description": "Default destinations for an event.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "defaultManagementGroupsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all management groups. Each management group can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultStreamsConfiguration": { + "description": "Stringified JSON that contains connector-specific default configuration for all streams. Each stream can have its own configuration that overrides the default settings here.", + "type": "string" + }, + "defaultStreamsDestinations": { + "description": "Default destinations for a stream.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "description": { + "description": "Human-readable description of the asset.", + "type": "string" + }, + "deviceRef": { + "description": "Reference to the device that provides data for this asset. Must provide device name & endpoint on the device to use.", + "title": "AssetDeviceRef", + "type": "object", + "required": [ "deviceName", "endpointName" ], + "properties": { + "deviceName": { + "description": "Name of the device resource.", + "type": "string" + }, + "endpointName": { + "description": "The name of endpoint to use.", + "type": "string" + } + } + }, + "discoveredAssetRefs": { + "description": "Reference to a list of discovered assets. Populated only if the asset has been created from discovery flow. Discovered asset names must be provided.", + "type": "array", + "items": { + "type": "string" + } + }, + "displayName": { + "description": "Human-readable display name.", + "type": "string" + }, + "documentationUri": { + "description": "Asset documentation reference.", + "type": "string" + }, + "enabled": { + "description": "Enabled/Disabled status of the asset.", + "type": "boolean" + }, + "eventGroups": { + "description": "Array of events that are part of the asset. Each event can have per-event configuration.", + "type": "array", + "items": { + "title": "AssetEventGroupSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "dataSource": { + "description": "The address of the notifier of the event in the asset (e.g. URL) so that a client can access the event on the asset.", + "type": "string" + }, + "defaultEventsDestinations": { + "description": "Default destinations for an event.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "eventGroupConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the event. For OPC UA, this could include configuration like, publishingInterval, samplingInterval, and queueSize.", + "type": "string" + }, + "events": { + "description": "Array of events that are part of the asset. Each event can have per-event configuration.", + "type": "array", + "items": { + "title": "AssetEventSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "dataSource": { + "description": "Reference to a data source for a given event.", + "type": "string" + }, + "destinations": { + "description": "Destinations for an event.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "eventConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the specific event.", + "type": "string" + }, + "name": { + "description": "The name of the event.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "name": { + "description": "Name of the event group.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "externalAssetId": { + "description": "Asset ID provided by the customer.", + "type": "string" + }, + "hardwareRevision": { + "description": "Asset hardware revision number.", + "type": "string" + }, + "lastTransitionTime": { + "description": "A timestamp (in UTC) that is updated each time the resource is modified.", + "type": "string", + "format": "date-time" + }, + "managementGroups": { + "description": "Array of management groups that are part of the asset.", + "type": "array", + "items": { + "title": "AssetManagementGroupSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "actions": { + "description": "Array of actions that are part of the management group. Each action can have an individual configuration.", + "type": "array", + "items": { + "title": "AssetManagementGroupActionSchemaElementSchema", + "type": "object", + "required": [ "actionType", "name", "targetUri" ], + "properties": { + "actionConfiguration": { + "description": "Configuration for the action.", + "type": "string" + }, + "actionType": { + "description": "Type of the action.", + "title": "AssetManagementGroupActionType", + "type": "string", + "enum": [ + "Call", + "Read", + "Write" + ] + }, + "name": { + "description": "Name of the action.", + "type": "string" + }, + "targetUri": { + "description": "The target URI on which a client can invoke the specific action.", + "type": "string" + }, + "timeoutInSeconds": { + "description": "Response timeout for the action.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + }, + "topic": { + "description": "The MQTT topic path on which a client will receive the request for the action.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "dataSource": { + "description": "Reference to a data source for a given management group.", + "type": "string" + }, + "defaultTimeoutInSeconds": { + "description": "Default response timeout for all actions that are part of the management group.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + }, + "defaultTopic": { + "description": "Default MQTT topic path on which a client will receive the request for all actions that are part of the management group.", + "type": "string" + }, + "managementGroupConfiguration": { + "description": "Stringified JSON that contains connector-specific configuration for the management group.", + "type": "string" + }, + "name": { + "description": "Name of the management group.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "manufacturer": { + "description": "Asset manufacturer.", + "type": "string" + }, + "manufacturerUri": { + "description": "Asset manufacturer URI.", + "type": "string" + }, + "model": { + "description": "Asset model.", + "type": "string" + }, + "productCode": { + "description": "Asset product code.", + "type": "string" + }, + "serialNumber": { + "description": "Asset serial number.", + "type": "string" + }, + "softwareRevision": { + "description": "Asset software revision number.", + "type": "string" + }, + "streams": { + "description": "Array of streams that are part of the asset. Each stream can have per-stream configuration.", + "type": "array", + "items": { + "title": "AssetStreamSchemaElementSchema", + "type": "object", + "required": [ "name" ], + "properties": { + "destinations": { + "description": "Destinations for a Stream.", + "type": "array", + "items": { + "title": "EventStreamDestination", + "type": "object", + "required": [ "configuration", "target" ], + "properties": { + "configuration": { + "description": "The destination configuration.", + "title": "DestinationConfiguration", + "type": "object", + "properties": { + "key": { + "description": "The BrokerStateStore destination configuration key.", + "type": "string" + }, + "path": { + "description": "The Storage destination configuration path.", + "type": "string" + }, + "qos": { + "description": "The MQTT QoS setting.", + "title": "Qos", + "type": "string", + "enum": [ + "Qos0", + "Qos1" + ] + }, + "retain": { + "description": "When set to 'Keep', messages published to an MQTT broker will have the retain flag set.", + "title": "Retain", + "type": "string", + "enum": [ + "Keep", + "Never" + ] + }, + "topic": { + "description": "The MQTT topic.", + "type": "string" + }, + "ttl": { + "description": "The MQTT TTL setting.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + }, + "target": { + "description": "The target destination.", + "title": "EventStreamTarget", + "type": "string", + "enum": [ + "Mqtt", + "Storage" + ] + } + } + } + }, + "name": { + "description": "Name of the stream definition.", + "type": "string" + }, + "streamConfiguration": { + "description": "Stringified JSON that contains connector-specific JSON string that describes configuration for the specific stream.", + "type": "string" + }, + "typeRef": { + "description": "URI or type definition ID.", + "type": "string" + } + } + } + }, + "uuid": { + "description": "Globally unique, immutable, non-reusable id.", + "type": "string" + }, + "version": { + "description": "A read-only integer that is incremented each time the resource is modified the cloud.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "akri/connector/resources/telemetry/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/assetUpdateEvent", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/eng/dtdl/DeviceDiscoveryService.TM.json b/eng/dtdl/DeviceDiscoveryService.TM.json new file mode 100644 index 0000000000..ecc549639b --- /dev/null +++ b/eng/dtdl/DeviceDiscoveryService.TM.json @@ -0,0 +1,218 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "DeviceDiscoveryService", + "links": [ + { + "rel": "dtv:naming", + "href": "SchemaNames.json", + "type": "application/json" + } + ], + "schemaDefinitions": { + "AkriServiceError": { + "title": "AkriServiceError", + "type": "object", + "required": [ "code", "message", "timestamp" ], + "dtv:errorMessage": "message", + "properties": { + "code": { + "description": "The error code that identifies the error.", + "title": "CodeSchema", + "type": "string", + "enum": [ + "BadRequest", + "InternalError", + "KubeError", + "SerializationError", + "Unauthorized" + ] + }, + "message": { + "description": "A human-readable description of the error.", + "type": "string" + }, + "timestamp": { + "description": "The timestamp (in UTC) when the error occurred.", + "type": "string", + "format": "date-time" + } + } + } + }, + "actions": { + "createOrUpdateDiscoveredDevice": { + "description": "Create or update a discovered device resource.", + "input": { + "description": "The request to create or update a discovered device.", + "type": "object", + "required": [ "discoveredDeviceRequest" ], + "properties": { + "discoveredDeviceRequest": { + "title": "CreateOrUpdateDiscoveredDeviceRequestSchema", + "type": "object", + "required": [ "discoveredDeviceName", "discoveredDevice" ], + "properties": { + "discoveredDeviceName": { + "description": "The name of the discovered device to create or update. This field is used to perform deduplication of discovered devices.", + "type": "string" + }, + "discoveredDevice": { + "description": "The discovered device resource to create or update. Fields omitted in the request will be removed. The specified inbound endpoint will be added or updated in the inbound endpoints map.", + "title": "DiscoveredDevice", + "type": "object", + "properties": { + "attributes": { + "description": "A set of key-value pairs that contain custom attributes set by the customer.", + "type": "object", + "dtv:additionalProperties": { + "type": "string" + } + }, + "endpoints": { + "description": "Connection endpoint URL a device can use to connect to a service.", + "title": "DiscoveredDeviceEndpoints", + "type": "object", + "properties": { + "inbound": { + "description": "Set of endpoints to connect to the device.", + "type": "object", + "dtv:additionalProperties": { + "title": "DiscoveredDeviceInboundEndpointSchema", + "type": "object", + "required": [ "address", "endpointType" ], + "properties": { + "additionalConfiguration": { + "description": "Stringified JSON that contains connectivity type specific further configuration (e.g. OPC UA, Modbus, ONVIF).", + "type": "string" + }, + "address": { + "description": "The endpoint address & port. This can be either an IP address (e.g., 192.168.1.1) or a fully qualified domain name (FQDN, e.g., server.example.com).", + "type": "string" + }, + "endpointType": { + "description": "Type of connection endpoint.", + "type": "string" + }, + "lastUpdatedOn": { + "description": "The timestamp (in UTC) when the endpoint was discovered.", + "type": "string", + "format": "date-time" + }, + "supportedAuthenticationMethods": { + "description": "List of supported authentication methods supported by device for Inbound connections.", + "type": "array", + "items": { + "type": "string" + } + }, + "version": { + "description": "Version associated with the device endpoint.", + "type": "string" + } + } + } + }, + "outbound": { + "description": "Property bag contains the device's outbound endpoints", + "title": "DiscoveredDeviceOutboundEndpointsSchema", + "type": "object", + "required": [ "assigned" ], + "properties": { + "assigned": { + "description": "Endpoints the device can connect to.", + "type": "object", + "dtv:additionalProperties": { + "title": "DeviceOutboundEndpoint", + "type": "object", + "required": [ "address" ], + "properties": { + "address": { + "description": "The endpoint address to connect to.", + "type": "string" + }, + "endpointType": { + "description": "Type of connection used for the messaging endpoint.", + "type": "string" + } + } + } + } + } + } + } + }, + "externalDeviceId": { + "description": "The unique identifier of the device.", + "type": "string" + }, + "manufacturer": { + "description": "Device manufacturer.", + "type": "string" + }, + "model": { + "description": "Device model.", + "type": "string" + }, + "operatingSystem": { + "description": "Device operating system name.", + "type": "string" + }, + "operatingSystemVersion": { + "description": "Device operating system version.", + "type": "string" + } + } + } + } + } + } + }, + "output": { + "description": "Response containing the discovered device response or error details if the operation failed.", + "type": "object", + "required": [ "discoveredDeviceResponse" ], + "properties": { + "discoveredDeviceResponse": { + "description": "The discovered device response.", + "title": "DiscoveredDeviceResponseSchema", + "type": "object", + "required": [ "discoveryId", "version" ], + "properties": { + "discoveryId": { + "description": "The unique identifier for the discovered device.", + "type": "string" + }, + "version": { + "description": "The version of the discovered device resource.", + "type": "integer", + "minimum": 0, + "maximum": 18446744073709551615 + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "AkriServiceError" + } + ], + "dtv:topic": "akri/discovery/resources/{ex:discoveryClientId}/{ex:inboundEndpointType}/createOrUpdateDiscoveredDevice", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/eng/dtdl/SchemaNames.json b/eng/dtdl/SchemaNames.json new file mode 100644 index 0000000000..19c8363589 --- /dev/null +++ b/eng/dtdl/SchemaNames.json @@ -0,0 +1,52 @@ +{ + "aggregateEventName": "Telemetry", + "aggregateEventSchema": "TelemetryCollection", + "aggregatePropName": "Property", + "aggregateReadRespValueField": "Properties", + "aggregateRespErrorField": "Errors", + "eventSchema": { + "in": [ "eventName" ], + "out": "{eventName}Telemetry", + "capitalize": true + }, + "eventValueSchema": { + "in": [ "eventName" ], + "out": "{eventName}Schema", + "capitalize": true + }, + "eventSenderBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Sender", + "capitalize": true + }, + "eventReceiverBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Receiver", + "capitalize": true + }, + "actionInSchema": { + "in": [ "actionName" ], + "out": "{actionName}RequestPayload", + "capitalize": true + }, + "actionOutSchema": { + "in": [ "actionName" ], + "out": "{actionName}ResponsePayload", + "capitalize": true + }, + "actionExecutorBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandExecutor", + "capitalize": true + }, + "actionInvokerBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandInvoker", + "capitalize": true + }, + "actionRespErrorField": { + "in": [ "actionName", "errorSchemaName" ], + "out": "{actionName}Error", + "capitalize": false + } +} diff --git a/eng/dtdl/SchemaRegistry.TM.json b/eng/dtdl/SchemaRegistry.TM.json new file mode 100644 index 0000000000..b323fea47c --- /dev/null +++ b/eng/dtdl/SchemaRegistry.TM.json @@ -0,0 +1,408 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "SchemaRegistry", + "links": [ + { + "rel": "dtv:naming", + "href": "SchemaNames.json", + "type": "application/json" + } + ], + "schemaDefinitions": { + "SchemaRegistryError": { + "description": "Error object for schema operations", + "title": "SchemaRegistryError", + "type": "object", + "required": [ "code", "message" ], + "dtv:errorMessage": "message", + "properties": { + "code": { + "description": "Error code for classification of errors (ex: '400', '404', '500', etc.).", + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "message": { + "description": "Human-readable error message.", + "type": "string" + }, + "target": { + "description": "Target of the error, if applicable (e.g., 'schemaType').", + "type": "string" + }, + "details": { + "description": "Additional details about the error, if available.", + "title": "SchemaRegistryErrorDetails", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (e.g., '400.200').", + "type": "string" + }, + "message": { + "description": "Human-readable helpful error message to provide additional context for the error", + "type": "string" + }, + "correlationId": { + "description": "Correlation ID for tracing the error across systems.", + "type": "string" + } + } + }, + "innerError": { + "description": "Inner error object for nested errors, if applicable.", + "title": "SchemaRegistryErrorDetails", + "type": "object", + "properties": { + "code": { + "description": "Multi-part error code for classification and root causing of errors (e.g., '400.200').", + "type": "string" + }, + "message": { + "description": "Human-readable helpful error message to provide additional context for the error", + "type": "string" + }, + "correlationId": { + "description": "Correlation ID for tracing the error across systems.", + "type": "string" + } + } + } + } + }, + "Format": { + "type": "object", + "const": { + "JsonSchemaDraft07": "JsonSchema/draft-07", + "Delta1": "Delta/1.0" + }, + "description": "Supported schema formats", + "properties": { + "JsonSchemaDraft07": { + "description": "JSON Schema Draft-07 format", + "type": "string" + }, + "Delta1": { + "description": "Delta-Parquet format", + "type": "string" + } + } + }, + "SchemaRegistryErrorCode": { + "type": "object", + "const": { + "BadRequest": 400, + "NotFound": 404, + "InternalError": 500 + }, + "properties": { + "BadRequest": { + "description": "The request is invalid or malformed.", + "type": "integer" + }, + "NotFound": { + "description": "The target resource was not found.", + "type": "integer" + }, + "InternalError": { + "description": "An internal server error occurred.", + "type": "integer" + } + } + }, + "SchemaRegistryErrorTarget": { + "type": "object", + "const": { + "schemaTypeProperty": "SchemaType", + "formatProperty": "Format", + "versionProperty": "Version", + "nameProperty": "Name", + "tagsProperty": "Tags", + "descriptionProperty": "Description", + "displayNameProperty": "DisplayName", + "schemaContentProperty": "SchemaContent", + "schemaArmResource": "SchemaArmResource", + "schemaVersionArmResource": "SchemaVersionArmResource", + "SchemaRegistryArmResource": "SchemaRegistryResource" + }, + "properties": { + "schemaTypeProperty": { + "description": "Indicates the error is related to the schema type.", + "type": "string" + }, + "formatProperty": { + "description": "Indicates the error is related to the schema format.", + "type": "string" + }, + "versionProperty": { + "description": "Indicates the error is related to the schema version.", + "type": "string" + }, + "nameProperty": { + "description": "Indicates the error is related to the schema name.", + "type": "string" + }, + "tagsProperty": { + "description": "Indicates the error is related to schema tags.", + "type": "string" + }, + "descriptionProperty": { + "description": "Indicates the error is related to the schema description.", + "type": "string" + }, + "displayNameProperty": { + "description": "Indicates the error is related to the schema display name.", + "type": "string" + }, + "schemaContentProperty": { + "description": "Indicates the error is related to the schema content.", + "type": "string" + }, + "schemaArmResource": { + "description": "Indicates the error is related to the schema ARM resource.", + "type": "string" + }, + "schemaVersionArmResource": { + "description": "Indicates the error is related to the schema version ARM resource.", + "type": "string" + }, + "SchemaRegistryArmResource": { + "description": "Indicates the error is related to the schema registry ARM resource.", + "type": "string" + } + } + } + }, + "actions": { + "put": { + "description": "PUT Schema Command", + "input": { + "description": "PUT Schema request object", + "title": "PutRequestSchema", + "type": "object", + "required": [ "format", "schemaType", "schemaContent", "version" ], + "properties": { + "format": { + "description": "Format of the schema.", + "type": "string" + }, + "schemaType": { + "description": "Type of the schema.", + "title": "SchemaType", + "type": "string", + "enum": [ + "MessageSchema" + ] + }, + "schemaContent": { + "description": "Content stored in the schema.", + "type": "string" + }, + "version": { + "description": "Version of the schema. Allowed between 0-9.", + "type": "string" + }, + "description": { + "description": "Human-readable description of the schema.", + "type": "string" + }, + "displayName": { + "description": "Human-readable display name.", + "type": "string" + }, + "tags": { + "description": "Schema tags.", + "type": "object", + "dtv:additionalProperties": { + "description": "User-defined schema tags", + "type": "string" + } + } + } + }, + "output": { + "description": "PUT Schema response object", + "type": "object", + "required": [ "schema" ], + "properties": { + "schema": { + "description": "Schema object that was created.", + "title": "Schema", + "type": "object", + "required": [ "name", "format", "schemaType", "version", "schemaContent", "namespace" ], + "properties": { + "name": { + "description": "Schema name.", + "type": "string" + }, + "format": { + "description": "Format of the schema.", + "type": "string" + }, + "schemaType": { + "description": "Type of the schema.", + "title": "SchemaType", + "type": "string", + "enum": [ + "MessageSchema" + ] + }, + "version": { + "description": "Version of the schema. Allowed between 0-9.", + "type": "string" + }, + "schemaContent": { + "description": "Content stored in the schema.", + "type": "string" + }, + "description": { + "description": "Human-readable description of the schema.", + "type": "string" + }, + "displayName": { + "description": "Human-readable display name.", + "type": "string" + }, + "tags": { + "description": "Schema tags.", + "type": "object", + "dtv:additionalProperties": { + "description": "User-defined schema tags", + "type": "string" + } + }, + "hash": { + "description": "Hash of the schema content.", + "type": "string" + }, + "namespace": { + "description": "Schema registry namespace. Uniquely identifies a schema registry within a tenant.", + "type": "string" + } + } + } + } + }, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "SchemaRegistryError" + } + ], + "dtv:serviceGroupId": "schema-registry-edge", + "dtv:topic": "adr/dtmi:ms:adr:SchemaRegistry;2/put", + "op": "invokeaction" + } + ] + }, + "get": { + "description": "GET Schema Command", + "input": { + "description": "GET Schema request object", + "title": "GetRequestSchema", + "type": "object", + "required": [ "name", "version" ], + "properties": { + "name": { + "description": "Schema name.", + "type": "string" + }, + "version": { + "description": "Version of the schema. Allowed between 0-9.", + "type": "string" + } + } + }, + "output": { + "description": "GET Schema response object", + "type": "object", + "required": [ "schema" ], + "properties": { + "schema": { + "description": "The requested schema object.", + "title": "Schema", + "type": "object", + "required": [ "name", "format", "schemaType", "version", "schemaContent", "namespace" ], + "properties": { + "name": { + "description": "Schema name.", + "type": "string" + }, + "format": { + "description": "Format of the schema.", + "type": "string" + }, + "schemaType": { + "description": "Type of the schema.", + "title": "SchemaType", + "type": "string", + "enum": [ + "MessageSchema" + ] + }, + "version": { + "description": "Version of the schema. Allowed between 0-9.", + "type": "string" + }, + "schemaContent": { + "description": "Content stored in the schema.", + "type": "string" + }, + "description": { + "description": "Human-readable description of the schema.", + "type": "string" + }, + "displayName": { + "description": "Human-readable display name.", + "type": "string" + }, + "tags": { + "description": "Schema tags.", + "type": "object", + "dtv:additionalProperties": { + "description": "User-defined schema tags", + "type": "string" + } + }, + "hash": { + "description": "Hash of the schema content.", + "type": "string" + }, + "namespace": { + "description": "Schema registry namespace. Uniquely identifies a schema registry within a tenant.", + "type": "string" + } + } + } + } + }, + "idempotent": true, + "safe": true, + "forms": [ + { + "contentType": "application/json", + "additionalResponses": [ + { + "success": false, + "schema": "SchemaRegistryError" + } + ], + "dtv:serviceGroupId": "schema-registry-edge", + "dtv:topic": "adr/dtmi:ms:adr:SchemaRegistry;2/get", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/eng/dtdl/StateStore.TM.json b/eng/dtdl/StateStore.TM.json new file mode 100644 index 0000000000..7f32b61b3b --- /dev/null +++ b/eng/dtdl/StateStore.TM.json @@ -0,0 +1,36 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "StateStore", + "links": [ + { + "rel": "dtv:naming", + "href": "SchemaNames.json", + "type": "application/json" + } + ], + "actions": { + "invoke": { + "input": { + "type": "null" + }, + "output": { + "type": "null" + }, + "forms": [ + { + "contentType": "application/octet-stream", + "dtv:topic": "statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invoke", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + } +} diff --git a/eng/test/schema-samples/Counter.TM.json b/eng/test/schema-samples/Counter.TM.json new file mode 100644 index 0000000000..87e3266163 --- /dev/null +++ b/eng/test/schema-samples/Counter.TM.json @@ -0,0 +1,102 @@ +{ + "@context": [ + "https://www.w3.org/2022/wot/td/v1.1", + { "dtv": "http://azure.com/DigitalTwins/dtmi#" } + ], + "@type": "tm:ThingModel", + "title": "Counter", + "links": [ + { + "rel": "dtv:naming", + "href": "SchemaNames.json", + "type": "application/json" + } + ], + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "telemetry/telemetry-samples/counterValue", + "op": "subscribeallevents" + } + ], + "actions": { + "readCounter": { + "output": { + "type": "object", + "required": [ "CounterResponse" ], + "properties": { + "CounterResponse": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "rpc/command-samples/{executorId}/readCounter", + "op": "invokeaction" + } + ] + }, + "increment": { + "input": { + "type": "object", + "required": [ "incrementValue" ], + "properties": { + "incrementValue": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + }, + "output": { + "type": "object", + "required": [ "CounterResponse" ], + "properties": { + "CounterResponse": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + } + } + }, + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "rpc/command-samples/{executorId}/increment", + "op": "invokeaction" + } + ] + }, + "reset": { + "forms": [ + { + "contentType": "application/json", + "dtv:topic": "rpc/command-samples/{executorId}/reset", + "op": "invokeaction" + } + ] + } + }, + "properties": { + }, + "events": { + "CounterValue": { + "description": "The current value of the counter.", + "data": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "forms": [ + { + "contentType": "application/json", + "op": "subscribeevent" + } + ] + } + } +} diff --git a/eng/test/schema-samples/SchemaNames.json b/eng/test/schema-samples/SchemaNames.json new file mode 100644 index 0000000000..19c8363589 --- /dev/null +++ b/eng/test/schema-samples/SchemaNames.json @@ -0,0 +1,52 @@ +{ + "aggregateEventName": "Telemetry", + "aggregateEventSchema": "TelemetryCollection", + "aggregatePropName": "Property", + "aggregateReadRespValueField": "Properties", + "aggregateRespErrorField": "Errors", + "eventSchema": { + "in": [ "eventName" ], + "out": "{eventName}Telemetry", + "capitalize": true + }, + "eventValueSchema": { + "in": [ "eventName" ], + "out": "{eventName}Schema", + "capitalize": true + }, + "eventSenderBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Sender", + "capitalize": true + }, + "eventReceiverBinder": { + "in": [ "eventSchema" ], + "out": "{eventSchema}Receiver", + "capitalize": true + }, + "actionInSchema": { + "in": [ "actionName" ], + "out": "{actionName}RequestPayload", + "capitalize": true + }, + "actionOutSchema": { + "in": [ "actionName" ], + "out": "{actionName}ResponsePayload", + "capitalize": true + }, + "actionExecutorBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandExecutor", + "capitalize": true + }, + "actionInvokerBinder": { + "in": [ "actionName" ], + "out": "{actionName}CommandInvoker", + "capitalize": true + }, + "actionRespErrorField": { + "in": [ "actionName", "errorSchemaName" ], + "out": "{actionName}Error", + "capitalize": false + } +} diff --git a/rust/azure_iot_operations_services/gen.sh b/rust/azure_iot_operations_services/gen.sh index 53a1e235e2..24cbc7152a 100755 --- a/rust/azure_iot_operations_services/gen.sh +++ b/rust/azure_iot_operations_services/gen.sh @@ -1,17 +1,17 @@ #!/bin/sh rm -r ./src/schema_registry/schemaregistry_gen -../../codegen/src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler \ - --clientOnly --modelFile ../../eng/dtdl/SchemaRegistry-1.json --sdkPath ../ --lang=rust --noProj \ - --outDir src/schema_registry/schemaregistry_gen +../../codegen2/src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler \ + --thingFiles ../../eng/dtdl/SchemaRegistry.TM.json --outDir src/schema_registry/schemaregistry_gen --lang rust \ + --namespace SchemaRegistry --workingDir target/akri/SchemaRegistry --sdkPath ../ --noProj --clientOnly rm -r ./src/azure_device_registry/adr_base_gen -../../codegen/src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler \ - --clientOnly --modelFile ../../eng/dtdl/adr-base-service.json --sdkPath ../ --lang=rust --noProj \ - --outDir src/azure_device_registry/adr_base_gen +../../codegen2/src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler \ + --thingFiles ../../eng/dtdl/AdrBaseService.TM.json --outDir src/azure_device_registry/adr_base_gen --lang rust \ + --namespace AdrBaseService --workingDir target/akri/AdrBaseService --sdkPath ../ --noProj rm -r ./src/azure_device_registry/device_discovery_gen -../../codegen/src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler \ - --clientOnly --modelFile ../../eng/dtdl/device-discovery-service.json --sdkPath ../ --lang=rust --noProj \ - --outDir src/azure_device_registry/device_discovery_gen +../../codegen2/src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler \ + --thingFiles ../../eng/dtdl/DeviceDiscoveryService.TM.json --outDir src/azure_device_registry/device_discovery_gen --lang rust \ + --namespace DeviceDiscoveryService --workingDir target/akri/DeviceDiscoveryService --sdkPath ../ --noProj --clientOnly cargo fmt diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service.rs index f53df22f86..217162aa84 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service.rs @@ -21,11 +21,13 @@ mod asset_stream_schema_element_schema; mod asset_update_event_schema; mod asset_update_event_telemetry; mod asset_update_event_telemetry_receiver; +mod asset_update_event_telemetry_sender; mod asset_update_event_telemetry_serialization; mod authentication_schema; mod code_schema; mod config_error; mod config_status; +mod create_or_update_discovered_asset_command_executor; mod create_or_update_discovered_asset_command_invoker; mod create_or_update_discovered_asset_request_payload; mod create_or_update_discovered_asset_request_payload_serialization; @@ -47,6 +49,7 @@ mod device_status_inbound_endpoint_schema_map_value_schema; mod device_update_event_schema; mod device_update_event_telemetry; mod device_update_event_telemetry_receiver; +mod device_update_event_telemetry_sender; mod device_update_event_telemetry_serialization; mod discovered_asset; mod discovered_asset_dataset; @@ -59,6 +62,7 @@ mod discovered_asset_response_schema; mod discovered_asset_stream; mod event_stream_destination; mod event_stream_target; +mod get_asset_command_executor; mod get_asset_command_invoker; mod get_asset_request_payload; mod get_asset_request_payload_serialization; @@ -66,6 +70,7 @@ mod get_asset_response_payload; mod get_asset_response_payload_serialization; mod get_asset_response_schema; mod get_asset_response_schema_serialization; +mod get_asset_status_command_executor; mod get_asset_status_command_invoker; mod get_asset_status_request_payload; mod get_asset_status_request_payload_serialization; @@ -73,11 +78,13 @@ mod get_asset_status_response_payload; mod get_asset_status_response_payload_serialization; mod get_asset_status_response_schema; mod get_asset_status_response_schema_serialization; +mod get_device_command_executor; mod get_device_command_invoker; mod get_device_response_payload; mod get_device_response_payload_serialization; mod get_device_response_schema; mod get_device_response_schema_serialization; +mod get_device_status_command_executor; mod get_device_status_command_invoker; mod get_device_status_response_payload; mod get_device_status_response_payload_serialization; @@ -90,6 +97,7 @@ mod notification_preference; mod outbound_schema; mod qos; mod retain; +mod set_notification_preference_for_asset_updates_command_executor; mod set_notification_preference_for_asset_updates_command_invoker; mod set_notification_preference_for_asset_updates_request_payload; mod set_notification_preference_for_asset_updates_request_payload_serialization; @@ -98,6 +106,7 @@ mod set_notification_preference_for_asset_updates_response_payload; mod set_notification_preference_for_asset_updates_response_payload_serialization; mod set_notification_preference_for_asset_updates_response_schema; mod set_notification_preference_for_asset_updates_response_schema_serialization; +mod set_notification_preference_for_device_updates_command_executor; mod set_notification_preference_for_device_updates_command_invoker; mod set_notification_preference_for_device_updates_request_payload; mod set_notification_preference_for_device_updates_request_payload_serialization; @@ -106,6 +115,7 @@ mod set_notification_preference_for_device_updates_response_payload_serializatio mod set_notification_preference_for_device_updates_response_schema; mod set_notification_preference_for_device_updates_response_schema_serialization; mod trust_settings_schema; +mod update_asset_status_command_executor; mod update_asset_status_command_invoker; mod update_asset_status_request_payload; mod update_asset_status_request_payload_serialization; @@ -114,6 +124,7 @@ mod update_asset_status_response_payload; mod update_asset_status_response_payload_serialization; mod update_asset_status_response_schema; mod update_asset_status_response_schema_serialization; +mod update_device_status_command_executor; mod update_device_status_command_invoker; mod update_device_status_request_payload; mod update_device_status_request_payload_serialization; @@ -126,12 +137,9 @@ mod x509credentials_schema; pub use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +pub use super::common_types::options::{CommandExecutorOptions, TelemetrySenderOptions}; pub use super::common_types::options::{CommandInvokerOptions, TelemetryReceiverOptions}; -pub const MODEL_ID: &str = "dtmi:com:microsoft:akri:AdrBaseService;1"; -pub const REQUEST_TOPIC_PATTERN: &str = "akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/{commandName}"; -pub const TELEMETRY_TOPIC_PATTERN: &str = "akri/connector/resources/telemetry/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/{telemetryName}"; - pub mod client { pub use super::akri_service_error::*; pub use super::akri_service_error_error::*; @@ -229,3 +237,101 @@ pub mod client { pub use super::username_password_credentials_schema::*; pub use super::x509credentials_schema::*; } + +pub mod service { + pub use super::akri_service_error::*; + pub use super::akri_service_error_error::*; + pub use super::asset::*; + pub use super::asset_dataset_data_point_schema_element_schema::*; + pub use super::asset_dataset_event_stream_status::*; + pub use super::asset_dataset_schema_element_schema::*; + pub use super::asset_device_ref::*; + pub use super::asset_event_group_schema_element_schema::*; + pub use super::asset_event_group_status_schema_element_schema::*; + pub use super::asset_event_schema_element_schema::*; + pub use super::asset_management_group_action_schema_element_schema::*; + pub use super::asset_management_group_action_status_schema_element_schema::*; + pub use super::asset_management_group_action_type::*; + pub use super::asset_management_group_schema_element_schema::*; + pub use super::asset_management_group_status_schema_element_schema::*; + pub use super::asset_status::*; + pub use super::asset_stream_schema_element_schema::*; + pub use super::asset_update_event_schema::*; + pub use super::asset_update_event_telemetry::*; + pub use super::asset_update_event_telemetry_sender::*; + pub use super::authentication_schema::*; + pub use super::code_schema::*; + pub use super::config_error::*; + pub use super::config_status::*; + pub use super::create_or_update_discovered_asset_command_executor::*; + pub use super::create_or_update_discovered_asset_request_payload::*; + pub use super::create_or_update_discovered_asset_request_schema::*; + pub use super::create_or_update_discovered_asset_response_payload::*; + pub use super::create_or_update_discovered_asset_response_schema::*; + pub use super::dataset_destination::*; + pub use super::dataset_target::*; + pub use super::destination_configuration::*; + pub use super::details_schema_element_schema::*; + pub use super::device::*; + pub use super::device_endpoints_schema::*; + pub use super::device_outbound_endpoint::*; + pub use super::device_status::*; + pub use super::device_status_endpoint_schema::*; + pub use super::device_status_inbound_endpoint_schema_map_value_schema::*; + pub use super::device_update_event_schema::*; + pub use super::device_update_event_telemetry::*; + pub use super::device_update_event_telemetry_sender::*; + pub use super::discovered_asset::*; + pub use super::discovered_asset_dataset::*; + pub use super::discovered_asset_dataset_data_point::*; + pub use super::discovered_asset_event::*; + pub use super::discovered_asset_event_group::*; + pub use super::discovered_asset_management_group::*; + pub use super::discovered_asset_management_group_action::*; + pub use super::discovered_asset_response_schema::*; + pub use super::discovered_asset_stream::*; + pub use super::event_stream_destination::*; + pub use super::event_stream_target::*; + pub use super::get_asset_command_executor::*; + pub use super::get_asset_request_payload::*; + pub use super::get_asset_response_payload::*; + pub use super::get_asset_response_schema::*; + pub use super::get_asset_status_command_executor::*; + pub use super::get_asset_status_request_payload::*; + pub use super::get_asset_status_response_payload::*; + pub use super::get_asset_status_response_schema::*; + pub use super::get_device_command_executor::*; + pub use super::get_device_response_payload::*; + pub use super::get_device_response_schema::*; + pub use super::get_device_status_command_executor::*; + pub use super::get_device_status_response_payload::*; + pub use super::get_device_status_response_schema::*; + pub use super::inbound_schema_map_value_schema::*; + pub use super::message_schema_reference::*; + pub use super::method_schema::*; + pub use super::notification_preference::*; + pub use super::outbound_schema::*; + pub use super::qos::*; + pub use super::retain::*; + pub use super::set_notification_preference_for_asset_updates_command_executor::*; + pub use super::set_notification_preference_for_asset_updates_request_payload::*; + pub use super::set_notification_preference_for_asset_updates_request_schema::*; + pub use super::set_notification_preference_for_asset_updates_response_payload::*; + pub use super::set_notification_preference_for_asset_updates_response_schema::*; + pub use super::set_notification_preference_for_device_updates_command_executor::*; + pub use super::set_notification_preference_for_device_updates_request_payload::*; + pub use super::set_notification_preference_for_device_updates_response_payload::*; + pub use super::set_notification_preference_for_device_updates_response_schema::*; + pub use super::trust_settings_schema::*; + pub use super::update_asset_status_command_executor::*; + pub use super::update_asset_status_request_payload::*; + pub use super::update_asset_status_request_schema::*; + pub use super::update_asset_status_response_payload::*; + pub use super::update_asset_status_response_schema::*; + pub use super::update_device_status_command_executor::*; + pub use super::update_device_status_request_payload::*; + pub use super::update_device_status_response_payload::*; + pub use super::update_device_status_response_schema::*; + pub use super::username_password_credentials_schema::*; + pub use super::x509credentials_schema::*; +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset.rs index 2b6fa2f9a8..e0ad57317f 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset.rs @@ -18,6 +18,7 @@ use super::asset_stream_schema_element_schema::AssetStreamSchemaElementSchema; use super::dataset_destination::DatasetDestination; use super::event_stream_destination::EventStreamDestination; +/// The asset resource #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct Asset { /// URIs or type definition IDs. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_device_ref.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_device_ref.rs index 36893bb3ea..d63c35b644 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_device_ref.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_device_ref.rs @@ -11,7 +11,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; -/// A reference to the Device and Endpoint within the device (connection information) used by brokers to connect that provides data points for this asset. +/// Reference to the device that provides data for this asset. Must provide device name & endpoint on the device to use. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct AssetDeviceRef { /// Name of the device resource. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_management_group_action_type.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_management_group_action_type.rs index ee09b12a0d..1b39df5812 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_management_group_action_type.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_management_group_action_type.rs @@ -2,12 +2,10 @@ use serde::{Deserialize, Serialize}; +/// Type of the action. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum AssetManagementGroupActionType { - #[serde(rename = "Call")] Call, - #[serde(rename = "Read")] Read, - #[serde(rename = "Write")] Write, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_status.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_status.rs index 19a60fe19b..4fe4947abb 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_status.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_status.rs @@ -15,6 +15,7 @@ use super::asset_event_group_status_schema_element_schema::AssetEventGroupStatus use super::asset_management_group_status_schema_element_schema::AssetManagementGroupStatusSchemaElementSchema; use super::config_status::ConfigStatus; +/// The asset status #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct AssetStatus { /// The configuration status of the asset. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry.rs index 0665bb372c..1019670d6a 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::asset_update_event_schema::AssetUpdateEventSchema; +/// Telemetry event emitted when an asset is updated. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct AssetUpdateEventTelemetry { /// Telemetry event emitted when an asset is updated. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry_receiver.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry_receiver.rs index 225848af3e..e3c6cccfbf 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry_receiver.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry_receiver.rs @@ -8,8 +8,6 @@ use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; use azure_iot_operations_protocol::telemetry; use super::super::common_types::options::TelemetryReceiverOptions; -use super::MODEL_ID; -use super::TELEMETRY_TOPIC_PATTERN; use super::asset_update_event_telemetry::AssetUpdateEventTelemetry; pub type AssetUpdateEventTelemetryMessage = telemetry::receiver::Message; @@ -32,18 +30,15 @@ impl AssetUpdateEventTelemetryReceiver { receiver_options_builder.topic_namespace(topic_namespace.clone()); } - let mut topic_token_map: HashMap = options + let topic_token_map: HashMap = options .topic_token_map .clone() .into_iter() .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); - topic_token_map.insert("telemetryName".to_string(), "assetUpdateEvent".to_string()); - let receiver_options = receiver_options_builder - .topic_pattern(TELEMETRY_TOPIC_PATTERN) + .topic_pattern("akri/connector/resources/telemetry/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/assetUpdateEvent") .topic_token_map(topic_token_map) .auto_ack(options.auto_ack) .build() diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry_sender.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry_sender.rs new file mode 100644 index 0000000000..11a414a1df --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/asset_update_event_telemetry_sender.rs @@ -0,0 +1,136 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; +use std::time::Duration; + +use azure_iot_operations_mqtt::control_packet::QoS; +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::telemetry; + +use super::super::common_types::options::TelemetrySenderOptions; +use super::asset_update_event_telemetry::AssetUpdateEventTelemetry; + +pub type AssetUpdateEventTelemetryMessage = telemetry::sender::Message; +pub type AssetUpdateEventTelemetryMessageBuilderError = telemetry::sender::MessageBuilderError; + +/// Builder for [`AssetUpdateEventTelemetryMessage`] +#[derive(Default)] +pub struct AssetUpdateEventTelemetryMessageBuilder { + inner_builder: telemetry::sender::MessageBuilder, + topic_tokens: HashMap, +} + +impl AssetUpdateEventTelemetryMessageBuilder { + /// Quality of Service of the telemetry message. Can only be `AtMostOnce` or `AtLeastOnce`. + pub fn qos(&mut self, qos: QoS) -> &mut Self { + self.inner_builder.qos(qos); + self + } + + /// Custom user data to set on the message + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Topic token keys/values to be replaced into the publish topic of the telemetry message. + /// A prefix of "ex:" will be prepended to each key before scanning the topic pattern. + /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced. + pub fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self { + for (k, v) in topic_tokens { + self.topic_tokens.insert(format!("ex:{k}"), v); + } + self + } + + /// Time before message expires + pub fn message_expiry(&mut self, message_expiry: Duration) -> &mut Self { + self.inner_builder.message_expiry(message_expiry); + self + } + + /// Cloud event for the message + pub fn cloud_event(&mut self, cloud_event: Option) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the message + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: AssetUpdateEventTelemetry, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(payload)?; + Ok(self) + } + + /// Builds a new `AssetUpdateEventTelemetryMessage` + /// + /// # Errors + /// If a required field has not been initialized + pub fn build( + &mut self, + ) -> Result + { + self.inner_builder.topic_tokens(self.topic_tokens.clone()); + + self.inner_builder.build() + } +} + +/// Telemetry Sender for `AssetUpdateEventTelemetry` +pub struct AssetUpdateEventTelemetrySender(telemetry::Sender); + +impl AssetUpdateEventTelemetrySender { + /// Creates a new [`AssetUpdateEventTelemetrySender`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &TelemetrySenderOptions, + ) -> Self { + let mut sender_options_builder = telemetry::sender::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + sender_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("senderId".to_string(), client.client_id().to_string()); + + let sender_options = sender_options_builder + .topic_pattern("akri/connector/resources/telemetry/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/assetUpdateEvent") + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + telemetry::Sender::new(application_context, client, sender_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Sends a [`AssetUpdateEventTelemetryMessage`] + /// + /// # Error + /// [`AIOProtocolError`] if there is a failure sending the message + pub async fn send( + &self, + message: AssetUpdateEventTelemetryMessage, + ) -> Result<(), AIOProtocolError> { + self.0.send(message).await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/authentication_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/authentication_schema.rs index cd3ba91e04..c158222279 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/authentication_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/authentication_schema.rs @@ -14,6 +14,7 @@ use super::method_schema::MethodSchema; use super::username_password_credentials_schema::UsernamePasswordCredentialsSchema; use super::x509credentials_schema::X509credentialsSchema; +/// Defines the client authentication mechanism to the server. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct AuthenticationSchema { /// Defines the method to authenticate the user of the client at the server. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/code_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/code_schema.rs index 36143884f6..26ce660204 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/code_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/code_schema.rs @@ -2,16 +2,12 @@ use serde::{Deserialize, Serialize}; +/// The error code that identifies the error. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum CodeSchema { - #[serde(rename = "BadRequest")] BadRequest, - #[serde(rename = "InternalError")] InternalError, - #[serde(rename = "KubeError")] KubeError, - #[serde(rename = "SerializationError")] SerializationError, - #[serde(rename = "Unauthorized")] Unauthorized, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/config_error.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/config_error.rs index d0fdf4beb8..23055eedab 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/config_error.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/config_error.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::details_schema_element_schema::DetailsSchemaElementSchema; +/// The last error that occurred while processing the configuration. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct ConfigError { /// Error code for classification of errors (ex: '400', '404', '500', etc.). diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/config_status.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/config_status.rs index 3e587be930..35bcdc3a39 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/config_status.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/config_status.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::config_error::ConfigError; +/// The configuration status of the device. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct ConfigStatus { /// The last error that occurred while processing the configuration. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_command_executor.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_command_executor.rs new file mode 100644 index 0000000000..7666ffc875 --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_command_executor.rs @@ -0,0 +1,153 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::rpc_command; + +use super::super::common_types::options::CommandExecutorOptions; +use super::akri_service_error::AkriServiceError; +use super::create_or_update_discovered_asset_request_payload::CreateOrUpdateDiscoveredAssetRequestPayload; +use super::create_or_update_discovered_asset_response_payload::CreateOrUpdateDiscoveredAssetResponsePayload; +use super::create_or_update_discovered_asset_response_schema::CreateOrUpdateDiscoveredAssetResponseSchema; + +pub type CreateOrUpdateDiscoveredAssetRequest = rpc_command::executor::Request< + CreateOrUpdateDiscoveredAssetRequestPayload, + CreateOrUpdateDiscoveredAssetResponseSchema, +>; +pub type CreateOrUpdateDiscoveredAssetResponse = + rpc_command::executor::Response; +pub type CreateOrUpdateDiscoveredAssetResponseBuilderError = + rpc_command::executor::ResponseBuilderError; + +/// Builder for [`CreateOrUpdateDiscoveredAssetResponse`] +#[derive(Default)] +pub struct CreateOrUpdateDiscoveredAssetResponseBuilder { + inner_builder: + rpc_command::executor::ResponseBuilder, +} + +impl CreateOrUpdateDiscoveredAssetResponseBuilder { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event( + &mut self, + cloud_event: Option, + ) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: CreateOrUpdateDiscoveredAssetResponsePayload, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(CreateOrUpdateDiscoveredAssetResponseSchema { + discovered_asset_response: Some(payload.discovered_asset_response), + create_or_update_discovered_asset_error: None, + })?; + Ok(self) + } + + pub fn error(&mut self, error: AkriServiceError) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(CreateOrUpdateDiscoveredAssetResponseSchema { + discovered_asset_response: None, + create_or_update_discovered_asset_error: Some(error), + })?; + Ok(self) + } + + /// Builds a new `CreateOrUpdateDiscoveredAssetResponse` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build( + &mut self, + ) -> Result< + CreateOrUpdateDiscoveredAssetResponse, + CreateOrUpdateDiscoveredAssetResponseBuilderError, + > { + self.inner_builder.build() + } +} + +/// Command Executor for `createOrUpdateDiscoveredAsset` +pub struct CreateOrUpdateDiscoveredAssetCommandExecutor( + rpc_command::Executor< + CreateOrUpdateDiscoveredAssetRequestPayload, + CreateOrUpdateDiscoveredAssetResponseSchema, + >, +); + +impl CreateOrUpdateDiscoveredAssetCommandExecutor { + /// Creates a new [`CreateOrUpdateDiscoveredAssetCommandExecutor`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &CommandExecutorOptions, + ) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/createOrUpdateDiscoveredAsset") + .command_name("createOrUpdateDiscoveredAsset") + .is_idempotent(false) + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`CreateOrUpdateDiscoveredAssetRequest`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv( + &mut self, + ) -> Option> { + self.0.recv().await + } + + /// Shutdown the [`CreateOrUpdateDiscoveredAssetCommandExecutor`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_command_invoker.rs index 121c3d5354..ec6d3451f8 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::create_or_update_discovered_asset_request_payload::CreateOrUpdateDiscoveredAssetRequestPayload; use super::create_or_update_discovered_asset_response_payload::CreateOrUpdateDiscoveredAssetResponsePayload; @@ -128,18 +126,13 @@ impl CreateOrUpdateDiscoveredAssetCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert( - "commandName".to_string(), - "createOrUpdateDiscoveredAsset".to_string(), - ); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/createOrUpdateDiscoveredAsset") .command_name("createOrUpdateDiscoveredAsset") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -178,12 +171,17 @@ impl CreateOrUpdateDiscoveredAssetCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(discovered_asset_response) = - response.payload.discovered_asset_response - { + } else { Ok(Ok(CreateOrUpdateDiscoveredAssetResponse { payload: CreateOrUpdateDiscoveredAssetResponsePayload { - discovered_asset_response, + discovered_asset_response: response + .payload + .discovered_asset_response + .ok_or( + CreateOrUpdateDiscoveredAssetCommandInvoker::get_err( + "discoveredAssetResponse", + ), + )?, }, content_type: response.content_type, format_indicator: response.format_indicator, @@ -191,26 +189,6 @@ impl CreateOrUpdateDiscoveredAssetCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("createOrUpdateDiscoveredAsset".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -225,4 +203,23 @@ impl CreateOrUpdateDiscoveredAssetCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("createOrUpdateDiscoveredAsset".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_request_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_request_payload.rs index dc8617368c..1b652a3821 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_request_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_request_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::create_or_update_discovered_asset_request_schema::CreateOrUpdateDiscoveredAssetRequestSchema; +/// The request to create or update a discovered asset. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct CreateOrUpdateDiscoveredAssetRequestPayload { - /// The Command request argument. + /// The 'discoveredAssetRequest' Field. #[serde(rename = "discoveredAssetRequest")] pub discovered_asset_request: CreateOrUpdateDiscoveredAssetRequestSchema, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_response_payload.rs index 49b203c949..1e698caed3 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_response_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::discovered_asset_response_schema::DiscoveredAssetResponseSchema; +/// Response containing the discovered asset response or error details if the operation failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct CreateOrUpdateDiscoveredAssetResponsePayload { - /// The Command response argument. + /// The discovered asset response. #[serde(rename = "discoveredAssetResponse")] pub discovered_asset_response: DiscoveredAssetResponseSchema, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_response_schema.rs index a877c66167..8f0ff2216e 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/create_or_update_discovered_asset_response_schema.rs @@ -13,9 +13,10 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::akri_service_error::AkriServiceError; use super::discovered_asset_response_schema::DiscoveredAssetResponseSchema; +/// Response containing the discovered asset response or error details if the operation failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct CreateOrUpdateDiscoveredAssetResponseSchema { - /// Error details, if the discovered asset creation or update failed. + /// Read error for the 'createOrUpdateDiscoveredAsset' Action. #[serde(rename = "createOrUpdateDiscoveredAssetError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/dataset_target.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/dataset_target.rs index 273001e34c..253c176ae9 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/dataset_target.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/dataset_target.rs @@ -2,12 +2,10 @@ use serde::{Deserialize, Serialize}; +/// The target destination. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum DatasetTarget { - #[serde(rename = "BrokerStateStore")] BrokerStateStore, - #[serde(rename = "Mqtt")] Mqtt, - #[serde(rename = "Storage")] Storage, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/destination_configuration.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/destination_configuration.rs index f0a72fa564..4c98597015 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/destination_configuration.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/destination_configuration.rs @@ -13,6 +13,7 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::qos::Qos; use super::retain::Retain; +/// The destination configuration. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DestinationConfiguration { /// The BrokerStateStore destination configuration key. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device.rs index 66cfae3272..e71e451291 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device.rs @@ -12,7 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::device_endpoints_schema::DeviceEndpointsSchema; -/// Represents a Device resource, modeled after the devices.namespaces.deviceregistry.microsoft.com CRD in Kubernetes. +/// The device resource, containing the specific inbound endpoint details as specified by the request. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct Device { /// A set of key-value pairs that contain custom attributes set by the customer. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_endpoints_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_endpoints_schema.rs index 4c70efd052..9536a4a6ca 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_endpoints_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_endpoints_schema.rs @@ -13,6 +13,7 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::inbound_schema_map_value_schema::InboundSchemaMapValueSchema; use super::outbound_schema::OutboundSchema; +/// Connection endpoint url a device can use to connect to a service. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DeviceEndpointsSchema { /// The 'inbound' Field. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_status.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_status.rs index d067aad8f1..d75e270edd 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_status.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_status.rs @@ -13,6 +13,7 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::config_status::ConfigStatus; use super::device_status_endpoint_schema::DeviceStatusEndpointSchema; +/// The device status, containing the specific inbound endpoint status as specified by the request. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DeviceStatus { /// The configuration status of the device. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_status_endpoint_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_status_endpoint_schema.rs index fd66aeced6..0eaedf1158 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_status_endpoint_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_status_endpoint_schema.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::device_status_inbound_endpoint_schema_map_value_schema::DeviceStatusInboundEndpointSchemaMapValueSchema; +/// Defines the device status for inbound/outbound endpoints. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DeviceStatusEndpointSchema { /// The 'inbound' Field. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry.rs index 0cf854079e..64d63ac1b7 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::device_update_event_schema::DeviceUpdateEventSchema; +/// Telemetry event emitted when a device is updated, containing the relevant inbound endpoint details as specified in the topic. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DeviceUpdateEventTelemetry { /// Telemetry event emitted when a device is updated, containing the relevant inbound endpoint details as specified in the topic. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry_receiver.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry_receiver.rs index 6c226dcec5..2798f14a7f 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry_receiver.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry_receiver.rs @@ -8,8 +8,6 @@ use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; use azure_iot_operations_protocol::telemetry; use super::super::common_types::options::TelemetryReceiverOptions; -use super::MODEL_ID; -use super::TELEMETRY_TOPIC_PATTERN; use super::device_update_event_telemetry::DeviceUpdateEventTelemetry; pub type DeviceUpdateEventTelemetryMessage = @@ -33,18 +31,15 @@ impl DeviceUpdateEventTelemetryReceiver { receiver_options_builder.topic_namespace(topic_namespace.clone()); } - let mut topic_token_map: HashMap = options + let topic_token_map: HashMap = options .topic_token_map .clone() .into_iter() .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); - topic_token_map.insert("telemetryName".to_string(), "deviceUpdateEvent".to_string()); - let receiver_options = receiver_options_builder - .topic_pattern(TELEMETRY_TOPIC_PATTERN) + .topic_pattern("akri/connector/resources/telemetry/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/deviceUpdateEvent") .topic_token_map(topic_token_map) .auto_ack(options.auto_ack) .build() diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry_sender.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry_sender.rs new file mode 100644 index 0000000000..8937291bdc --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/device_update_event_telemetry_sender.rs @@ -0,0 +1,136 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; +use std::time::Duration; + +use azure_iot_operations_mqtt::control_packet::QoS; +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::telemetry; + +use super::super::common_types::options::TelemetrySenderOptions; +use super::device_update_event_telemetry::DeviceUpdateEventTelemetry; + +pub type DeviceUpdateEventTelemetryMessage = telemetry::sender::Message; +pub type DeviceUpdateEventTelemetryMessageBuilderError = telemetry::sender::MessageBuilderError; + +/// Builder for [`DeviceUpdateEventTelemetryMessage`] +#[derive(Default)] +pub struct DeviceUpdateEventTelemetryMessageBuilder { + inner_builder: telemetry::sender::MessageBuilder, + topic_tokens: HashMap, +} + +impl DeviceUpdateEventTelemetryMessageBuilder { + /// Quality of Service of the telemetry message. Can only be `AtMostOnce` or `AtLeastOnce`. + pub fn qos(&mut self, qos: QoS) -> &mut Self { + self.inner_builder.qos(qos); + self + } + + /// Custom user data to set on the message + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Topic token keys/values to be replaced into the publish topic of the telemetry message. + /// A prefix of "ex:" will be prepended to each key before scanning the topic pattern. + /// Thus, only tokens of the form `{ex:SOMEKEY}` will be replaced. + pub fn topic_tokens(&mut self, topic_tokens: HashMap) -> &mut Self { + for (k, v) in topic_tokens { + self.topic_tokens.insert(format!("ex:{k}"), v); + } + self + } + + /// Time before message expires + pub fn message_expiry(&mut self, message_expiry: Duration) -> &mut Self { + self.inner_builder.message_expiry(message_expiry); + self + } + + /// Cloud event for the message + pub fn cloud_event(&mut self, cloud_event: Option) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the message + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: DeviceUpdateEventTelemetry, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(payload)?; + Ok(self) + } + + /// Builds a new `DeviceUpdateEventTelemetryMessage` + /// + /// # Errors + /// If a required field has not been initialized + pub fn build( + &mut self, + ) -> Result + { + self.inner_builder.topic_tokens(self.topic_tokens.clone()); + + self.inner_builder.build() + } +} + +/// Telemetry Sender for `DeviceUpdateEventTelemetry` +pub struct DeviceUpdateEventTelemetrySender(telemetry::Sender); + +impl DeviceUpdateEventTelemetrySender { + /// Creates a new [`DeviceUpdateEventTelemetrySender`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &TelemetrySenderOptions, + ) -> Self { + let mut sender_options_builder = telemetry::sender::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + sender_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("senderId".to_string(), client.client_id().to_string()); + + let sender_options = sender_options_builder + .topic_pattern("akri/connector/resources/telemetry/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/deviceUpdateEvent") + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + telemetry::Sender::new(application_context, client, sender_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Sends a [`DeviceUpdateEventTelemetryMessage`] + /// + /// # Error + /// [`AIOProtocolError`] if there is a failure sending the message + pub async fn send( + &self, + message: DeviceUpdateEventTelemetryMessage, + ) -> Result<(), AIOProtocolError> { + self.0.send(message).await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/discovered_asset.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/discovered_asset.rs index 2f616ed38a..5cb33fb3c1 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/discovered_asset.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/discovered_asset.rs @@ -18,6 +18,7 @@ use super::discovered_asset_management_group::DiscoveredAssetManagementGroup; use super::discovered_asset_stream::DiscoveredAssetStream; use super::event_stream_destination::EventStreamDestination; +/// The discovered asset resource to create or update. Fields omitted in the request will be removed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DiscoveredAsset { /// URIs or type definition IDs for the asset type. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/discovered_asset_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/discovered_asset_response_schema.rs index 88338e519d..68d1c1ab50 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/discovered_asset_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/discovered_asset_response_schema.rs @@ -11,6 +11,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// The discovered asset response. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DiscoveredAssetResponseSchema { /// The unique identifier for the discovered asset. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/event_stream_target.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/event_stream_target.rs index 79a042e1c4..1c80aea43e 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/event_stream_target.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/event_stream_target.rs @@ -2,10 +2,9 @@ use serde::{Deserialize, Serialize}; +/// The target destination. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum EventStreamTarget { - #[serde(rename = "Mqtt")] Mqtt, - #[serde(rename = "Storage")] Storage, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_command_executor.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_command_executor.rs new file mode 100644 index 0000000000..f203a652be --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_command_executor.rs @@ -0,0 +1,136 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::rpc_command; + +use super::super::common_types::options::CommandExecutorOptions; +use super::akri_service_error::AkriServiceError; +use super::get_asset_request_payload::GetAssetRequestPayload; +use super::get_asset_response_payload::GetAssetResponsePayload; +use super::get_asset_response_schema::GetAssetResponseSchema; + +pub type GetAssetRequest = + rpc_command::executor::Request; +pub type GetAssetResponse = rpc_command::executor::Response; +pub type GetAssetResponseBuilderError = rpc_command::executor::ResponseBuilderError; + +/// Builder for [`GetAssetResponse`] +#[derive(Default)] +pub struct GetAssetResponseBuilder { + inner_builder: rpc_command::executor::ResponseBuilder, +} + +impl GetAssetResponseBuilder { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event( + &mut self, + cloud_event: Option, + ) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: GetAssetResponsePayload, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(GetAssetResponseSchema { + asset: Some(payload.asset), + get_asset_error: None, + })?; + Ok(self) + } + + pub fn error(&mut self, error: AkriServiceError) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(GetAssetResponseSchema { + asset: None, + get_asset_error: Some(error), + })?; + Ok(self) + } + + /// Builds a new `GetAssetResponse` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build(&mut self) -> Result { + self.inner_builder.build() + } +} + +/// Command Executor for `getAsset` +pub struct GetAssetCommandExecutor( + rpc_command::Executor, +); + +impl GetAssetCommandExecutor { + /// Creates a new [`GetAssetCommandExecutor`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &CommandExecutorOptions, + ) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getAsset") + .command_name("getAsset") + .is_idempotent(false) + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`GetAssetRequest`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv(&mut self) -> Option> { + self.0.recv().await + } + + /// Shutdown the [`GetAssetCommandExecutor`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_command_invoker.rs index 6c943fac93..92565bff76 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::get_asset_request_payload::GetAssetRequestPayload; use super::get_asset_response_payload::GetAssetResponsePayload; @@ -115,15 +113,13 @@ impl GetAssetCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "getAsset".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getAsset") .command_name("getAsset") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -157,35 +153,20 @@ impl GetAssetCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(asset) = response.payload.asset { + } else { Ok(Ok(GetAssetResponse { - payload: GetAssetResponsePayload { asset }, + payload: GetAssetResponsePayload { + asset: response + .payload + .asset + .ok_or(GetAssetCommandInvoker::get_err("asset"))?, + }, content_type: response.content_type, format_indicator: response.format_indicator, custom_user_data: response.custom_user_data, timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("getAsset".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -200,4 +181,23 @@ impl GetAssetCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("getAsset".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_request_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_request_payload.rs index b7fc8fde8b..c9b6001977 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_request_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_request_payload.rs @@ -11,9 +11,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// The name of the asset to retrieve. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetAssetRequestPayload { - /// The Command request argument. + /// The 'assetName' Field. #[serde(rename = "assetName")] pub asset_name: String, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_response_payload.rs index 442d347f7e..ed6c34d1e1 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_response_payload.rs @@ -12,8 +12,9 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::asset::Asset; +/// Response containing the asset resource or error details if the asset could not be retrieved. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetAssetResponsePayload { - /// The Command response argument. + /// The asset resource pub asset: Asset, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_response_schema.rs index 963d7d81b8..b5cfe6b67a 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_response_schema.rs @@ -13,6 +13,7 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::akri_service_error::AkriServiceError; use super::asset::Asset; +/// Response containing the asset resource or error details if the asset could not be retrieved. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetAssetResponseSchema { /// The asset resource @@ -20,7 +21,7 @@ pub struct GetAssetResponseSchema { #[builder(default = "None")] pub asset: Option, - /// Error details, if the asset could not be retrieved. + /// Read error for the 'getAsset' Action. #[serde(rename = "getAssetError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_command_executor.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_command_executor.rs new file mode 100644 index 0000000000..4a864d6fe1 --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_command_executor.rs @@ -0,0 +1,136 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::rpc_command; + +use super::super::common_types::options::CommandExecutorOptions; +use super::akri_service_error::AkriServiceError; +use super::get_asset_status_request_payload::GetAssetStatusRequestPayload; +use super::get_asset_status_response_payload::GetAssetStatusResponsePayload; +use super::get_asset_status_response_schema::GetAssetStatusResponseSchema; + +pub type GetAssetStatusRequest = + rpc_command::executor::Request; +pub type GetAssetStatusResponse = rpc_command::executor::Response; +pub type GetAssetStatusResponseBuilderError = rpc_command::executor::ResponseBuilderError; + +/// Builder for [`GetAssetStatusResponse`] +#[derive(Default)] +pub struct GetAssetStatusResponseBuilder { + inner_builder: rpc_command::executor::ResponseBuilder, +} + +impl GetAssetStatusResponseBuilder { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event( + &mut self, + cloud_event: Option, + ) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: GetAssetStatusResponsePayload, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(GetAssetStatusResponseSchema { + asset_status: Some(payload.asset_status), + get_asset_status_error: None, + })?; + Ok(self) + } + + pub fn error(&mut self, error: AkriServiceError) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(GetAssetStatusResponseSchema { + asset_status: None, + get_asset_status_error: Some(error), + })?; + Ok(self) + } + + /// Builds a new `GetAssetStatusResponse` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build(&mut self) -> Result { + self.inner_builder.build() + } +} + +/// Command Executor for `getAssetStatus` +pub struct GetAssetStatusCommandExecutor( + rpc_command::Executor, +); + +impl GetAssetStatusCommandExecutor { + /// Creates a new [`GetAssetStatusCommandExecutor`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &CommandExecutorOptions, + ) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getAssetStatus") + .command_name("getAssetStatus") + .is_idempotent(false) + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`GetAssetStatusRequest`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv(&mut self) -> Option> { + self.0.recv().await + } + + /// Shutdown the [`GetAssetStatusCommandExecutor`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_command_invoker.rs index 6419a004d8..7b894b2bec 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::get_asset_status_request_payload::GetAssetStatusRequestPayload; use super::get_asset_status_response_payload::GetAssetStatusResponsePayload; @@ -115,15 +113,13 @@ impl GetAssetStatusCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "getAssetStatus".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getAssetStatus") .command_name("getAssetStatus") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -157,35 +153,20 @@ impl GetAssetStatusCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(asset_status) = response.payload.asset_status { + } else { Ok(Ok(GetAssetStatusResponse { - payload: GetAssetStatusResponsePayload { asset_status }, + payload: GetAssetStatusResponsePayload { + asset_status: response + .payload + .asset_status + .ok_or(GetAssetStatusCommandInvoker::get_err("assetStatus"))?, + }, content_type: response.content_type, format_indicator: response.format_indicator, custom_user_data: response.custom_user_data, timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("getAssetStatus".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -200,4 +181,23 @@ impl GetAssetStatusCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("getAssetStatus".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_request_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_request_payload.rs index 8a6bc4fdea..c1e2990343 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_request_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_request_payload.rs @@ -11,9 +11,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// The name of the asset to retrieve the status for. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetAssetStatusRequestPayload { - /// The Command request argument. + /// The 'assetName' Field. #[serde(rename = "assetName")] pub asset_name: String, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_response_payload.rs index fe9cc66119..082f70eac6 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_response_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::asset_status::AssetStatus; +/// Response containing the asset status or error details if the status could not be retrieved. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetAssetStatusResponsePayload { - /// The Command response argument. + /// The asset status #[serde(rename = "assetStatus")] pub asset_status: AssetStatus, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_response_schema.rs index de8dcb7734..d09448be2b 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_asset_status_response_schema.rs @@ -13,6 +13,7 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::akri_service_error::AkriServiceError; use super::asset_status::AssetStatus; +/// Response containing the asset status or error details if the status could not be retrieved. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetAssetStatusResponseSchema { /// The asset status @@ -21,7 +22,7 @@ pub struct GetAssetStatusResponseSchema { #[builder(default = "None")] pub asset_status: Option, - /// Error details, if the asset status could not be retrieved. + /// Read error for the 'getAssetStatus' Action. #[serde(rename = "getAssetStatusError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_command_executor.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_command_executor.rs new file mode 100644 index 0000000000..d7401f9814 --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_command_executor.rs @@ -0,0 +1,133 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::rpc_command; + +use super::super::common_types::empty_json::EmptyJson; +use super::super::common_types::options::CommandExecutorOptions; +use super::akri_service_error::AkriServiceError; +use super::get_device_response_payload::GetDeviceResponsePayload; +use super::get_device_response_schema::GetDeviceResponseSchema; + +pub type GetDeviceRequest = rpc_command::executor::Request; +pub type GetDeviceResponse = rpc_command::executor::Response; +pub type GetDeviceResponseBuilderError = rpc_command::executor::ResponseBuilderError; + +/// Builder for [`GetDeviceResponse`] +#[derive(Default)] +pub struct GetDeviceResponseBuilder { + inner_builder: rpc_command::executor::ResponseBuilder, +} + +impl GetDeviceResponseBuilder { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event( + &mut self, + cloud_event: Option, + ) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: GetDeviceResponsePayload, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(GetDeviceResponseSchema { + device: Some(payload.device), + get_device_error: None, + })?; + Ok(self) + } + + pub fn error(&mut self, error: AkriServiceError) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(GetDeviceResponseSchema { + device: None, + get_device_error: Some(error), + })?; + Ok(self) + } + + /// Builds a new `GetDeviceResponse` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build(&mut self) -> Result { + self.inner_builder.build() + } +} + +/// Command Executor for `getDevice` +pub struct GetDeviceCommandExecutor(rpc_command::Executor); + +impl GetDeviceCommandExecutor { + /// Creates a new [`GetDeviceCommandExecutor`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &CommandExecutorOptions, + ) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getDevice") + .command_name("getDevice") + .is_idempotent(false) + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`GetDeviceRequest`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv(&mut self) -> Option> { + self.0.recv().await + } + + /// Shutdown the [`GetDeviceCommandExecutor`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_command_invoker.rs index d78edeec2d..aad722819b 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::rpc_command; use super::super::common_types::empty_json::EmptyJson; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::get_device_response_payload::GetDeviceResponsePayload; use super::get_device_response_schema::GetDeviceResponseSchema; @@ -102,15 +100,13 @@ impl GetDeviceCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "getDevice".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getDevice") .command_name("getDevice") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -144,35 +140,20 @@ impl GetDeviceCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(device) = response.payload.device { + } else { Ok(Ok(GetDeviceResponse { - payload: GetDeviceResponsePayload { device }, + payload: GetDeviceResponsePayload { + device: response + .payload + .device + .ok_or(GetDeviceCommandInvoker::get_err("device"))?, + }, content_type: response.content_type, format_indicator: response.format_indicator, custom_user_data: response.custom_user_data, timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("getDevice".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -187,4 +168,23 @@ impl GetDeviceCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("getDevice".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_response_payload.rs index 776120dc0b..107d312bb8 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_response_payload.rs @@ -12,8 +12,9 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::device::Device; +/// Response containing the device resource or error details if the device could not be retrieved. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetDeviceResponsePayload { - /// The Command response argument. + /// The device resource, containing the specific inbound endpoint details as specified by the request. pub device: Device, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_response_schema.rs index 856336225d..3da8a00584 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_response_schema.rs @@ -13,6 +13,7 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::akri_service_error::AkriServiceError; use super::device::Device; +/// Response containing the device resource or error details if the device could not be retrieved. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetDeviceResponseSchema { /// The device resource, containing the specific inbound endpoint details as specified by the request. @@ -20,7 +21,7 @@ pub struct GetDeviceResponseSchema { #[builder(default = "None")] pub device: Option, - /// Error details, if the device could not be retrieved. + /// Read error for the 'getDevice' Action. #[serde(rename = "getDeviceError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_command_executor.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_command_executor.rs new file mode 100644 index 0000000000..0103b43121 --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_command_executor.rs @@ -0,0 +1,138 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::rpc_command; + +use super::super::common_types::empty_json::EmptyJson; +use super::super::common_types::options::CommandExecutorOptions; +use super::akri_service_error::AkriServiceError; +use super::get_device_status_response_payload::GetDeviceStatusResponsePayload; +use super::get_device_status_response_schema::GetDeviceStatusResponseSchema; + +pub type GetDeviceStatusRequest = + rpc_command::executor::Request; +pub type GetDeviceStatusResponse = rpc_command::executor::Response; +pub type GetDeviceStatusResponseBuilderError = rpc_command::executor::ResponseBuilderError; + +/// Builder for [`GetDeviceStatusResponse`] +#[derive(Default)] +pub struct GetDeviceStatusResponseBuilder { + inner_builder: rpc_command::executor::ResponseBuilder, +} + +impl GetDeviceStatusResponseBuilder { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event( + &mut self, + cloud_event: Option, + ) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: GetDeviceStatusResponsePayload, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(GetDeviceStatusResponseSchema { + device_status: Some(payload.device_status), + get_device_status_error: None, + })?; + Ok(self) + } + + pub fn error(&mut self, error: AkriServiceError) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder.payload(GetDeviceStatusResponseSchema { + device_status: None, + get_device_status_error: Some(error), + })?; + Ok(self) + } + + /// Builds a new `GetDeviceStatusResponse` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build( + &mut self, + ) -> Result { + self.inner_builder.build() + } +} + +/// Command Executor for `getDeviceStatus` +pub struct GetDeviceStatusCommandExecutor( + rpc_command::Executor, +); + +impl GetDeviceStatusCommandExecutor { + /// Creates a new [`GetDeviceStatusCommandExecutor`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &CommandExecutorOptions, + ) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getDeviceStatus") + .command_name("getDeviceStatus") + .is_idempotent(false) + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`GetDeviceStatusRequest`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv(&mut self) -> Option> { + self.0.recv().await + } + + /// Shutdown the [`GetDeviceStatusCommandExecutor`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_command_invoker.rs index 2eb93c9e93..b3eff624e2 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::rpc_command; use super::super::common_types::empty_json::EmptyJson; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::get_device_status_response_payload::GetDeviceStatusResponsePayload; use super::get_device_status_response_schema::GetDeviceStatusResponseSchema; @@ -104,15 +102,13 @@ impl GetDeviceStatusCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "getDeviceStatus".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/getDeviceStatus") .command_name("getDeviceStatus") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -147,35 +143,20 @@ impl GetDeviceStatusCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(device_status) = response.payload.device_status { + } else { Ok(Ok(GetDeviceStatusResponse { - payload: GetDeviceStatusResponsePayload { device_status }, + payload: GetDeviceStatusResponsePayload { + device_status: response + .payload + .device_status + .ok_or(GetDeviceStatusCommandInvoker::get_err("deviceStatus"))?, + }, content_type: response.content_type, format_indicator: response.format_indicator, custom_user_data: response.custom_user_data, timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("getDeviceStatus".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -190,4 +171,23 @@ impl GetDeviceStatusCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("getDeviceStatus".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_response_payload.rs index 6a0ba7a350..e22e99bc56 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_response_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::device_status::DeviceStatus; +/// Response containing the device status or error details if the status could not be retrieved. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetDeviceStatusResponsePayload { - /// The Command response argument. + /// The device status, containing the specific inbound endpoint status as specified by the request. #[serde(rename = "deviceStatus")] pub device_status: DeviceStatus, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_response_schema.rs index c8f297e7f0..5dbf0734a1 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/get_device_status_response_schema.rs @@ -13,6 +13,7 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::akri_service_error::AkriServiceError; use super::device_status::DeviceStatus; +/// Response containing the device status or error details if the status could not be retrieved. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetDeviceStatusResponseSchema { /// The device status, containing the specific inbound endpoint status as specified by the request. @@ -21,7 +22,7 @@ pub struct GetDeviceStatusResponseSchema { #[builder(default = "None")] pub device_status: Option, - /// Error details, if the device status could not be retrieved. + /// Read error for the 'getDeviceStatus' Action. #[serde(rename = "getDeviceStatusError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/method_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/method_schema.rs index 8e7baca356..8688e876d2 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/method_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/method_schema.rs @@ -2,12 +2,10 @@ use serde::{Deserialize, Serialize}; +/// Defines the method to authenticate the user of the client at the server. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum MethodSchema { - #[serde(rename = "Anonymous")] Anonymous, - #[serde(rename = "Certificate")] Certificate, - #[serde(rename = "UsernamePassword")] UsernamePassword, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/notification_preference.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/notification_preference.rs index fac0a0c4e2..5f206459f7 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/notification_preference.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/notification_preference.rs @@ -4,8 +4,6 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub enum NotificationPreference { - #[serde(rename = "Off")] Off, - #[serde(rename = "On")] On, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/outbound_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/outbound_schema.rs index 44b3eecd3d..465263df3e 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/outbound_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/outbound_schema.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::device_outbound_endpoint::DeviceOutboundEndpoint; +/// Set of endpoints for device to connect to. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct OutboundSchema { /// Device messaging endpoint model. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/qos.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/qos.rs index b9ea630065..76f9c4b52f 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/qos.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/qos.rs @@ -2,10 +2,9 @@ use serde::{Deserialize, Serialize}; +/// The MQTT QoS setting. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum Qos { - #[serde(rename = "Qos0")] Qos0, - #[serde(rename = "Qos1")] Qos1, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/retain.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/retain.rs index 93cab78fe8..fbf63ceaf5 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/retain.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/retain.rs @@ -2,10 +2,9 @@ use serde::{Deserialize, Serialize}; +/// When set to 'Keep', messages published to an MQTT broker will have the retain flag set. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum Retain { - #[serde(rename = "Keep")] Keep, - #[serde(rename = "Never")] Never, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_command_executor.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_command_executor.rs new file mode 100644 index 0000000000..46d4abbc8a --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_command_executor.rs @@ -0,0 +1,154 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::rpc_command; + +use super::super::common_types::options::CommandExecutorOptions; +use super::akri_service_error::AkriServiceError; +use super::set_notification_preference_for_asset_updates_request_payload::SetNotificationPreferenceForAssetUpdatesRequestPayload; +use super::set_notification_preference_for_asset_updates_response_payload::SetNotificationPreferenceForAssetUpdatesResponsePayload; +use super::set_notification_preference_for_asset_updates_response_schema::SetNotificationPreferenceForAssetUpdatesResponseSchema; + +pub type SetNotificationPreferenceForAssetUpdatesRequest = rpc_command::executor::Request< + SetNotificationPreferenceForAssetUpdatesRequestPayload, + SetNotificationPreferenceForAssetUpdatesResponseSchema, +>; +pub type SetNotificationPreferenceForAssetUpdatesResponse = + rpc_command::executor::Response; +pub type SetNotificationPreferenceForAssetUpdatesResponseBuilderError = + rpc_command::executor::ResponseBuilderError; + +/// Builder for [`SetNotificationPreferenceForAssetUpdatesResponse`] +#[derive(Default)] +pub struct SetNotificationPreferenceForAssetUpdatesResponseBuilder { + inner_builder: rpc_command::executor::ResponseBuilder< + SetNotificationPreferenceForAssetUpdatesResponseSchema, + >, +} + +impl SetNotificationPreferenceForAssetUpdatesResponseBuilder { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event( + &mut self, + cloud_event: Option, + ) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: SetNotificationPreferenceForAssetUpdatesResponsePayload, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(SetNotificationPreferenceForAssetUpdatesResponseSchema { + response_payload: Some(payload.response_payload), + set_notification_preference_for_asset_updates_error: None, + })?; + Ok(self) + } + + pub fn error(&mut self, error: AkriServiceError) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(SetNotificationPreferenceForAssetUpdatesResponseSchema { + response_payload: None, + set_notification_preference_for_asset_updates_error: Some(error), + })?; + Ok(self) + } + + /// Builds a new `SetNotificationPreferenceForAssetUpdatesResponse` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build( + &mut self, + ) -> Result< + SetNotificationPreferenceForAssetUpdatesResponse, + SetNotificationPreferenceForAssetUpdatesResponseBuilderError, + > { + self.inner_builder.build() + } +} + +/// Command Executor for `setNotificationPreferenceForAssetUpdates` +pub struct SetNotificationPreferenceForAssetUpdatesCommandExecutor( + rpc_command::Executor< + SetNotificationPreferenceForAssetUpdatesRequestPayload, + SetNotificationPreferenceForAssetUpdatesResponseSchema, + >, +); + +impl SetNotificationPreferenceForAssetUpdatesCommandExecutor { + /// Creates a new [`SetNotificationPreferenceForAssetUpdatesCommandExecutor`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &CommandExecutorOptions, + ) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/setNotificationPreferenceForAssetUpdates") + .command_name("setNotificationPreferenceForAssetUpdates") + .is_idempotent(false) + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`SetNotificationPreferenceForAssetUpdatesRequest`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv( + &mut self, + ) -> Option> { + self.0.recv().await + } + + /// Shutdown the [`SetNotificationPreferenceForAssetUpdatesCommandExecutor`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_command_invoker.rs index 3f06013cfb..46a643f516 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::set_notification_preference_for_asset_updates_request_payload::SetNotificationPreferenceForAssetUpdatesRequestPayload; use super::set_notification_preference_for_asset_updates_response_payload::SetNotificationPreferenceForAssetUpdatesResponsePayload; @@ -129,18 +127,13 @@ impl SetNotificationPreferenceForAssetUpdatesCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert( - "commandName".to_string(), - "setNotificationPreferenceForAssetUpdates".to_string(), - ); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/setNotificationPreferenceForAssetUpdates") .command_name("setNotificationPreferenceForAssetUpdates") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -183,10 +176,14 @@ impl SetNotificationPreferenceForAssetUpdatesCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(response_payload) = response.payload.response_payload { + } else { Ok(Ok(SetNotificationPreferenceForAssetUpdatesResponse { payload: SetNotificationPreferenceForAssetUpdatesResponsePayload { - response_payload, + response_payload: response.payload.response_payload.ok_or( + SetNotificationPreferenceForAssetUpdatesCommandInvoker::get_err( + "responsePayload", + ), + )?, }, content_type: response.content_type, format_indicator: response.format_indicator, @@ -194,26 +191,6 @@ impl SetNotificationPreferenceForAssetUpdatesCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("setNotificationPreferenceForAssetUpdates".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -228,4 +205,23 @@ impl SetNotificationPreferenceForAssetUpdatesCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("setNotificationPreferenceForAssetUpdates".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_request_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_request_payload.rs index 4dd379bb03..c074d083e1 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_request_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_request_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::set_notification_preference_for_asset_updates_request_schema::SetNotificationPreferenceForAssetUpdatesRequestSchema; +/// The request to set the notification preference for asset updates. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct SetNotificationPreferenceForAssetUpdatesRequestPayload { - /// The Command request argument. + /// The 'notificationPreferenceRequest' Field. #[serde(rename = "notificationPreferenceRequest")] pub notification_preference_request: SetNotificationPreferenceForAssetUpdatesRequestSchema, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_response_payload.rs index 8ce514e39f..49fe91b5f7 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_response_payload.rs @@ -11,9 +11,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// Response containing the result of setting the notification preference for asset updates or error details if the operation failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct SetNotificationPreferenceForAssetUpdatesResponsePayload { - /// The Command response argument. + /// The response payload indicating that the operation was successful. #[serde(rename = "responsePayload")] pub response_payload: String, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_response_schema.rs index 00a7c0eb59..61ebd7fb12 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_asset_updates_response_schema.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::akri_service_error::AkriServiceError; +/// Response containing the result of setting the notification preference for asset updates or error details if the operation failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct SetNotificationPreferenceForAssetUpdatesResponseSchema { /// The response payload indicating that the operation was successful. @@ -20,7 +21,7 @@ pub struct SetNotificationPreferenceForAssetUpdatesResponseSchema { #[builder(default = "None")] pub response_payload: Option, - /// Error details, if setting the notification preference for asset updates failed. + /// Read error for the 'setNotificationPreferenceForAssetUpdates' Action. #[serde(rename = "setNotificationPreferenceForAssetUpdatesError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_command_executor.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_command_executor.rs new file mode 100644 index 0000000000..1b3f5ab592 --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_command_executor.rs @@ -0,0 +1,154 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::rpc_command; + +use super::super::common_types::options::CommandExecutorOptions; +use super::akri_service_error::AkriServiceError; +use super::set_notification_preference_for_device_updates_request_payload::SetNotificationPreferenceForDeviceUpdatesRequestPayload; +use super::set_notification_preference_for_device_updates_response_payload::SetNotificationPreferenceForDeviceUpdatesResponsePayload; +use super::set_notification_preference_for_device_updates_response_schema::SetNotificationPreferenceForDeviceUpdatesResponseSchema; + +pub type SetNotificationPreferenceForDeviceUpdatesRequest = rpc_command::executor::Request< + SetNotificationPreferenceForDeviceUpdatesRequestPayload, + SetNotificationPreferenceForDeviceUpdatesResponseSchema, +>; +pub type SetNotificationPreferenceForDeviceUpdatesResponse = + rpc_command::executor::Response; +pub type SetNotificationPreferenceForDeviceUpdatesResponseBuilderError = + rpc_command::executor::ResponseBuilderError; + +/// Builder for [`SetNotificationPreferenceForDeviceUpdatesResponse`] +#[derive(Default)] +pub struct SetNotificationPreferenceForDeviceUpdatesResponseBuilder { + inner_builder: rpc_command::executor::ResponseBuilder< + SetNotificationPreferenceForDeviceUpdatesResponseSchema, + >, +} + +impl SetNotificationPreferenceForDeviceUpdatesResponseBuilder { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event( + &mut self, + cloud_event: Option, + ) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: SetNotificationPreferenceForDeviceUpdatesResponsePayload, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(SetNotificationPreferenceForDeviceUpdatesResponseSchema { + response_payload: Some(payload.response_payload), + set_notification_preference_for_device_updates_error: None, + })?; + Ok(self) + } + + pub fn error(&mut self, error: AkriServiceError) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(SetNotificationPreferenceForDeviceUpdatesResponseSchema { + response_payload: None, + set_notification_preference_for_device_updates_error: Some(error), + })?; + Ok(self) + } + + /// Builds a new `SetNotificationPreferenceForDeviceUpdatesResponse` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build( + &mut self, + ) -> Result< + SetNotificationPreferenceForDeviceUpdatesResponse, + SetNotificationPreferenceForDeviceUpdatesResponseBuilderError, + > { + self.inner_builder.build() + } +} + +/// Command Executor for `setNotificationPreferenceForDeviceUpdates` +pub struct SetNotificationPreferenceForDeviceUpdatesCommandExecutor( + rpc_command::Executor< + SetNotificationPreferenceForDeviceUpdatesRequestPayload, + SetNotificationPreferenceForDeviceUpdatesResponseSchema, + >, +); + +impl SetNotificationPreferenceForDeviceUpdatesCommandExecutor { + /// Creates a new [`SetNotificationPreferenceForDeviceUpdatesCommandExecutor`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &CommandExecutorOptions, + ) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/setNotificationPreferenceForDeviceUpdates") + .command_name("setNotificationPreferenceForDeviceUpdates") + .is_idempotent(false) + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`SetNotificationPreferenceForDeviceUpdatesRequest`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv( + &mut self, + ) -> Option> { + self.0.recv().await + } + + /// Shutdown the [`SetNotificationPreferenceForDeviceUpdatesCommandExecutor`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_command_invoker.rs index 455edf3327..f221ade39b 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::set_notification_preference_for_device_updates_request_payload::SetNotificationPreferenceForDeviceUpdatesRequestPayload; use super::set_notification_preference_for_device_updates_response_payload::SetNotificationPreferenceForDeviceUpdatesResponsePayload; @@ -129,18 +127,13 @@ impl SetNotificationPreferenceForDeviceUpdatesCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert( - "commandName".to_string(), - "setNotificationPreferenceForDeviceUpdates".to_string(), - ); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/setNotificationPreferenceForDeviceUpdates") .command_name("setNotificationPreferenceForDeviceUpdates") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -185,10 +178,14 @@ impl SetNotificationPreferenceForDeviceUpdatesCommandInvoker { executor_id: response.executor_id, }, )) - } else if let Some(response_payload) = response.payload.response_payload { + } else { Ok(Ok(SetNotificationPreferenceForDeviceUpdatesResponse { payload: SetNotificationPreferenceForDeviceUpdatesResponsePayload { - response_payload, + response_payload: response.payload.response_payload.ok_or( + SetNotificationPreferenceForDeviceUpdatesCommandInvoker::get_err( + "responsePayload", + ), + )?, }, content_type: response.content_type, format_indicator: response.format_indicator, @@ -196,26 +193,6 @@ impl SetNotificationPreferenceForDeviceUpdatesCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("setNotificationPreferenceForDeviceUpdates".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -230,4 +207,23 @@ impl SetNotificationPreferenceForDeviceUpdatesCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("setNotificationPreferenceForDeviceUpdates".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_request_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_request_payload.rs index 5963241384..28f4087000 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_request_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_request_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::notification_preference::NotificationPreference; +/// The request to set the notification preference for device updates. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct SetNotificationPreferenceForDeviceUpdatesRequestPayload { - /// The Command request argument. + /// The 'notificationPreferenceRequest' Field. #[serde(rename = "notificationPreferenceRequest")] pub notification_preference_request: NotificationPreference, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_response_payload.rs index 2ce68801a7..3246127150 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_response_payload.rs @@ -11,9 +11,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// Response containing the result of setting the notification preference for device updates or error details if the operation failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct SetNotificationPreferenceForDeviceUpdatesResponsePayload { - /// The Command response argument. + /// The response payload indicating that the operation was successful. #[serde(rename = "responsePayload")] pub response_payload: String, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_response_schema.rs index 9594edeb3b..407e814b59 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/set_notification_preference_for_device_updates_response_schema.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::akri_service_error::AkriServiceError; +/// Response containing the result of setting the notification preference for device updates or error details if the operation failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct SetNotificationPreferenceForDeviceUpdatesResponseSchema { /// The response payload indicating that the operation was successful. @@ -20,7 +21,7 @@ pub struct SetNotificationPreferenceForDeviceUpdatesResponseSchema { #[builder(default = "None")] pub response_payload: Option, - /// Error details, if setting the notification preference for device updates failed. + /// Read error for the 'setNotificationPreferenceForDeviceUpdates' Action. #[serde(rename = "setNotificationPreferenceForDeviceUpdatesError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/trust_settings_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/trust_settings_schema.rs index e74f3289f9..21a8fb7ff0 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/trust_settings_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/trust_settings_schema.rs @@ -11,6 +11,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// Defines server trust settings for the endpoint. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct TrustSettingsSchema { /// Secret reference to certificates list to trust. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_command_executor.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_command_executor.rs new file mode 100644 index 0000000000..8e11ac9a73 --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_command_executor.rs @@ -0,0 +1,143 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::rpc_command; + +use super::super::common_types::options::CommandExecutorOptions; +use super::akri_service_error::AkriServiceError; +use super::update_asset_status_request_payload::UpdateAssetStatusRequestPayload; +use super::update_asset_status_response_payload::UpdateAssetStatusResponsePayload; +use super::update_asset_status_response_schema::UpdateAssetStatusResponseSchema; + +pub type UpdateAssetStatusRequest = rpc_command::executor::Request< + UpdateAssetStatusRequestPayload, + UpdateAssetStatusResponseSchema, +>; +pub type UpdateAssetStatusResponse = + rpc_command::executor::Response; +pub type UpdateAssetStatusResponseBuilderError = rpc_command::executor::ResponseBuilderError; + +/// Builder for [`UpdateAssetStatusResponse`] +#[derive(Default)] +pub struct UpdateAssetStatusResponseBuilder { + inner_builder: rpc_command::executor::ResponseBuilder, +} + +impl UpdateAssetStatusResponseBuilder { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event( + &mut self, + cloud_event: Option, + ) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: UpdateAssetStatusResponsePayload, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(UpdateAssetStatusResponseSchema { + updated_asset_status: Some(payload.updated_asset_status), + update_asset_status_error: None, + })?; + Ok(self) + } + + pub fn error(&mut self, error: AkriServiceError) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(UpdateAssetStatusResponseSchema { + updated_asset_status: None, + update_asset_status_error: Some(error), + })?; + Ok(self) + } + + /// Builds a new `UpdateAssetStatusResponse` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build( + &mut self, + ) -> Result { + self.inner_builder.build() + } +} + +/// Command Executor for `updateAssetStatus` +pub struct UpdateAssetStatusCommandExecutor( + rpc_command::Executor, +); + +impl UpdateAssetStatusCommandExecutor { + /// Creates a new [`UpdateAssetStatusCommandExecutor`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &CommandExecutorOptions, + ) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/updateAssetStatus") + .command_name("updateAssetStatus") + .is_idempotent(false) + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`UpdateAssetStatusRequest`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv(&mut self) -> Option> { + self.0.recv().await + } + + /// Shutdown the [`UpdateAssetStatusCommandExecutor`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_command_invoker.rs index 59c562ee45..8a7a9e46dd 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::update_asset_status_request_payload::UpdateAssetStatusRequestPayload; use super::update_asset_status_response_payload::UpdateAssetStatusResponsePayload; @@ -118,15 +116,13 @@ impl UpdateAssetStatusCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "updateAssetStatus".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/updateAssetStatus") .command_name("updateAssetStatus") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -162,10 +158,12 @@ impl UpdateAssetStatusCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(updated_asset_status) = response.payload.updated_asset_status { + } else { Ok(Ok(UpdateAssetStatusResponse { payload: UpdateAssetStatusResponsePayload { - updated_asset_status, + updated_asset_status: response.payload.updated_asset_status.ok_or( + UpdateAssetStatusCommandInvoker::get_err("updatedAssetStatus"), + )?, }, content_type: response.content_type, format_indicator: response.format_indicator, @@ -173,26 +171,6 @@ impl UpdateAssetStatusCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("updateAssetStatus".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -207,4 +185,23 @@ impl UpdateAssetStatusCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("updateAssetStatus".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_request_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_request_payload.rs index 636c155372..3a6dcf7fba 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_request_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_request_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::update_asset_status_request_schema::UpdateAssetStatusRequestSchema; +/// The asset status update request. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct UpdateAssetStatusRequestPayload { - /// The Command request argument. + /// The 'assetStatusUpdate' Field. #[serde(rename = "assetStatusUpdate")] pub asset_status_update: UpdateAssetStatusRequestSchema, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_response_payload.rs index c5555a41e9..aa82db77f1 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_response_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::asset_status::AssetStatus; +/// Response containing the updated asset status or error details if the update failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct UpdateAssetStatusResponsePayload { - /// The Command response argument. + /// The updated asset status. #[serde(rename = "updatedAssetStatus")] pub updated_asset_status: AssetStatus, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_response_schema.rs index c741ce9065..cff149f2a8 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_asset_status_response_schema.rs @@ -13,9 +13,10 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::akri_service_error::AkriServiceError; use super::asset_status::AssetStatus; +/// Response containing the updated asset status or error details if the update failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct UpdateAssetStatusResponseSchema { - /// Error details, if the asset status update failed. + /// Read error for the 'updateAssetStatus' Action. #[serde(rename = "updateAssetStatusError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_command_executor.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_command_executor.rs new file mode 100644 index 0000000000..af5993b853 --- /dev/null +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_command_executor.rs @@ -0,0 +1,143 @@ +/* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ + +use std::collections::HashMap; + +use azure_iot_operations_mqtt::session::SessionManagedClient; +use azure_iot_operations_protocol::application::ApplicationContext; +use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; +use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; +use azure_iot_operations_protocol::rpc_command; + +use super::super::common_types::options::CommandExecutorOptions; +use super::akri_service_error::AkriServiceError; +use super::update_device_status_request_payload::UpdateDeviceStatusRequestPayload; +use super::update_device_status_response_payload::UpdateDeviceStatusResponsePayload; +use super::update_device_status_response_schema::UpdateDeviceStatusResponseSchema; + +pub type UpdateDeviceStatusRequest = rpc_command::executor::Request< + UpdateDeviceStatusRequestPayload, + UpdateDeviceStatusResponseSchema, +>; +pub type UpdateDeviceStatusResponse = + rpc_command::executor::Response; +pub type UpdateDeviceStatusResponseBuilderError = rpc_command::executor::ResponseBuilderError; + +/// Builder for [`UpdateDeviceStatusResponse`] +#[derive(Default)] +pub struct UpdateDeviceStatusResponseBuilder { + inner_builder: rpc_command::executor::ResponseBuilder, +} + +impl UpdateDeviceStatusResponseBuilder { + /// Custom user data to set on the response + pub fn custom_user_data(&mut self, custom_user_data: Vec<(String, String)>) -> &mut Self { + self.inner_builder.custom_user_data(custom_user_data); + self + } + + /// Cloud event for the response + pub fn cloud_event( + &mut self, + cloud_event: Option, + ) -> &mut Self { + self.inner_builder.cloud_event(cloud_event); + self + } + + /// Payload of the response + /// + /// # Errors + /// If the payload cannot be serialized + pub fn payload( + &mut self, + payload: UpdateDeviceStatusResponsePayload, + ) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(UpdateDeviceStatusResponseSchema { + updated_device_status: Some(payload.updated_device_status), + update_device_status_error: None, + })?; + Ok(self) + } + + pub fn error(&mut self, error: AkriServiceError) -> Result<&mut Self, AIOProtocolError> { + self.inner_builder + .payload(UpdateDeviceStatusResponseSchema { + updated_device_status: None, + update_device_status_error: Some(error), + })?; + Ok(self) + } + + /// Builds a new `UpdateDeviceStatusResponse` + /// + /// # Errors + /// If a required field has not been initialized + #[allow(clippy::missing_panics_doc)] // The panic is not possible + pub fn build( + &mut self, + ) -> Result { + self.inner_builder.build() + } +} + +/// Command Executor for `updateDeviceStatus` +pub struct UpdateDeviceStatusCommandExecutor( + rpc_command::Executor, +); + +impl UpdateDeviceStatusCommandExecutor { + /// Creates a new [`UpdateDeviceStatusCommandExecutor`] + /// + /// # Panics + /// If the DTDL that generated this code was invalid + pub fn new( + application_context: ApplicationContext, + client: SessionManagedClient, + options: &CommandExecutorOptions, + ) -> Self { + let mut executor_options_builder = rpc_command::executor::OptionsBuilder::default(); + if let Some(topic_namespace) = &options.topic_namespace { + executor_options_builder.topic_namespace(topic_namespace.clone()); + } + + let mut topic_token_map: HashMap = options + .topic_token_map + .clone() + .into_iter() + .map(|(k, v)| (format!("ex:{k}"), v)) + .collect(); + + topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); + + let executor_options = executor_options_builder + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/updateDeviceStatus") + .command_name("updateDeviceStatus") + .is_idempotent(false) + .topic_token_map(topic_token_map) + .build() + .expect("DTDL schema generated invalid arguments"); + + Self( + rpc_command::Executor::new(application_context, client, executor_options) + .expect("DTDL schema generated invalid arguments"), + ) + } + + /// Receive the next [`UpdateDeviceStatusRequest`] or [`None`] if there will be no more requests + /// + /// # Errors + /// [`AIOProtocolError`] if there is a failure receiving a request + pub async fn recv(&mut self) -> Option> { + self.0.recv().await + } + + /// Shutdown the [`UpdateDeviceStatusCommandExecutor`]. Unsubscribes from the request topic. + /// + /// Returns Ok(()) on success, otherwise returns [`AIOProtocolError`]. + /// # Errors + /// [`AIOProtocolError`] of kind [`ClientError`](azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolErrorKind::ClientError) if the unsubscribe fails or if the unsuback reason code doesn't indicate success. + pub async fn shutdown(&mut self) -> Result<(), AIOProtocolError> { + self.0.shutdown().await + } +} diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_command_invoker.rs index 45f973e8f8..af0e946f60 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::update_device_status_request_payload::UpdateDeviceStatusRequestPayload; use super::update_device_status_response_payload::UpdateDeviceStatusResponsePayload; @@ -119,15 +117,13 @@ impl UpdateDeviceStatusCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "updateDeviceStatus".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/connector/resources/{ex:connectorClientId}/{ex:deviceName}/{ex:inboundEndpointName}/updateDeviceStatus") .command_name("updateDeviceStatus") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -164,10 +160,12 @@ impl UpdateDeviceStatusCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(updated_device_status) = response.payload.updated_device_status { + } else { Ok(Ok(UpdateDeviceStatusResponse { payload: UpdateDeviceStatusResponsePayload { - updated_device_status, + updated_device_status: response.payload.updated_device_status.ok_or( + UpdateDeviceStatusCommandInvoker::get_err("updatedDeviceStatus"), + )?, }, content_type: response.content_type, format_indicator: response.format_indicator, @@ -175,26 +173,6 @@ impl UpdateDeviceStatusCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("updateDeviceStatus".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -209,4 +187,23 @@ impl UpdateDeviceStatusCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("updateDeviceStatus".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_request_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_request_payload.rs index 0c5d5c32fa..b9926c2587 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_request_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_request_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::device_status::DeviceStatus; +/// The device status to update. Fields omitted in the request will be removed. The specified inbound endpoint status will be added or updated in the inbound endpoints map. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct UpdateDeviceStatusRequestPayload { - /// The Command request argument. + /// The 'deviceStatusUpdate' Field. #[serde(rename = "deviceStatusUpdate")] pub device_status_update: DeviceStatus, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_response_payload.rs index 08e623d83d..06c97ec704 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_response_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::device_status::DeviceStatus; +/// Response containing the updated device status or error details if the update failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct UpdateDeviceStatusResponsePayload { - /// The Command response argument. + /// The updated device status containing the specific inbound endpoint status as specified by the request. #[serde(rename = "updatedDeviceStatus")] pub updated_device_status: DeviceStatus, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_response_schema.rs index ade637e55c..01e827c7e8 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/update_device_status_response_schema.rs @@ -13,6 +13,7 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::akri_service_error::AkriServiceError; use super::device_status::DeviceStatus; +/// Response containing the updated device status or error details if the update failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct UpdateDeviceStatusResponseSchema { /// The updated device status containing the specific inbound endpoint status as specified by the request. @@ -21,7 +22,7 @@ pub struct UpdateDeviceStatusResponseSchema { #[builder(default = "None")] pub updated_device_status: Option, - /// Error details, if the device status update failed. + /// Read error for the 'updateDeviceStatus' Action. #[serde(rename = "updateDeviceStatusError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/username_password_credentials_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/username_password_credentials_schema.rs index efc7d0da6f..138bcefb1e 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/username_password_credentials_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/username_password_credentials_schema.rs @@ -11,6 +11,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// The credentials for authentication mode UsernamePassword. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct UsernamePasswordCredentialsSchema { /// The name of the secret containing the password. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/x509credentials_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/x509credentials_schema.rs index 5d287fde69..9c0a288588 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/x509credentials_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/adr_base_gen/adr_base_service/x509credentials_schema.rs @@ -11,6 +11,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// The x509 certificate for authentication mode Certificate. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct X509credentialsSchema { /// The name of the secret containing the certificate and private key (e.g. stored as .der/.pem or .der/.pfx). diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service.rs index 6979b72e07..c2ba7b63ea 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service.rs @@ -23,10 +23,6 @@ pub use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolEr pub use super::common_types::options::{CommandInvokerOptions, TelemetryReceiverOptions}; -pub const MODEL_ID: &str = "dtmi:com:microsoft:akri:DeviceDiscoveryService;1"; -pub const REQUEST_TOPIC_PATTERN: &str = - "akri/discovery/resources/{ex:discoveryClientId}/{ex:inboundEndpointType}/{commandName}"; - pub mod client { pub use super::akri_service_error::*; pub use super::akri_service_error_error::*; diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/code_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/code_schema.rs index 36143884f6..26ce660204 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/code_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/code_schema.rs @@ -2,16 +2,12 @@ use serde::{Deserialize, Serialize}; +/// The error code that identifies the error. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum CodeSchema { - #[serde(rename = "BadRequest")] BadRequest, - #[serde(rename = "InternalError")] InternalError, - #[serde(rename = "KubeError")] KubeError, - #[serde(rename = "SerializationError")] SerializationError, - #[serde(rename = "Unauthorized")] Unauthorized, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_command_invoker.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_command_invoker.rs index 23b72265a5..56ffe6e2d9 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::akri_service_error::AkriServiceError; use super::create_or_update_discovered_device_request_payload::CreateOrUpdateDiscoveredDeviceRequestPayload; use super::create_or_update_discovered_device_response_payload::CreateOrUpdateDiscoveredDeviceResponsePayload; @@ -128,18 +126,13 @@ impl CreateOrUpdateDiscoveredDeviceCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert( - "commandName".to_string(), - "createOrUpdateDiscoveredDevice".to_string(), - ); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("akri/discovery/resources/{ex:discoveryClientId}/{ex:inboundEndpointType}/createOrUpdateDiscoveredDevice") .command_name("createOrUpdateDiscoveredDevice") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -178,12 +171,15 @@ impl CreateOrUpdateDiscoveredDeviceCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(discovered_device_response) = - response.payload.discovered_device_response - { + } else { Ok(Ok(CreateOrUpdateDiscoveredDeviceResponse { payload: CreateOrUpdateDiscoveredDeviceResponsePayload { - discovered_device_response, + discovered_device_response: response + .payload + .discovered_device_response + .ok_or(CreateOrUpdateDiscoveredDeviceCommandInvoker::get_err( + "discoveredDeviceResponse", + ))?, }, content_type: response.content_type, format_indicator: response.format_indicator, @@ -191,26 +187,6 @@ impl CreateOrUpdateDiscoveredDeviceCommandInvoker { timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("createOrUpdateDiscoveredDevice".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -225,4 +201,23 @@ impl CreateOrUpdateDiscoveredDeviceCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("createOrUpdateDiscoveredDevice".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_request_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_request_payload.rs index 2dda6e69d0..095cea4639 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_request_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_request_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::create_or_update_discovered_device_request_schema::CreateOrUpdateDiscoveredDeviceRequestSchema; +/// The request to create or update a discovered device. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct CreateOrUpdateDiscoveredDeviceRequestPayload { - /// The Command request argument. + /// The 'discoveredDeviceRequest' Field. #[serde(rename = "discoveredDeviceRequest")] pub discovered_device_request: CreateOrUpdateDiscoveredDeviceRequestSchema, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_response_payload.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_response_payload.rs index f67b7843d7..e8751a880a 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_response_payload.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_response_payload.rs @@ -12,9 +12,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::discovered_device_response_schema::DiscoveredDeviceResponseSchema; +/// Response containing the discovered device response or error details if the operation failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct CreateOrUpdateDiscoveredDeviceResponsePayload { - /// The Command response argument. + /// The discovered device response. #[serde(rename = "discoveredDeviceResponse")] pub discovered_device_response: DiscoveredDeviceResponseSchema, } diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_response_schema.rs index 704a514448..87eee2e430 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/create_or_update_discovered_device_response_schema.rs @@ -13,9 +13,10 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::akri_service_error::AkriServiceError; use super::discovered_device_response_schema::DiscoveredDeviceResponseSchema; +/// Response containing the discovered device response or error details if the operation failed. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct CreateOrUpdateDiscoveredDeviceResponseSchema { - /// Error details, if the discovered device creation or update failed. + /// Read error for the 'createOrUpdateDiscoveredDevice' Action. #[serde(rename = "createOrUpdateDiscoveredDeviceError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device.rs index 2bdea2665d..d2fdf5d393 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::discovered_device_endpoints::DiscoveredDeviceEndpoints; +/// The discovered device resource to create or update. Fields omitted in the request will be removed. The specified inbound endpoint will be added or updated in the inbound endpoints map. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DiscoveredDevice { /// A set of key-value pairs that contain custom attributes set by the customer. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_endpoints.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_endpoints.rs index 2f0b831817..3fd0c5e3cd 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_endpoints.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_endpoints.rs @@ -13,6 +13,7 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::discovered_device_inbound_endpoint_schema::DiscoveredDeviceInboundEndpointSchema; use super::discovered_device_outbound_endpoints_schema::DiscoveredDeviceOutboundEndpointsSchema; +/// Connection endpoint URL a device can use to connect to a service. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DiscoveredDeviceEndpoints { /// Set of endpoints to connect to the device. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_outbound_endpoints_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_outbound_endpoints_schema.rs index 4209066a8a..7cc7f4b338 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_outbound_endpoints_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_outbound_endpoints_schema.rs @@ -12,6 +12,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::device_outbound_endpoint::DeviceOutboundEndpoint; +/// Property bag contains the device's outbound endpoints #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DiscoveredDeviceOutboundEndpointsSchema { /// Endpoints the device can connect to. diff --git a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_response_schema.rs b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_response_schema.rs index 31d70dd8db..ab60151d2a 100644 --- a/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_response_schema.rs +++ b/rust/azure_iot_operations_services/src/azure_device_registry/device_discovery_gen/device_discovery_service/discovered_device_response_schema.rs @@ -11,6 +11,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// The discovered device response. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct DiscoveredDeviceResponseSchema { /// The unique identifier for the discovered device. diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry.rs index 62a4a8673c..78a1eb4a54 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry.rs @@ -28,10 +28,6 @@ pub use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolEr pub use super::common_types::options::{CommandInvokerOptions, TelemetryReceiverOptions}; -pub const MODEL_ID: &str = "dtmi:ms:adr:SchemaRegistry;2"; -pub const REQUEST_TOPIC_PATTERN: &str = "adr/{modelId}/{commandName}"; -pub const COMMAND_SERVICE_GROUP_ID: &str = "schema-registry-edge"; - pub mod client { pub use super::format::*; pub use super::get_command_invoker::*; diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/format.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/format.rs index ce3a098c9d..2b183454a4 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/format.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/format.rs @@ -1,12 +1,9 @@ /* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ -use serde::{Deserialize, Serialize}; +// Supported schema formats -/// Supported schema formats -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum Format { - #[serde(rename = "Delta/1.0")] - Delta1, - #[serde(rename = "JsonSchema/draft-07")] - JsonSchemaDraft07, -} +/// JSON Schema Draft-07 format +pub const JSON_SCHEMA_DRAFT07: &str = "JsonSchema/draft-07"; + +/// Delta-Parquet format +pub const DELTA1: &str = "Delta/1.0"; diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_command_invoker.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_command_invoker.rs index 480ccae9cd..19f05e8b84 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::get_request_schema::GetRequestSchema; use super::get_response_payload::GetResponsePayload; use super::get_response_schema::GetResponseSchema; @@ -110,15 +108,13 @@ impl GetCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "get".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("adr/dtmi:ms:adr:SchemaRegistry;2/get") .command_name("get") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -143,44 +139,29 @@ impl GetCommandInvoker { let response = self.0.invoke(request).await; match response { Ok(response) => { - if let Some(error) = response.payload.error { + if let Some(get_error) = response.payload.get_error { Ok(Err(GetResponseError { - payload: error, + payload: get_error, content_type: response.content_type, format_indicator: response.format_indicator, custom_user_data: response.custom_user_data, timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(schema) = response.payload.schema { + } else { Ok(Ok(GetResponse { - payload: GetResponsePayload { schema }, + payload: GetResponsePayload { + schema: response + .payload + .schema + .ok_or(GetCommandInvoker::get_err("schema"))?, + }, content_type: response.content_type, format_indicator: response.format_indicator, custom_user_data: response.custom_user_data, timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("get".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -195,4 +176,23 @@ impl GetCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("get".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_request_schema.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_request_schema.rs index 5c571dc816..92c8bac0c5 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_request_schema.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_request_schema.rs @@ -11,6 +11,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// GET Schema request object #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetRequestSchema { /// Schema name. diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_response_payload.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_response_payload.rs index 80ef017ed7..ccdec915d0 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_response_payload.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_response_payload.rs @@ -12,8 +12,9 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::schema::Schema; +/// GET Schema response object #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetResponsePayload { - /// The Command response argument. + /// The requested schema object. pub schema: Schema, } diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_response_schema.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_response_schema.rs index 72b61b6fa0..0a7d45962f 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_response_schema.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/get_response_schema.rs @@ -13,12 +13,14 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::schema::Schema; use super::schema_registry_error::SchemaRegistryError; +/// GET Schema response object #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct GetResponseSchema { - /// Error object in case of failure. + /// Read error for the 'get' Action. + #[serde(rename = "getError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] - pub error: Option, + pub get_error: Option, /// The requested schema object. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_command_invoker.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_command_invoker.rs index 70eac4c40e..ba6feaff5c 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_command_invoker.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_command_invoker.rs @@ -13,8 +13,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::put_request_schema::PutRequestSchema; use super::put_response_payload::PutResponsePayload; use super::put_response_schema::PutResponseSchema; @@ -110,15 +108,13 @@ impl PutCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "put".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("adr/dtmi:ms:adr:SchemaRegistry;2/put") .command_name("put") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) @@ -143,44 +139,29 @@ impl PutCommandInvoker { let response = self.0.invoke(request).await; match response { Ok(response) => { - if let Some(error) = response.payload.error { + if let Some(put_error) = response.payload.put_error { Ok(Err(PutResponseError { - payload: error, + payload: put_error, content_type: response.content_type, format_indicator: response.format_indicator, custom_user_data: response.custom_user_data, timestamp: response.timestamp, executor_id: response.executor_id, })) - } else if let Some(schema) = response.payload.schema { + } else { Ok(Ok(PutResponse { - payload: PutResponsePayload { schema }, + payload: PutResponsePayload { + schema: response + .payload + .schema + .ok_or(PutCommandInvoker::get_err("schema"))?, + }, content_type: response.content_type, format_indicator: response.format_indicator, custom_user_data: response.custom_user_data, timestamp: response.timestamp, executor_id: response.executor_id, })) - } else { - Err(AIOProtocolError { - message: Some( - "Command response has neither normal nor error payload content" - .to_string(), - ), - kind: AIOProtocolErrorKind::PayloadInvalid, - is_shallow: false, - is_remote: true, - nested_error: None, - header_name: None, - header_value: None, - timeout_name: None, - timeout_value: None, - property_name: None, - property_value: None, - command_name: Some("put".to_string()), - protocol_version: None, - supported_protocol_major_versions: None, - }) } } Err(err) => Err(err), @@ -195,4 +176,23 @@ impl PutCommandInvoker { pub async fn shutdown(&self) -> Result<(), AIOProtocolError> { self.0.shutdown().await } + + fn get_err(field_name: &str) -> AIOProtocolError { + AIOProtocolError { + message: Some(format!("Command response missing field {field_name}")), + kind: AIOProtocolErrorKind::PayloadInvalid, + is_shallow: false, + is_remote: false, + nested_error: None, + header_name: None, + header_value: None, + timeout_name: None, + timeout_value: None, + property_name: None, + property_value: None, + command_name: Some("put".to_string()), + protocol_version: None, + supported_protocol_major_versions: None, + } + } } diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_request_schema.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_request_schema.rs index 049a2a1e6e..0cc9c66c6e 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_request_schema.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_request_schema.rs @@ -10,9 +10,9 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; -use super::format::Format; use super::schema_type::SchemaType; +/// PUT Schema request object #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct PutRequestSchema { /// Human-readable description of the schema. @@ -27,7 +27,7 @@ pub struct PutRequestSchema { pub display_name: Option, /// Format of the schema. - pub format: Format, + pub format: String, /// Content stored in the schema. #[serde(rename = "schemaContent")] diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_response_payload.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_response_payload.rs index 83795d9d9a..9eff71036f 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_response_payload.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_response_payload.rs @@ -12,8 +12,9 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; use super::schema::Schema; +/// PUT Schema response object #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct PutResponsePayload { - /// The Command response argument. + /// Schema object that was created. pub schema: Schema, } diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_response_schema.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_response_schema.rs index 045e36ddea..a6a013a0b2 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_response_schema.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/put_response_schema.rs @@ -13,12 +13,14 @@ use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, use super::schema::Schema; use super::schema_registry_error::SchemaRegistryError; +/// PUT Schema response object #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct PutResponseSchema { - /// Error object in case of failure. + /// Read error for the 'put' Action. + #[serde(rename = "putError")] #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] - pub error: Option, + pub put_error: Option, /// Schema object that was created. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema.rs index 5dd1c2e466..a9e572632a 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema.rs @@ -10,10 +10,9 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; -use super::format::Format; use super::schema_type::SchemaType; -/// Schema object +/// Schema object that was created. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct Schema { /// Human-readable description of the schema. @@ -28,7 +27,7 @@ pub struct Schema { pub display_name: Option, /// Format of the schema. - pub format: Format, + pub format: String, /// Hash of the schema content. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error.rs index 012dacf411..2884e39090 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error.rs @@ -10,15 +10,13 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; -use super::schema_registry_error_code::SchemaRegistryErrorCode; use super::schema_registry_error_details::SchemaRegistryErrorDetails; -use super::schema_registry_error_target::SchemaRegistryErrorTarget; /// Error object for schema operations #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct SchemaRegistryError { /// Error code for classification of errors (ex: '400', '404', '500', etc.). - pub code: SchemaRegistryErrorCode, + pub code: i32, /// Additional details about the error, if available. #[serde(skip_serializing_if = "Option::is_none")] @@ -37,5 +35,5 @@ pub struct SchemaRegistryError { /// Target of the error, if applicable (e.g., 'schemaType'). #[serde(skip_serializing_if = "Option::is_none")] #[builder(default = "None")] - pub target: Option, + pub target: Option, } diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_code.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_code.rs index 4dcc18738a..7e686ed3b0 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_code.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_code.rs @@ -1,11 +1,10 @@ /* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ -use serde_repr::{Deserialize_repr, Serialize_repr}; +/// The request is invalid or malformed. +pub const BAD_REQUEST: i32 = 400; -#[derive(Serialize_repr, Deserialize_repr, Debug, Clone)] -#[repr(i32)] -pub enum SchemaRegistryErrorCode { - BadRequest = 400, - InternalError = 500, - NotFound = 404, -} +/// The target resource was not found. +pub const NOT_FOUND: i32 = 404; + +/// An internal server error occurred. +pub const INTERNAL_ERROR: i32 = 500; diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_details.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_details.rs index 82fd3531f6..72b9846f38 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_details.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_details.rs @@ -11,7 +11,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; -/// Additional details about an error +/// Additional details about the error, if available. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct SchemaRegistryErrorDetails { /// Multi-part error code for classification and root causing of errors (e.g., '400.200'). diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_target.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_target.rs index 4bb2c2f0af..dac974b79f 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_target.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_registry_error_target.rs @@ -1,29 +1,34 @@ /* Code generated by Azure.Iot.Operations.ProtocolCompilerLib v0.10.0.0; DO NOT EDIT. */ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum SchemaRegistryErrorTarget { - #[serde(rename = "Description")] - DescriptionProperty, - #[serde(rename = "DisplayName")] - DisplayNameProperty, - #[serde(rename = "Format")] - FormatProperty, - #[serde(rename = "Name")] - NameProperty, - #[serde(rename = "SchemaArmResource")] - SchemaArmResource, - #[serde(rename = "SchemaContent")] - SchemaContentProperty, - #[serde(rename = "SchemaRegistryResource")] - SchemaRegistryArmResource, - #[serde(rename = "SchemaType")] - SchemaTypeProperty, - #[serde(rename = "SchemaVersionArmResource")] - SchemaVersionArmResource, - #[serde(rename = "Tags")] - TagsProperty, - #[serde(rename = "Version")] - VersionProperty, -} +/// Indicates the error is related to the schema type. +pub const SCHEMA_TYPE_PROPERTY: &str = "SchemaType"; + +/// Indicates the error is related to the schema format. +pub const FORMAT_PROPERTY: &str = "Format"; + +/// Indicates the error is related to the schema version. +pub const VERSION_PROPERTY: &str = "Version"; + +/// Indicates the error is related to the schema name. +pub const NAME_PROPERTY: &str = "Name"; + +/// Indicates the error is related to schema tags. +pub const TAGS_PROPERTY: &str = "Tags"; + +/// Indicates the error is related to the schema description. +pub const DESCRIPTION_PROPERTY: &str = "Description"; + +/// Indicates the error is related to the schema display name. +pub const DISPLAY_NAME_PROPERTY: &str = "DisplayName"; + +/// Indicates the error is related to the schema content. +pub const SCHEMA_CONTENT_PROPERTY: &str = "SchemaContent"; + +/// Indicates the error is related to the schema ARM resource. +pub const SCHEMA_ARM_RESOURCE: &str = "SchemaArmResource"; + +/// Indicates the error is related to the schema version ARM resource. +pub const SCHEMA_VERSION_ARM_RESOURCE: &str = "SchemaVersionArmResource"; + +/// Indicates the error is related to the schema registry ARM resource. +pub const SCHEMA_REGISTRY_ARM_RESOURCE: &str = "SchemaRegistryResource"; diff --git a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_type.rs b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_type.rs index 11ca6b4baf..f4b2d6d225 100644 --- a/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_type.rs +++ b/rust/azure_iot_operations_services/src/schema_registry/schemaregistry_gen/schema_registry/schema_type.rs @@ -2,9 +2,8 @@ use serde::{Deserialize, Serialize}; -/// Supported schema types +/// Type of the schema. #[derive(Serialize, Deserialize, Debug, Clone)] pub enum SchemaType { - #[serde(rename = "MessageSchema")] MessageSchema, } diff --git a/rust/sample_applications/counter/envoy/src/counter.rs b/rust/sample_applications/counter/envoy/src/counter.rs index 5c8504cacf..098125cba4 100644 --- a/rust/sample_applications/counter/envoy/src/counter.rs +++ b/rust/sample_applications/counter/envoy/src/counter.rs @@ -13,19 +13,15 @@ mod read_counter_response_payload_serialization; mod reset_command_executor; mod reset_command_invoker; mod telemetry_collection; +mod telemetry_collection_receiver; +mod telemetry_collection_sender; mod telemetry_collection_serialization; -mod telemetry_receiver; -mod telemetry_sender; pub use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; pub use super::common_types::options::{CommandExecutorOptions, TelemetrySenderOptions}; pub use super::common_types::options::{CommandInvokerOptions, TelemetryReceiverOptions}; -pub const MODEL_ID: &str = "dtmi:com:example:Counter;1"; -pub const REQUEST_TOPIC_PATTERN: &str = "rpc/command-samples/{executorId}/{commandName}"; -pub const TELEMETRY_TOPIC_PATTERN: &str = "telemetry/telemetry-samples/counterValue"; - pub mod client { pub use super::increment_command_invoker::*; pub use super::increment_request_payload::*; @@ -34,7 +30,7 @@ pub mod client { pub use super::read_counter_response_payload::*; pub use super::reset_command_invoker::*; pub use super::telemetry_collection::*; - pub use super::telemetry_receiver::*; + pub use super::telemetry_collection_receiver::*; } pub mod service { @@ -45,5 +41,5 @@ pub mod service { pub use super::read_counter_response_payload::*; pub use super::reset_command_executor::*; pub use super::telemetry_collection::*; - pub use super::telemetry_sender::*; + pub use super::telemetry_collection_sender::*; } diff --git a/rust/sample_applications/counter/envoy/src/counter/increment_command_executor.rs b/rust/sample_applications/counter/envoy/src/counter/increment_command_executor.rs index 45084a658c..2197732702 100644 --- a/rust/sample_applications/counter/envoy/src/counter/increment_command_executor.rs +++ b/rust/sample_applications/counter/envoy/src/counter/increment_command_executor.rs @@ -9,8 +9,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandExecutorOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::increment_request_payload::IncrementRequestPayload; use super::increment_response_payload::IncrementResponsePayload; @@ -90,12 +88,10 @@ impl IncrementCommandExecutor { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); - topic_token_map.insert("commandName".to_string(), "increment".to_string()); let executor_options = executor_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("rpc/command-samples/{executorId}/increment") .command_name("increment") .is_idempotent(false) .topic_token_map(topic_token_map) diff --git a/rust/sample_applications/counter/envoy/src/counter/increment_command_invoker.rs b/rust/sample_applications/counter/envoy/src/counter/increment_command_invoker.rs index b06078cb9f..425aaf0be8 100644 --- a/rust/sample_applications/counter/envoy/src/counter/increment_command_invoker.rs +++ b/rust/sample_applications/counter/envoy/src/counter/increment_command_invoker.rs @@ -10,8 +10,6 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::rpc_command; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::increment_request_payload::IncrementRequestPayload; use super::increment_response_payload::IncrementResponsePayload; @@ -124,15 +122,13 @@ impl IncrementCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "increment".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("rpc/command-samples/{executorId}/increment") .command_name("increment") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) diff --git a/rust/sample_applications/counter/envoy/src/counter/increment_request_payload.rs b/rust/sample_applications/counter/envoy/src/counter/increment_request_payload.rs index a8bc05a81a..eb12e47870 100644 --- a/rust/sample_applications/counter/envoy/src/counter/increment_request_payload.rs +++ b/rust/sample_applications/counter/envoy/src/counter/increment_request_payload.rs @@ -11,9 +11,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// Input arguments for action 'increment' #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct IncrementRequestPayload { - /// The Command request argument. + /// The 'incrementValue' Field. #[serde(rename = "incrementValue")] pub increment_value: i32, } diff --git a/rust/sample_applications/counter/envoy/src/counter/increment_response_payload.rs b/rust/sample_applications/counter/envoy/src/counter/increment_response_payload.rs index 06543a1ebb..68a59281ae 100644 --- a/rust/sample_applications/counter/envoy/src/counter/increment_response_payload.rs +++ b/rust/sample_applications/counter/envoy/src/counter/increment_response_payload.rs @@ -11,9 +11,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// Output arguments for action 'increment' #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct IncrementResponsePayload { - /// The Command response argument. + /// The 'CounterResponse' Field. #[serde(rename = "CounterResponse")] pub counter_response: i32, } diff --git a/rust/sample_applications/counter/envoy/src/counter/read_counter_command_executor.rs b/rust/sample_applications/counter/envoy/src/counter/read_counter_command_executor.rs index 7b5b719545..5536d62f61 100644 --- a/rust/sample_applications/counter/envoy/src/counter/read_counter_command_executor.rs +++ b/rust/sample_applications/counter/envoy/src/counter/read_counter_command_executor.rs @@ -10,8 +10,6 @@ use azure_iot_operations_protocol::rpc_command; use super::super::common_types::empty_json::EmptyJson; use super::super::common_types::options::CommandExecutorOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::read_counter_response_payload::ReadCounterResponsePayload; pub type ReadCounterRequest = rpc_command::executor::Request; @@ -87,12 +85,10 @@ impl ReadCounterCommandExecutor { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); - topic_token_map.insert("commandName".to_string(), "readCounter".to_string()); let executor_options = executor_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("rpc/command-samples/{executorId}/readCounter") .command_name("readCounter") .is_idempotent(false) .topic_token_map(topic_token_map) diff --git a/rust/sample_applications/counter/envoy/src/counter/read_counter_command_invoker.rs b/rust/sample_applications/counter/envoy/src/counter/read_counter_command_invoker.rs index ece411980f..6d76e925e1 100644 --- a/rust/sample_applications/counter/envoy/src/counter/read_counter_command_invoker.rs +++ b/rust/sample_applications/counter/envoy/src/counter/read_counter_command_invoker.rs @@ -10,8 +10,6 @@ use azure_iot_operations_protocol::rpc_command; use super::super::common_types::empty_json::EmptyJson; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; use super::read_counter_response_payload::ReadCounterResponsePayload; pub type ReadCounterRequest = rpc_command::invoker::Request; @@ -111,15 +109,13 @@ impl ReadCounterCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "readCounter".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("rpc/command-samples/{executorId}/readCounter") .command_name("readCounter") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) diff --git a/rust/sample_applications/counter/envoy/src/counter/read_counter_response_payload.rs b/rust/sample_applications/counter/envoy/src/counter/read_counter_response_payload.rs index e390e1881b..51b39f3214 100644 --- a/rust/sample_applications/counter/envoy/src/counter/read_counter_response_payload.rs +++ b/rust/sample_applications/counter/envoy/src/counter/read_counter_response_payload.rs @@ -11,9 +11,10 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// Output arguments for action 'readCounter' #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct ReadCounterResponsePayload { - /// The Command response argument. + /// The 'CounterResponse' Field. #[serde(rename = "CounterResponse")] pub counter_response: i32, } diff --git a/rust/sample_applications/counter/envoy/src/counter/reset_command_executor.rs b/rust/sample_applications/counter/envoy/src/counter/reset_command_executor.rs index de3aeafe1c..9a7590dbe2 100644 --- a/rust/sample_applications/counter/envoy/src/counter/reset_command_executor.rs +++ b/rust/sample_applications/counter/envoy/src/counter/reset_command_executor.rs @@ -9,8 +9,6 @@ use azure_iot_operations_protocol::rpc_command; use super::super::common_types::empty_json::EmptyJson; use super::super::common_types::options::CommandExecutorOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; pub type ResetRequest = rpc_command::executor::Request; pub type ResetResponse = rpc_command::executor::Response; @@ -75,12 +73,10 @@ impl ResetCommandExecutor { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert("executorId".to_string(), client.client_id().to_string()); - topic_token_map.insert("commandName".to_string(), "reset".to_string()); let executor_options = executor_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("rpc/command-samples/{executorId}/reset") .command_name("reset") .is_idempotent(false) .topic_token_map(topic_token_map) diff --git a/rust/sample_applications/counter/envoy/src/counter/reset_command_invoker.rs b/rust/sample_applications/counter/envoy/src/counter/reset_command_invoker.rs index 75174314e5..fe83fdbe9b 100644 --- a/rust/sample_applications/counter/envoy/src/counter/reset_command_invoker.rs +++ b/rust/sample_applications/counter/envoy/src/counter/reset_command_invoker.rs @@ -10,8 +10,6 @@ use azure_iot_operations_protocol::rpc_command; use super::super::common_types::empty_json::EmptyJson; use super::super::common_types::options::CommandInvokerOptions; -use super::MODEL_ID; -use super::REQUEST_TOPIC_PATTERN; pub type ResetRequest = rpc_command::invoker::Request; pub type ResetResponse = rpc_command::invoker::Response; @@ -108,15 +106,13 @@ impl ResetCommandInvoker { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert( "invokerClientId".to_string(), client.client_id().to_string(), ); - topic_token_map.insert("commandName".to_string(), "reset".to_string()); let invoker_options = invoker_options_builder - .request_topic_pattern(REQUEST_TOPIC_PATTERN) + .request_topic_pattern("rpc/command-samples/{executorId}/reset") .command_name("reset") .topic_token_map(topic_token_map) .response_topic_prefix(options.response_topic_prefix.clone()) diff --git a/rust/sample_applications/counter/envoy/src/counter/telemetry_collection.rs b/rust/sample_applications/counter/envoy/src/counter/telemetry_collection.rs index 714a549572..29b21fc35c 100644 --- a/rust/sample_applications/counter/envoy/src/counter/telemetry_collection.rs +++ b/rust/sample_applications/counter/envoy/src/counter/telemetry_collection.rs @@ -11,6 +11,7 @@ use uuid::Uuid; use super::super::common_types::{b64::Bytes, date_only::Date, decimal::Decimal, time_only::Time}; +/// Data values of Events. #[derive(Serialize, Deserialize, Debug, Clone, Builder)] pub struct TelemetryCollection { /// The current value of the counter. diff --git a/rust/sample_applications/counter/envoy/src/counter/telemetry_receiver.rs b/rust/sample_applications/counter/envoy/src/counter/telemetry_collection_receiver.rs similarity index 75% rename from rust/sample_applications/counter/envoy/src/counter/telemetry_receiver.rs rename to rust/sample_applications/counter/envoy/src/counter/telemetry_collection_receiver.rs index 3405f306cf..fccb076cf6 100644 --- a/rust/sample_applications/counter/envoy/src/counter/telemetry_receiver.rs +++ b/rust/sample_applications/counter/envoy/src/counter/telemetry_collection_receiver.rs @@ -8,17 +8,15 @@ use azure_iot_operations_protocol::common::aio_protocol_error::AIOProtocolError; use azure_iot_operations_protocol::telemetry; use super::super::common_types::options::TelemetryReceiverOptions; -use super::MODEL_ID; -use super::TELEMETRY_TOPIC_PATTERN; use super::telemetry_collection::TelemetryCollection; -pub type TelemetryMessage = telemetry::receiver::Message; +pub type TelemetryCollectionMessage = telemetry::receiver::Message; /// Telemetry Receiver for `TelemetryCollection` -pub struct TelemetryReceiver(telemetry::Receiver); +pub struct TelemetryCollectionReceiver(telemetry::Receiver); -impl TelemetryReceiver { - /// Creates a new [`TelemetryReceiver`] +impl TelemetryCollectionReceiver { + /// Creates a new [`TelemetryCollectionReceiver`] /// /// # Panics /// If the DTDL that generated this code was invalid @@ -32,17 +30,15 @@ impl TelemetryReceiver { receiver_options_builder.topic_namespace(topic_namespace.clone()); } - let mut topic_token_map: HashMap = options + let topic_token_map: HashMap = options .topic_token_map .clone() .into_iter() .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); - let receiver_options = receiver_options_builder - .topic_pattern(TELEMETRY_TOPIC_PATTERN) + .topic_pattern("telemetry/telemetry-samples/counterValue") .topic_token_map(topic_token_map) .auto_ack(options.auto_ack) .build() @@ -54,7 +50,7 @@ impl TelemetryReceiver { ) } - /// Shut down the [`TelemetryReceiver`] + /// Shut down the [`TelemetryCollectionReceiver`] /// /// # Errors /// [`AIOProtocolError`] if there is a failure in graceful shutdown @@ -62,13 +58,13 @@ impl TelemetryReceiver { self.0.shutdown().await } - /// Receive the next [`TelemetryMessage`] + /// Receive the next [`TelemetryCollectionMessage`] /// /// # Errors /// [`AIOProtocolError`] if there is a failure receiving a message pub async fn recv( &mut self, - ) -> Option), AIOProtocolError>> { + ) -> Option), AIOProtocolError>> { self.0.recv().await } } diff --git a/rust/sample_applications/counter/envoy/src/counter/telemetry_sender.rs b/rust/sample_applications/counter/envoy/src/counter/telemetry_collection_sender.rs similarity index 81% rename from rust/sample_applications/counter/envoy/src/counter/telemetry_sender.rs rename to rust/sample_applications/counter/envoy/src/counter/telemetry_collection_sender.rs index 45ee8a70e4..bb7a820d6f 100644 --- a/rust/sample_applications/counter/envoy/src/counter/telemetry_sender.rs +++ b/rust/sample_applications/counter/envoy/src/counter/telemetry_collection_sender.rs @@ -11,21 +11,19 @@ use azure_iot_operations_protocol::common::payload_serialize::PayloadSerialize; use azure_iot_operations_protocol::telemetry; use super::super::common_types::options::TelemetrySenderOptions; -use super::MODEL_ID; -use super::TELEMETRY_TOPIC_PATTERN; use super::telemetry_collection::TelemetryCollection; -pub type TelemetryMessage = telemetry::sender::Message; -pub type TelemetryMessageBuilderError = telemetry::sender::MessageBuilderError; +pub type TelemetryCollectionMessage = telemetry::sender::Message; +pub type TelemetryCollectionMessageBuilderError = telemetry::sender::MessageBuilderError; -/// Builder for [`TelemetryMessage`] +/// Builder for [`TelemetryCollectionMessage`] #[derive(Default)] -pub struct TelemetryMessageBuilder { +pub struct TelemetryCollectionMessageBuilder { inner_builder: telemetry::sender::MessageBuilder, topic_tokens: HashMap, } -impl TelemetryMessageBuilder { +impl TelemetryCollectionMessageBuilder { /// Quality of Service of the telemetry message. Can only be `AtMostOnce` or `AtLeastOnce`. pub fn qos(&mut self, qos: QoS) -> &mut Self { self.inner_builder.qos(qos); @@ -69,11 +67,13 @@ impl TelemetryMessageBuilder { Ok(self) } - /// Builds a new `TelemetryMessage` + /// Builds a new `TelemetryCollectionMessage` /// /// # Errors /// If a required field has not been initialized - pub fn build(&mut self) -> Result { + pub fn build( + &mut self, + ) -> Result { self.inner_builder.topic_tokens(self.topic_tokens.clone()); self.inner_builder.build() @@ -81,10 +81,10 @@ impl TelemetryMessageBuilder { } /// Telemetry Sender for `TelemetryCollection` -pub struct TelemetrySender(telemetry::Sender); +pub struct TelemetryCollectionSender(telemetry::Sender); -impl TelemetrySender { - /// Creates a new [`TelemetrySender`] +impl TelemetryCollectionSender { + /// Creates a new [`TelemetryCollectionSender`] /// /// # Panics /// If the DTDL that generated this code was invalid @@ -105,11 +105,10 @@ impl TelemetrySender { .map(|(k, v)| (format!("ex:{k}"), v)) .collect(); - topic_token_map.insert("modelId".to_string(), MODEL_ID.to_string()); topic_token_map.insert("senderId".to_string(), client.client_id().to_string()); let sender_options = sender_options_builder - .topic_pattern(TELEMETRY_TOPIC_PATTERN) + .topic_pattern("telemetry/telemetry-samples/counterValue") .topic_token_map(topic_token_map) .build() .expect("DTDL schema generated invalid arguments"); @@ -120,11 +119,11 @@ impl TelemetrySender { ) } - /// Sends a [`TelemetryMessage`] + /// Sends a [`TelemetryCollectionMessage`] /// /// # Error /// [`AIOProtocolError`] if there is a failure sending the message - pub async fn send(&self, message: TelemetryMessage) -> Result<(), AIOProtocolError> { + pub async fn send(&self, message: TelemetryCollectionMessage) -> Result<(), AIOProtocolError> { self.0.send(message).await } } diff --git a/rust/sample_applications/counter/gen.sh b/rust/sample_applications/counter/gen.sh index d839f13e7a..6f55c110f4 100755 --- a/rust/sample_applications/counter/gen.sh +++ b/rust/sample_applications/counter/gen.sh @@ -1,3 +1,5 @@ #!/bin/sh -../../../codegen/src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler \ - --modelFile ../../../eng/test/schema-samples/counter.json --outDir ./envoy --sdkPath ../.. --lang=rust; + +rm -r ./envoy +../../../codegen2/src/Azure.Iot.Operations.ProtocolCompiler/bin/Debug/net9.0/Azure.Iot.Operations.ProtocolCompiler \ + --thingFiles ../../../eng/test/schema-samples/Counter.TM.json --outDir ./envoy --sdkPath ../.. --lang=rust --namespace Counter --workingDir target/counter;