This is an automated email from the ASF dual-hosted git repository.
ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 895502ecd1 IGNITE-17314 .NET: Implement unified exception handling
(#1074)
895502ecd1 is described below
commit 895502ecd188189a283a9fea37bac86a68d27b12
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Mon Sep 19 08:24:13 2022 +0300
IGNITE-17314 .NET: Implement unified exception handling (#1074)
* Add all exception groups, codes, and classes from the Java side.
* When server returns an error, throw corresponding mapped .NET exception
type.
* Use [Source
Generators](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview)
to produce C# code for error groups and exception classes. When there is a new
error code or exception added on Java side, no changes are required on C# side,
only a rebuild.
---
.../Apache.Ignite.Internal.Generators.csproj | 41 +++++
.../ErrorGroupsGenerator.cs | 153 ++++++++++++++++
.../ExceptionTemplate.cs | 60 +++++++
.../ExceptionsGenerator.cs | 177 +++++++++++++++++++
.../GeneratorUtils.cs | 37 ++++
.../platforms/dotnet/Apache.Ignite.Tests.ruleset | 3 +
.../Apache.Ignite.Tests/ClientSocketTests.cs | 2 +-
.../Compute/ComputeClusterAwarenessTests.cs | 2 +-
.../Apache.Ignite.Tests/Compute/ComputeTests.cs | 15 +-
.../dotnet/Apache.Ignite.Tests/ErrorGroupTests.cs | 196 +++++++++++++++++++++
.../dotnet/Apache.Ignite.Tests/ExceptionsTests.cs | 101 +++++++++++
.../dotnet/Apache.Ignite.Tests/FakeServer.cs | 4 +-
.../dotnet/Apache.Ignite.Tests/FakeServerTests.cs | 8 +-
.../Apache.Ignite.Tests/ProjectFilesTests.cs | 1 +
.../dotnet/Apache.Ignite.Tests/RetryPolicyTests.cs | 16 +-
.../dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs | 41 ++++-
.../dotnet/Apache.Ignite.Tests/StringExtensions.cs | 30 ++++
.../Apache.Ignite.Tests/Table/IgniteTupleTests.cs | 8 +-
.../Table/RecordViewBinaryTests.cs | 2 +-
.../Table/RecordViewPocoTests.cs | 2 +-
modules/platforms/dotnet/Apache.Ignite.sln | 6 +
.../dotnet/Apache.Ignite/Apache.Ignite.csproj | 7 +
.../platforms/dotnet/Apache.Ignite/ErrorGroups.cs | 68 +++++++
.../IgniteClientConnectionException.cs | 70 ++++++++
.../dotnet/Apache.Ignite/IgniteClientException.cs | 80 +++------
.../dotnet/Apache.Ignite/IgniteException.cs | 97 ++++++++++
.../Apache.Ignite/Internal/ClientFailoverSocket.cs | 11 +-
.../dotnet/Apache.Ignite/Internal/ClientSocket.cs | 28 +--
.../Apache.Ignite/Internal/Compute/Compute.cs | 5 +-
.../dotnet/Apache.Ignite/Internal/Endpoint.cs | 7 +-
.../Proto/BinaryTuple/BinaryTupleBuilder.cs | 2 +-
.../Proto/BinaryTuple/BinaryTupleReader.cs | 2 +-
.../Internal/Proto/MessagePackReaderExtensions.cs | 4 +-
.../Internal/Proto/MessagePackWriterExtensions.cs | 4 +-
.../dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs | 10 +-
.../Table/Serialization/BinaryTupleMethods.cs | 4 +-
.../Table/Serialization/ObjectSerializerHandler.cs | 7 +-
.../dotnet/Apache.Ignite/Internal/Table/Table.cs | 2 +-
.../dotnet/Apache.Ignite/Sql/SqlStatement.cs | 2 +-
.../dotnet/Apache.Ignite/Table/IgniteTuple.cs | 12 +-
modules/platforms/dotnet/DEVNOTES.md | 3 +-
modules/platforms/dotnet/Directory.Build.props | 2 +-
42 files changed, 1204 insertions(+), 128 deletions(-)
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/Apache.Ignite.Internal.Generators.csproj
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/Apache.Ignite.Internal.Generators.csproj
new file mode 100644
index 0000000000..e5f11d2788
--- /dev/null
+++
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/Apache.Ignite.Internal.Generators.csproj
@@ -0,0 +1,41 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <Nullable>enable</Nullable>
+
<CodeAnalysisRuleSet>..\Apache.Ignite.Tests.ruleset</CodeAnalysisRuleSet>
+ <IsPackable>false</IsPackable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <!-- Source Generators -->
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp"
Version="3.11.0" PrivateAssets="all" />
+ <PackageReference Include="Microsoft.CodeAnalysis.Analyzers"
Version="3.3.3">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers;
buildtransitive</IncludeAssets>
+ </PackageReference>
+ </ItemGroup>
+
+ <ItemGroup>
+ <Compile Remove="ExceptionTemplate.cs" />
+ <EmbeddedResource Include="ExceptionTemplate.cs" />
+ </ItemGroup>
+
+</Project>
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/ErrorGroupsGenerator.cs
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/ErrorGroupsGenerator.cs
new file mode 100644
index 0000000000..764ecc3cd5
--- /dev/null
+++
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/ErrorGroupsGenerator.cs
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Internal.Generators
+{
+ using System;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Reflection;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using Microsoft.CodeAnalysis;
+
+ /// <summary>
+ /// Generates error groups source from ErrorGroups.java.
+ /// </summary>
+ [Generator]
+ public sealed class ErrorGroupsGenerator : ISourceGenerator
+ {
+ /// <inheritdoc/>
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ // No-op.
+ }
+
+ /// <inheritdoc/>
+ public void Execute(GeneratorExecutionContext context)
+ {
+ var javaErrorGroupsFile = Path.GetFullPath(Path.Combine(
+ context.GetJavaModulesDirectory(),
+ "core",
+ "src",
+ "main",
+ "java",
+ "org",
+ "apache",
+ "ignite",
+ "lang",
+ "ErrorGroups.java"));
+
+ if (!File.Exists(javaErrorGroupsFile))
+ {
+ throw new Exception("File not found: " + javaErrorGroupsFile +
" = " + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!);
+ }
+
+ var javaErrorGroupsText = File.ReadAllText(javaErrorGroupsFile);
+
+ // ErrorGroup TX_ERR_GROUP = ErrorGroup.newGroup("TX", 7);
+ var javaErrorGroups = Regex.Matches(
+ javaErrorGroupsText,
+ @"public static class ([A-Za-z]+) {\s+/\*\*.*?\*/\s+public
static final ErrorGroup ([\w_]+)_ERR_GROUP = ErrorGroup.newGroup\(""([A-Z]+)"",
(\d+)",
+ RegexOptions.Singleline | RegexOptions.CultureInvariant)
+ .Cast<Match>()
+ .Select(x => (ClassName: x.Groups[1].Value, GroupName:
x.Groups[2].Value, ShortGroupName: x.Groups[3].Value, Code:
int.Parse(x.Groups[4].Value, CultureInfo.InvariantCulture)))
+ .ToList();
+
+ if (javaErrorGroups.Count == 0)
+ {
+ throw new Exception($"Failed to parse Java error groups from
'{javaErrorGroupsFile}'");
+ }
+
+ var sb = new StringBuilder();
+
+ sb.AppendLine("// <auto-generated/>");
+ sb.AppendLine("namespace Apache.Ignite");
+ sb.AppendLine("{");
+ sb.AppendLine(" using System;\n");
+ sb.AppendLine(" public static partial class ErrorGroups");
+ sb.AppendLine(" {");
+
+ // GetGroupName.
+ sb.AppendLine(
+@" /// <summary>
+ /// Gets the group name by code.
+ /// </summary>
+ /// <param name=""groupCode"">Group code.</param>
+ /// <returns>Group name.</returns>
+ public static string GetGroupName(int groupCode) => groupCode switch
+ {");
+
+ foreach (var (className, _, _, _) in javaErrorGroups)
+ {
+ sb.AppendLine(@$" {className}.GroupCode =>
{className}.GroupName,");
+ }
+
+ sb.AppendLine(
+ @"
+ _ => UnknownGroupName
+ };");
+
+ // Groups.
+ foreach (var (className, groupName, shortGroupName, groupCode) in
javaErrorGroups)
+ {
+ sb.AppendLine();
+ sb.AppendLine($" /// <summary> {className} errors.
</summary>");
+ sb.AppendLine($" public static class {className}");
+ sb.AppendLine($" {{");
+ sb.AppendLine($" /// <summary> {className} group
code. </summary>");
+ sb.AppendLine($" public const int GroupCode =
{groupCode};");
+ sb.AppendLine();
+ sb.AppendLine($" /// <summary> {className} group
name. </summary>");
+ sb.AppendLine($" public const string GroupName =
\"{shortGroupName}\";");
+
+ // TX_STATE_STORAGE_CREATE_ERR =
TX_ERR_GROUP.registerErrorCode(1)
+ var javaErrors = Regex.Matches(
+ javaErrorGroupsText,
+ @"([\w_]+)_ERR = " + groupName +
@"_ERR_GROUP\.registerErrorCode\((\d+)\);")
+ .Cast<Match>()
+ .Select(x => (Name: x.Groups[1].Value, Code:
int.Parse(x.Groups[2].Value, CultureInfo.InvariantCulture)))
+ .ToList();
+
+ if (javaErrors.Count == 0)
+ {
+ throw new Exception($"Failed to parse Java errors for
group {groupName} from '{javaErrorGroupsFile}'");
+ }
+
+ foreach (var (errorName, errorCode) in javaErrors)
+ {
+ var dotNetErrorName = SnakeToCamelCase(errorName);
+
+ sb.AppendLine();
+ sb.AppendLine($" /// <summary>
{dotNetErrorName} error. </summary>");
+ sb.AppendLine($" public static readonly int
{dotNetErrorName} = GetFullCode(GroupCode, {errorCode});");
+ }
+
+ sb.AppendLine(" }");
+ }
+
+ sb.AppendLine(" }");
+ sb.AppendLine("}");
+
+ context.AddSource("ErrorGroups.g.cs", sb.ToString());
+ }
+
+ private static string SnakeToCamelCase(string str) =>
+ string.Concat(str.Split('_').Select(x => x.Substring(0,
1).ToUpperInvariant() + x.Substring(1).ToLowerInvariant()));
+ }
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/ExceptionTemplate.cs
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/ExceptionTemplate.cs
new file mode 100644
index 0000000000..b9b5088143
--- /dev/null
+++
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/ExceptionTemplate.cs
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// <auto-generated/>
+#nullable enable
+namespace NAMESPACE
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Runtime.Serialization;
+
+ /// <summary>
+ /// XMLDOC
+ /// </summary>
+ [Serializable]
+ [SuppressMessage(
+ "Microsoft.Design",
+ "CA1032:ImplementStandardExceptionConstructors",
+ Justification="Ignite exceptions use a special constructor.")]
+ public sealed class IgniteTemplateException : IgniteException
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see
cref="IgniteTemplateException"/> class.
+ /// </summary>
+ /// <param name="traceId">Trace id.</param>
+ /// <param name="code">Code.</param>
+ /// <param name="message">Message.</param>
+ /// <param name="innerException">Inner exception.</param>
+ public IgniteTemplateException(Guid traceId, int code, string?
message, Exception? innerException = null)
+ : base(traceId, code, message, innerException)
+ {
+ // No-op.
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see
cref="IgniteTemplateException"/> class.
+ /// </summary>
+ /// <param name="serializationInfo">Serialization information.</param>
+ /// <param name="streamingContext">Streaming context.</param>
+ private IgniteTemplateException(SerializationInfo serializationInfo,
StreamingContext streamingContext)
+ : base(serializationInfo, streamingContext)
+ {
+ // No-op.
+ }
+ }
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/ExceptionsGenerator.cs
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/ExceptionsGenerator.cs
new file mode 100644
index 0000000000..0bd353f5a0
--- /dev/null
+++
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/ExceptionsGenerator.cs
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Internal.Generators
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Immutable;
+ using System.IO;
+ using System.Linq;
+ using System.Reflection;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using Microsoft.CodeAnalysis;
+
+ /// <summary>
+ /// Generates exception classes from Java exceptions.
+ /// </summary>
+ [Generator]
+ public sealed class ExceptionsGenerator : ISourceGenerator
+ {
+ /// <inheritdoc/>
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ // No-op.
+ }
+
+ /// <inheritdoc/>
+ public void Execute(GeneratorExecutionContext context)
+ {
+ var javaModulesDirectory = context.GetJavaModulesDirectory();
+
+ var javaExceptionsWithParents = Directory.EnumerateFiles(
+ javaModulesDirectory,
+ "*Exception.java",
+ SearchOption.AllDirectories)
+ .Where(x => !x.Contains("internal"))
+ .Select(File.ReadAllText)
+ .Select(x => (
+ Class: Regex.Match(x, @"public class (\w+) extends (\w+)"),
+ Source: x))
+ .Where(x => x.Class.Success)
+ .Where(x => !x.Class.Value.Contains("RaftException")) //
Ignore duplicate RaftException.
+ .Where(x => !x.Class.Value.Contains("IgniteClient")) // Skip
Java client exceptions.
+ .ToDictionary(x => x.Class.Groups[1].Value, x => (Parent:
x.Class.Groups[2].Value, x.Source));
+
+ var existingExceptions = context.Compilation.SyntaxTrees
+ .Where(x => x.FilePath.Contains("Exception"))
+ .Select(x => Path.GetFileNameWithoutExtension(x.FilePath))
+ .ToImmutableHashSet();
+
+ var javaExceptions = javaExceptionsWithParents
+ .Where(x => !existingExceptions.Contains(x.Key))
+ .Where(x => IsIgniteException(x.Key))
+ .ToList();
+
+ if (javaExceptionsWithParents.Count == 0 || javaExceptions.Count
== 0)
+ {
+ throw new Exception($"Failed to detect Java exception classes
in {javaModulesDirectory}.");
+ }
+
+ var template = GetExceptionClassTemplate();
+
+ var classMap = new List<(string JavaClass, string DotNetClass)>();
+
+ foreach (var javaException in javaExceptions)
+ {
+ var className = javaException.Key;
+
+ var (javaPackage, dotNetNamespace) =
GetPackageAndNamespace(className, javaException.Value.Source);
+
+ var src = template
+ .Replace("IgniteTemplateException", className)
+ .Replace("XMLDOC", GetXmlDoc(className,
javaException.Value.Source))
+ .Replace("NAMESPACE", dotNetNamespace);
+
+ context.AddSource(className + ".g.cs", src);
+
+ classMap.Add((javaPackage + "." + className, dotNetNamespace +
"." + className));
+ }
+
+ EmitClassMap(context, classMap);
+
+ bool IsIgniteException(string? ex) =>
+ ex != null &&
+ (ex == "IgniteException" ||
+ IsIgniteException(javaExceptionsWithParents.TryGetValue(ex,
out var parent) ? parent.Parent : null));
+ }
+
+ private static void EmitClassMap(GeneratorExecutionContext context,
List<(string JavaClass, string DotNetClass)> classMap)
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("// <auto-generated/>");
+ sb.AppendLine("#nullable enable");
+ sb.AppendLine("namespace Apache.Ignite.Internal");
+ sb.AppendLine("{");
+ sb.AppendLine(" using System;");
+ sb.AppendLine();
+ sb.AppendLine(" internal static class ExceptionMapper");
+ sb.AppendLine(" {");
+ sb.AppendLine(" public static IgniteException
GetException(Guid traceId, int code, string javaClass, string? message) =>");
+ sb.AppendLine(" javaClass switch");
+ sb.AppendLine(" {");
+
+ foreach (var (javaClass, dotNetClass) in classMap)
+ {
+ sb.AppendLine($" \"{javaClass}\" => new
{dotNetClass}(traceId, code, message, new IgniteException(traceId, code,
javaClass)),");
+ }
+
+ sb.AppendLine(" _ => new IgniteException(traceId,
code, message, new IgniteException(traceId, code, javaClass))");
+ sb.AppendLine(" };");
+ sb.AppendLine(" }");
+ sb.AppendLine("}");
+
+ context.AddSource("ExceptionMapper.g.cs", sb.ToString());
+ }
+
+ private static string GetXmlDoc(string javaClassName, string
javaSource)
+ {
+ var javaDocMatch = Regex.Match(javaSource,
@"/\*\*\s*\*?\s*(.*?)\s*\*/\s+public class", RegexOptions.Singleline);
+
+ if (!javaDocMatch.Success)
+ {
+ throw new Exception($"Failed to parse Java package name from
'{javaClassName}.java'");
+ }
+
+ var xmlDoc = javaDocMatch.Groups[1].Value
+ .Replace(Environment.NewLine, " ")
+ .Replace('\n', ' ')
+ .Replace(" * ", " ");
+
+ return xmlDoc;
+ }
+
+ private static (string JavaPackage, string DotNetNamespace)
GetPackageAndNamespace(string javaClassName, string javaSource)
+ {
+ var javaPackageMatch = Regex.Match(javaSource, @"package
org\.apache(\.[a-z.]+);");
+
+ if (!javaPackageMatch.Success)
+ {
+ throw new Exception($"Failed to parse Java package name from
'{javaClassName}.java'");
+ }
+
+ var javaPackage = javaPackageMatch.Groups[1].Value;
+
+ var ns = Regex.Replace(javaPackage, @"(\.[a-z])", x =>
x.Groups[1].Value.ToUpperInvariant())
+ .Replace(".Lang", string.Empty);
+
+ return ("org.apache" + javaPackage, "Apache" + ns);
+ }
+
+ private static string GetExceptionClassTemplate()
+ {
+ using var stream =
Assembly.GetExecutingAssembly().GetManifestResourceStream(
+ "Apache.Ignite.Internal.Generators.ExceptionTemplate.cs");
+
+ using var reader = new StreamReader(stream!);
+
+ return reader.ReadToEnd();
+ }
+ }
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/GeneratorUtils.cs
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/GeneratorUtils.cs
new file mode 100644
index 0000000000..8e95f6f4fc
--- /dev/null
+++
b/modules/platforms/dotnet/Apache.Ignite.Internal.Generators/GeneratorUtils.cs
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Internal.Generators
+{
+ using System;
+ using System.IO;
+ using System.Linq;
+ using Microsoft.CodeAnalysis;
+
+ /// <summary>
+ /// Source generator utils.
+ /// </summary>
+ internal static class GeneratorUtils
+ {
+ public static string GetProjectRootDirectory(this
GeneratorExecutionContext context) =>
+ Path.GetDirectoryName(
+ context.Compilation.SyntaxTrees.Single(x =>
x.FilePath.EndsWith("IIgnite.cs", StringComparison.Ordinal)).FilePath)!;
+
+ public static string GetJavaModulesDirectory(this
GeneratorExecutionContext context) =>
+ Path.GetFullPath(Path.Combine(context.GetProjectRootDirectory(),
"..", "..", ".."));
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests.ruleset
b/modules/platforms/dotnet/Apache.Ignite.Tests.ruleset
index a95763d3d5..921a068e9e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests.ruleset
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests.ruleset
@@ -55,5 +55,8 @@
<!-- Validate parameters. -->
<Rule Id="CA1062" Action="None" />
+
+ <!-- Normalize to upper case -->
+ <Rule Id="CA1308" Action="None" />
</Rules>
</RuleSet>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/ClientSocketTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/ClientSocketTests.cs
index e845884025..18deb81586 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/ClientSocketTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/ClientSocketTests.cs
@@ -59,7 +59,7 @@ namespace Apache.Ignite.Tests
using var requestWriter = ProtoCommon.GetMessageWriter();
requestWriter.GetMessageWriter().Write(123);
- var ex = Assert.ThrowsAsync<IgniteClientException>(
+ var ex = Assert.ThrowsAsync<IgniteException>(
async () => await socket.DoOutInOpAsync((ClientOp)1234567,
requestWriter));
StringAssert.Contains("Unexpected operation code: 1234567",
ex!.Message);
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeClusterAwarenessTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeClusterAwarenessTests.cs
index 8c798f7bc5..24220b2d40 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeClusterAwarenessTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeClusterAwarenessTests.cs
@@ -42,7 +42,7 @@ namespace Apache.Ignite.Tests.Compute
using var client = await IgniteClient.StartAsync(clientCfg);
// ReSharper disable once AccessToDisposedClosure
- TestUtils.WaitForCondition(() => client.GetConnections().Count ==
3);
+ TestUtils.WaitForCondition(() => client.GetConnections().Count ==
3, 5000);
var res2 = await client.Compute.ExecuteAsync<string>(nodes: new[]
{ server2.Node }, jobClassName: string.Empty);
var res3 = await client.Compute.ExecuteAsync<string>(nodes: new[]
{ server3.Node }, jobClassName: string.Empty);
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
index 795cbccde1..2d6d728dc7 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Compute/ComputeTests.cs
@@ -133,10 +133,19 @@ namespace Apache.Ignite.Tests.Compute
[Test]
public void TestJobErrorPropagatesToClientWithClassAndMessage()
{
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
+ var ex = Assert.ThrowsAsync<IgniteException>(async () =>
await Client.Compute.ExecuteAsync<string>(await
Client.GetClusterNodesAsync(), ErrorJob, "unused"));
StringAssert.Contains("Custom job error", ex!.Message);
+
+ Assert.AreEqual(
+
"org.apache.ignite.internal.runner.app.client.ItThinClientComputeTest$CustomException",
+ ex.InnerException!.Message);
+
+ Assert.AreEqual(ErrorGroups.Table.ColumnAlreadyExists, ex.Code);
+ Assert.AreEqual("IGN-TBL-3", ex.CodeAsString);
+ Assert.AreEqual(3, ex.ErrorCode);
+ Assert.AreEqual("TBL", ex.GroupName);
}
[Test]
@@ -144,7 +153,7 @@ namespace Apache.Ignite.Tests.Compute
{
var unknownNode = new ClusterNode("x", "y", new
IPEndPoint(IPAddress.Loopback, 0));
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
+ var ex = Assert.ThrowsAsync<IgniteException>(async () =>
await Client.Compute.ExecuteAsync<string>(new[] { unknownNode
}, EchoJob, "unused"));
StringAssert.Contains("Specified node is not present in the
cluster: y", ex!.Message);
@@ -215,7 +224,7 @@ namespace Apache.Ignite.Tests.Compute
[Test]
public void TestExecuteColocatedThrowsWhenKeyColumnIsMissing()
{
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
+ var ex = Assert.ThrowsAsync<IgniteException>(async () =>
await Client.Compute.ExecuteColocatedAsync<string>(TableName,
new IgniteTuple(), EchoJob));
StringAssert.Contains("Missed key column: KEY", ex!.Message);
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/ErrorGroupTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/ErrorGroupTests.cs
new file mode 100644
index 0000000000..247100ec89
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/ErrorGroupTests.cs
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Tests
+{
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+ using NUnit.Framework;
+
+ /// <summary>
+ /// Tests for <see cref="ErrorGroups"/>.
+ /// </summary>
+ public class ErrorGroupTests
+ {
+ private static readonly string JavaErrorGroupsFile = Path.Combine(
+ TestUtils.RepoRootDir, "modules", "core", "src", "main", "java",
"org", "apache", "ignite", "lang", "ErrorGroups.java");
+
+ [Test]
+ public void TestErrorGroupCodesAreUnique()
+ {
+ var existingCodes = new Dictionary<int, string>();
+
+ foreach (var (code, name) in GetErrorGroups())
+ {
+ if (existingCodes.TryGetValue(code, out var existingGroupName))
+ {
+ Assert.Fail($"Duplicate group code: {code}
({existingGroupName} and {name})");
+ }
+
+ existingCodes.Add(code, name);
+ }
+ }
+
+ [Test]
+ public void TestGetGroupNameReturnsUniqueNames()
+ {
+ var existingNames = new Dictionary<string, string>();
+
+ foreach (var (code, className) in GetErrorGroups())
+ {
+ var name = ErrorGroups.GetGroupName(code);
+
+ if (existingNames.TryGetValue(name, out var existingClassName))
+ {
+ Assert.Fail($"Duplicate group name: {name}
({existingClassName} and {className})");
+ }
+
+ existingNames.Add(name, className);
+ }
+ }
+
+ [Test]
+ public void TestErrorCodesAreUnique()
+ {
+ var duplicateCodes = GetErrorCodes()
+ .GroupBy(x => x.Code)
+ .Select(x => x.ToList())
+ .Where(x => x.Count > 1)
+ .ToList();
+
+ Assert.Multiple(() => duplicateCodes.ForEach(
+ x => Assert.Fail($"Duplicate error code: {x[0].Code}
({string.Join(", ", x.Select(y => y.Name))})")));
+ }
+
+ [Test]
+ public void TestErrorCodeMatchesParentGroup()
+ {
+ foreach (var (code, group, name) in GetErrorCodes())
+ {
+ var expectedGroup = ErrorGroups.GetGroupCode(code);
+
+ Assert.AreEqual(expectedGroup, group, $"Code {code} ({name})
has incorrect group. Expected {expectedGroup}, got {group}.");
+ }
+ }
+
+ [Test]
+ public void TestJavaErrorGroupsAndCodesHaveDotNetCounterparts()
+ {
+ var dotNetErrorCodes = GetErrorCodes().ToDictionary(x => x.Code, x
=> (x.Name, x.GroupCode));
+
+ var javaErrorGroupsText = File.ReadAllText(JavaErrorGroupsFile);
+
+ // ErrorGroup TX_ERR_GROUP = ErrorGroup.newGroup("TX", 7);
+ var javaErrorGroups = Regex.Matches(
+ javaErrorGroupsText,
+ @"ErrorGroup ([\w_]+)_ERR_GROUP =
ErrorGroup.newGroup\(""(\w+)"", (\d+)\);")
+ .Select(x => (Name: x.Groups[1].Value, ShortName:
x.Groups[2].Value, Code: int.Parse(x.Groups[3].Value,
CultureInfo.InvariantCulture)))
+ .ToList();
+
+ Assert.GreaterOrEqual(javaErrorGroups.Count, 7);
+
+ foreach (var (grpName, grpShortName, grpCode) in javaErrorGroups)
+ {
+ var dotNetName = ErrorGroups.GetGroupName(grpCode);
+
+ Assert.AreEqual(grpShortName, dotNetName, $"Java and .NET
error group '{grpName}' names do not match");
+
+ // TX_STATE_STORAGE_CREATE_ERR =
TX_ERR_GROUP.registerErrorCode(1)
+ var javaErrors = Regex.Matches(
+ javaErrorGroupsText,
+ @"([\w_]+) = " + grpName +
@"_ERR_GROUP\.registerErrorCode\((\d+)\);")
+ .Select(x => (Name: x.Groups[1].Value, Code:
int.Parse(x.Groups[2].Value, CultureInfo.InvariantCulture)))
+ .ToList();
+
+ Assert.IsNotEmpty(javaErrors);
+
+ foreach (var (errName, errCode) in javaErrors)
+ {
+ var fullErrCode = ErrorGroups.GetFullCode(grpCode,
errCode);
+ var expectedDotNetName = errName.SnakeToCamelCase()[..^3];
+
+ if (!dotNetErrorCodes.TryGetValue(fullErrCode, out var
dotNetError))
+ {
+ Assert.Fail(
+ $"Java error '{errName}' ('{errCode}') in group
'{grpName}' ('{grpCode}') has no .NET counterpart.\n" +
+ $"public static readonly int {expectedDotNetName}
= GetFullCode(GroupCode, {errCode});");
+ }
+
+ Assert.AreEqual(grpCode, dotNetError.GroupCode);
+ Assert.AreEqual(expectedDotNetName, dotNetError.Name);
+ }
+ }
+ }
+
+ [Test]
+ public void TestGeneratedErrorGroups()
+ {
+ Assert.AreEqual(1, ErrorGroups.Common.GroupCode);
+ Assert.AreEqual("CMN", ErrorGroups.Common.GroupName);
+ Assert.AreEqual("CMN", ErrorGroups.GetGroupName(1));
+
+ Assert.AreEqual(2, ErrorGroups.Table.GroupCode);
+ Assert.AreEqual("TBL", ErrorGroups.Table.GroupName);
+ Assert.AreEqual("TBL", ErrorGroups.GetGroupName(2));
+
+ Assert.AreEqual(3, ErrorGroups.Client.GroupCode);
+ Assert.AreEqual("CLIENT", ErrorGroups.Client.GroupName);
+ Assert.AreEqual("CLIENT", ErrorGroups.GetGroupName(3));
+
+ Assert.AreEqual(4, ErrorGroups.Sql.GroupCode);
+ Assert.AreEqual("SQL", ErrorGroups.Sql.GroupName);
+ Assert.AreEqual("SQL", ErrorGroups.GetGroupName(4));
+
+ Assert.AreEqual(5, ErrorGroups.MetaStorage.GroupCode);
+ Assert.AreEqual("META", ErrorGroups.MetaStorage.GroupName);
+ Assert.AreEqual("META", ErrorGroups.GetGroupName(5));
+
+ Assert.AreEqual(6, ErrorGroups.Index.GroupCode);
+ Assert.AreEqual("IDX", ErrorGroups.Index.GroupName);
+ Assert.AreEqual("IDX", ErrorGroups.GetGroupName(6));
+
+ Assert.AreEqual(7, ErrorGroups.Transactions.GroupCode);
+ Assert.AreEqual("TX", ErrorGroups.Transactions.GroupName);
+ Assert.AreEqual("TX", ErrorGroups.GetGroupName(7));
+ }
+
+ [Test]
+ public void TestGetGroupNameForUnknownGroupCode()
+ {
+ // Newer servers may return unknown to us error codes.
+ Assert.AreEqual("UNKNOWN", ErrorGroups.GetGroupName(-1));
+ Assert.AreEqual(ErrorGroups.UnknownGroupName,
ErrorGroups.GetGroupName(9999));
+ }
+
+ private static IEnumerable<(int Code, string Name)> GetErrorGroups()
=> typeof(ErrorGroups).GetNestedTypes()
+ .Select(x => ((int) x.GetField("GroupCode")!.GetValue(null)!,
x.Name));
+
+ private static IEnumerable<(int Code, int GroupCode, string Name)>
GetErrorCodes() => typeof(ErrorGroups).GetNestedTypes()
+ .SelectMany(groupClass =>
+ {
+ var groupCode =
(int)groupClass.GetField("GroupCode")!.GetValue(null)!;
+
+ return groupClass
+ .GetFields()
+ .Where(x => x.Name != "GroupCode" && x.Name !=
"GroupName")
+ .Select(errCode => ((int)errCode.GetValue(null)!,
groupCode, errCode.Name));
+ });
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/ExceptionsTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/ExceptionsTests.cs
new file mode 100644
index 0000000000..ad15237cf6
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/ExceptionsTests.cs
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Tests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Immutable;
+ using System.IO;
+ using System.Linq;
+ using System.Runtime.Serialization.Formatters.Binary;
+ using System.Text.RegularExpressions;
+ using NUnit.Framework;
+
+ /// <summary>
+ /// Tests Ignite exceptions.
+ /// </summary>
+ public class ExceptionsTests
+ {
+ [Test]
+ public void TestExceptionsAreSerializableAndHaveRequiredConstructors()
+ {
+ var types = typeof(IIgnite).Assembly.GetTypes().Where(x =>
x.IsSubclassOf(typeof(Exception)));
+
+ foreach (var type in types)
+ {
+ Assert.IsTrue(type.IsSerializable, "Exception is not
serializable: " + type);
+
+ // Check required IgniteException constructor.
+ var defCtor = type.GetConstructor(new[] { typeof(Guid),
typeof(int), typeof(string), typeof(Exception) });
+ Assert.IsNotNull(defCtor, "Required constructor is missing: "
+ type);
+
+ var traceId = Guid.NewGuid();
+ var ex = (IgniteException)defCtor!.Invoke(new object[] {
traceId, 123, "myMessage", new Exception() });
+ Assert.AreEqual("myMessage", ex.Message);
+
+ // Serialization.
+ var stream = new MemoryStream();
+ var formatter = new BinaryFormatter();
+
+ formatter.Serialize(stream, ex);
+ stream.Seek(0, SeekOrigin.Begin);
+
+ var res = (IgniteException) formatter.Deserialize(stream);
+ Assert.AreEqual("myMessage", res.Message);
+ Assert.AreEqual(traceId, res.TraceId);
+ Assert.AreEqual(123, res.Code);
+ }
+ }
+
+ [Test]
+ public void TestAllJavaIgniteExceptionsHaveDotNetCounterparts()
+ {
+ var modulesDir =
Path.GetFullPath(Path.Combine(TestUtils.RepoRootDir, "modules"));
+
+ var javaExceptionsWithParents =
Directory.EnumerateFiles(modulesDir, "*Exception.java",
SearchOption.AllDirectories)
+ .Where(x => !x.Contains("internal"))
+ .Select(File.ReadAllText)
+ .Select(x => Regex.Match(x, @"public class (\w+) extends
(\w+)"))
+ .Where(x => x.Success && !x.Value.Contains("RaftException"))
// Ignore duplicate RaftException.
+ .Where(x => !x.Value.Contains("IgniteClient")) // Skip Java
client exceptions.
+ .ToDictionary(x => x.Groups[1].Value, x => x.Groups[2].Value);
+
+ Assert.IsNotEmpty(javaExceptionsWithParents);
+
+ var javaExceptions = javaExceptionsWithParents.Select(x =>
x.Key).Where(IsIgniteException).ToList();
+
+ Assert.IsNotEmpty(javaExceptions);
+
+ Assert.Multiple(() =>
+ {
+ var dotNetExceptions = typeof(IIgnite).Assembly.GetTypes()
+ .Select(t => t.Name)
+ .Where(x => x.EndsWith("Exception",
StringComparison.Ordinal))
+ .ToImmutableHashSet();
+
+ foreach (var exception in javaExceptions)
+ {
+ Assert.IsTrue(dotNetExceptions.Contains(exception), "No
.NET equivalent for Java exception: " + exception);
+ }
+ });
+
+ bool IsIgniteException(string? ex) =>
+ ex != null && (ex == "IgniteException" ||
IsIgniteException(javaExceptionsWithParents.GetValueOrDefault(ex)));
+ }
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
index 40b4845187..07d5b145c0 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServer.cs
@@ -387,8 +387,8 @@ namespace Apache.Ignite.Tests
using var errWriter = new PooledArrayBufferWriter();
var w = new MessagePackWriter(errWriter);
w.Write(Guid.Empty);
- w.Write(65537);
- w.Write("ErrCls: ");
+ w.Write(262147);
+ w.Write("org.foo.bar.BazException");
w.Write(Err);
w.WriteNil(); // Stack trace.
w.Flush();
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServerTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServerTests.cs
index 8e85ec09da..2e0bb0f4a6 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServerTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/FakeServerTests.cs
@@ -17,6 +17,7 @@
namespace Apache.Ignite.Tests
{
+ using System;
using System.Threading.Tasks;
using NUnit.Framework;
@@ -41,8 +42,11 @@ namespace Apache.Ignite.Tests
using var server = new FakeServer();
using var client = await server.ConnectClientAsync();
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
await client.Tables.GetTableAsync("t"));
- Assert.AreEqual("ErrCls: : Err! (65537,
00000000-0000-0000-0000-000000000000)", ex!.Message);
+ var ex = Assert.ThrowsAsync<IgniteException>(async () => await
client.Tables.GetTableAsync("t"));
+ Assert.AreEqual("Err!", ex!.Message);
+ Assert.AreEqual("org.foo.bar.BazException",
ex.InnerException!.Message);
+ Assert.AreEqual(Guid.Empty, ex.TraceId);
+ Assert.AreEqual(ErrorGroups.Sql.QueryInvalid, ex.Code);
}
[Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/ProjectFilesTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/ProjectFilesTests.cs
index 09208a3ef2..ea74d699e5 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/ProjectFilesTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/ProjectFilesTests.cs
@@ -61,6 +61,7 @@ namespace Apache.Ignite.Tests
{
if (file.Contains(InternalDir, StringComparison.Ordinal) ||
file.Contains(".Tests", StringComparison.Ordinal) ||
+ file.Contains(".Internal.", StringComparison.Ordinal) ||
file.Contains(".Benchmarks", StringComparison.Ordinal) ||
file.EndsWith("RetryLimitPolicy.cs",
StringComparison.Ordinal) ||
file.EndsWith("Exception.cs", StringComparison.Ordinal))
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/RetryPolicyTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/RetryPolicyTests.cs
index b1febfe124..aca74abafd 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/RetryPolicyTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/RetryPolicyTests.cs
@@ -54,7 +54,7 @@ namespace Apache.Ignite.Tests
using var server = new FakeServer(reqId => reqId % 2 == 0);
using var client = await server.ConnectClientAsync(cfg);
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
await client.Tables.GetTableAsync("bad-table"));
+ var ex = Assert.ThrowsAsync<IgniteException>(async () => await
client.Tables.GetTableAsync("bad-table"));
StringAssert.Contains(FakeServer.Err, ex!.Message);
}
@@ -69,7 +69,7 @@ namespace Apache.Ignite.Tests
var tx = await client.Transactions.BeginAsync();
- Assert.ThrowsAsync<IgniteClientException>(async () => await
tx.CommitAsync());
+ Assert.ThrowsAsync<IgniteClientConnectionException>(async () =>
await tx.CommitAsync());
Assert.IsEmpty(testRetryPolicy.Invocations);
}
@@ -86,7 +86,7 @@ namespace Apache.Ignite.Tests
await client.Tables.GetTablesAsync();
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
await client.Tables.GetTablesAsync());
+ var ex = Assert.ThrowsAsync<IgniteClientConnectionException>(async
() => await client.Tables.GetTablesAsync());
Assert.AreEqual("Operation failed after 5 retries, examine
InnerException for details.", ex!.Message);
}
@@ -98,7 +98,7 @@ namespace Apache.Ignite.Tests
await client.Tables.GetTablesAsync();
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
await client.Tables.GetTablesAsync());
+ var ex = Assert.ThrowsAsync<IgniteClientConnectionException>(async
() => await client.Tables.GetTablesAsync());
Assert.AreEqual("Operation failed after 16 retries, examine
InnerException for details.", ex!.Message);
}
@@ -130,7 +130,7 @@ namespace Apache.Ignite.Tests
await client.Tables.GetTablesAsync();
- Assert.ThrowsAsync<IgniteClientException>(async () => await
client.Tables.GetTablesAsync());
+ Assert.ThrowsAsync<IgniteClientConnectionException>(async () =>
await client.Tables.GetTablesAsync());
}
[Test]
@@ -146,7 +146,7 @@ namespace Apache.Ignite.Tests
await client.Tables.GetTablesAsync();
- Assert.ThrowsAsync<IgniteClientException>(async () => await
client.Tables.GetTablesAsync());
+ Assert.ThrowsAsync<IgniteClientConnectionException>(async () =>
await client.Tables.GetTablesAsync());
}
[Test]
@@ -210,7 +210,7 @@ namespace Apache.Ignite.Tests
var table = await
client.Tables.GetTableAsync(FakeServer.ExistingTableName);
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
await table!.RecordBinaryView.UpsertAsync(tx, new IgniteTuple()));
+ var ex = Assert.ThrowsAsync<IgniteClientConnectionException>(async
() => await table!.RecordBinaryView.UpsertAsync(tx, new IgniteTuple()));
StringAssert.StartsWith("Socket is closed due to an error",
ex!.Message);
}
@@ -244,7 +244,7 @@ namespace Apache.Ignite.Tests
using var client = await server.ConnectClientAsync(cfg);
var table = await
client.Tables.GetTableAsync(FakeServer.ExistingTableName);
- Assert.ThrowsAsync<IgniteClientException>(async () => await
table!.RecordBinaryView.UpsertAsync(null, new IgniteTuple()));
+ Assert.ThrowsAsync<IgniteClientConnectionException>(async () =>
await table!.RecordBinaryView.UpsertAsync(null, new IgniteTuple()));
}
private class TestRetryPolicy : RetryLimitPolicy
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
index 5d10eadc16..4517a52ae3 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Sql/SqlTests.cs
@@ -240,10 +240,49 @@ namespace Apache.Ignite.Tests.Sql
[Test]
public void TestInvalidSqlThrowsException()
{
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
await Client.Sql.ExecuteAsync(null, "select x from bad"));
+ var ex = Assert.ThrowsAsync<IgniteException>(async () => await
Client.Sql.ExecuteAsync(null, "select x from bad"));
StringAssert.Contains("From line 1, column 15 to line 1, column
17: Object 'BAD' not found", ex!.Message);
}
+ [Test]
+ public void TestCreateTableExistsThrowsException()
+ {
+ var ex = Assert.ThrowsAsync<TableAlreadyExistsException>(
+ async () => await Client.Sql.ExecuteAsync(null, "CREATE TABLE
TEST(ID INT PRIMARY KEY)"));
+
+ StringAssert.EndsWith("Table already exists [name=PUBLIC.TEST]",
ex!.Message);
+ StringAssert.StartsWith("IGN-TBL-1", ex.Message);
+ StringAssert.StartsWith("IGN-TBL-1", ex.CodeAsString);
+ StringAssert.StartsWith("TBL", ex.GroupName);
+ Assert.AreEqual(ErrorGroups.Table.TableAlreadyExists, ex.Code);
+ }
+
+ [Test]
+ public void TestAlterTableNotFoundThrowsException()
+ {
+ var ex = Assert.ThrowsAsync<TableNotFoundException>(
+ async () => await Client.Sql.ExecuteAsync(null, "ALTER TABLE
NOT_EXISTS_TABLE ADD COLUMN VAL1 VARCHAR"));
+
+ StringAssert.EndsWith("Table does not exist
[name=PUBLIC.NOT_EXISTS_TABLE]", ex!.Message);
+ StringAssert.StartsWith("IGN-TBL-2", ex.Message);
+ StringAssert.StartsWith("IGN-TBL-2", ex.CodeAsString);
+ StringAssert.StartsWith("TBL", ex.GroupName);
+ Assert.AreEqual(ErrorGroups.Table.TableNotFound, ex.Code);
+ }
+
+ [Test]
+ public void TestAlterTableColumnExistsThrowsException()
+ {
+ var ex = Assert.ThrowsAsync<ColumnAlreadyExistsException>(
+ async () => await Client.Sql.ExecuteAsync(null, "ALTER TABLE
TEST ADD COLUMN ID INT"));
+
+ StringAssert.EndsWith("Column already exists [name=ID]",
ex!.Message);
+ StringAssert.StartsWith("IGN-TBL-3", ex.Message);
+ StringAssert.StartsWith("IGN-TBL-3", ex.CodeAsString);
+ StringAssert.StartsWith("TBL", ex.GroupName);
+ Assert.AreEqual(ErrorGroups.Table.ColumnAlreadyExists, ex.Code);
+ }
+
[Test]
public async Task TestStatementProperties()
{
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/StringExtensions.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/StringExtensions.cs
new file mode 100644
index 0000000000..10ff0c1a8c
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/StringExtensions.cs
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Tests
+{
+ using System.Linq;
+
+ /// <summary>
+ /// <see cref="string"/> extensions.
+ /// </summary>
+ public static class StringExtensions
+ {
+ public static string SnakeToCamelCase(this string str) =>
+ string.Concat(str.Split('_').Select(x => x[..1].ToUpperInvariant()
+ x[1..].ToLowerInvariant()));
+ }
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/IgniteTupleTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/IgniteTupleTests.cs
index 50756b94c1..a2eb5f6157 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/IgniteTupleTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/IgniteTupleTests.cs
@@ -70,19 +70,19 @@ namespace Apache.Ignite.Tests.Table
{
var tuple = new IgniteTuple { ["Foo"] = 1 };
- var ex = Assert.Throws<IgniteClientException>(() =>
tuple.GetOrdinal(string.Empty));
+ var ex = Assert.Throws<ArgumentException>(() =>
tuple.GetOrdinal(string.Empty));
Assert.AreEqual("Column name can not be null or empty.",
ex!.Message);
- ex = Assert.Throws<IgniteClientException>(() =>
tuple.GetOrdinal(null!));
+ ex = Assert.Throws<ArgumentException>(() =>
tuple.GetOrdinal(null!));
Assert.AreEqual("Column name can not be null or empty.",
ex!.Message);
- ex = Assert.Throws<IgniteClientException>(() =>
+ ex = Assert.Throws<ArgumentException>(() =>
{
var unused = tuple[string.Empty];
});
Assert.AreEqual("Column name can not be null or empty.",
ex!.Message);
- ex = Assert.Throws<IgniteClientException>(() =>
+ ex = Assert.Throws<ArgumentException>(() =>
{
var unused = tuple[null!];
});
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
index 816b5adc6d..1ff147bb5e 100644
---
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
+++
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewBinaryTests.cs
@@ -76,7 +76,7 @@ namespace Apache.Ignite.Tests.Table
[Test]
public void TestUpsertEmptyTupleThrowsException()
{
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
await TupleView.UpsertAsync(null, new IgniteTuple()));
+ var ex = Assert.ThrowsAsync<IgniteException>(async () => await
TupleView.UpsertAsync(null, new IgniteTuple()));
StringAssert.Contains(
"Missed key column: KEY",
diff --git
a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
index a11b046f4e..f3281ac82d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Table/RecordViewPocoTests.cs
@@ -69,7 +69,7 @@ namespace Apache.Ignite.Tests.Table
{
var pocoView = Table.GetRecordView<object>();
- var ex = Assert.ThrowsAsync<IgniteClientException>(async () =>
await pocoView.UpsertAsync(null, new object()));
+ var ex = Assert.ThrowsAsync<IgniteException>(async () => await
pocoView.UpsertAsync(null, new object()));
StringAssert.Contains("Missed key column: KEY", ex!.Message);
}
diff --git a/modules/platforms/dotnet/Apache.Ignite.sln
b/modules/platforms/dotnet/Apache.Ignite.sln
index 5f96ee7fce..38bd65d5d8 100644
--- a/modules/platforms/dotnet/Apache.Ignite.sln
+++ b/modules/platforms/dotnet/Apache.Ignite.sln
@@ -20,6 +20,8 @@ EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Apache.Ignite.Benchmarks",
"Apache.Ignite.Benchmarks\Apache.Ignite.Benchmarks.csproj",
"{9E63F965-535F-4AF2-A380-0780EAC6390E}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") =
"Apache.Ignite.Internal.Generators",
"Apache.Ignite.Internal.Generators\Apache.Ignite.Internal.Generators.csproj",
"{7BC74F53-FF5F-4CBF-A559-9E4BE3A63A0F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -41,5 +43,9 @@ Global
{9E63F965-535F-4AF2-A380-0780EAC6390E}.Debug|Any CPU.Build.0 =
Debug|Any CPU
{9E63F965-535F-4AF2-A380-0780EAC6390E}.Release|Any
CPU.ActiveCfg = Release|Any CPU
{9E63F965-535F-4AF2-A380-0780EAC6390E}.Release|Any CPU.Build.0
= Release|Any CPU
+ {7BC74F53-FF5F-4CBF-A559-9E4BE3A63A0F}.Debug|Any CPU.ActiveCfg
= Debug|Any CPU
+ {7BC74F53-FF5F-4CBF-A559-9E4BE3A63A0F}.Debug|Any CPU.Build.0 =
Debug|Any CPU
+ {7BC74F53-FF5F-4CBF-A559-9E4BE3A63A0F}.Release|Any
CPU.ActiveCfg = Release|Any CPU
+ {7BC74F53-FF5F-4CBF-A559-9E4BE3A63A0F}.Release|Any CPU.Build.0
= Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
b/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
index 6f6466b9ea..f61653cec9 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite/Apache.Ignite.csproj
@@ -24,6 +24,8 @@
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>Apache.Ignite.snk</AssemblyOriginatorKeyFile>
<IsPackable>true</IsPackable>
+ <EmitCompilerGeneratedFiles>false</EmitCompilerGeneratedFiles>
+
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
@@ -39,4 +41,9 @@
</AssemblyAttribute>
</ItemGroup>
+ <ItemGroup>
+ <ProjectReference
Include="..\Apache.Ignite.Internal.Generators\Apache.Ignite.Internal.Generators.csproj"
+ OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
SetTargetFramework="TargetFramework=netstandard2.0" />
+ </ItemGroup>
+
</Project>
diff --git a/modules/platforms/dotnet/Apache.Ignite/ErrorGroups.cs
b/modules/platforms/dotnet/Apache.Ignite/ErrorGroups.cs
new file mode 100644
index 0000000000..550101a819
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite/ErrorGroups.cs
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite
+{
+ using System.Diagnostics.CodeAnalysis;
+
+ /// <summary>
+ /// Represents a concept of error group. Error group defines a collection
of errors that belong to a single semantic component.
+ /// Each group can be identified by a name and an integer number that both
must be unique across all error groups.
+ /// </summary>
+ [SuppressMessage(
+ "Microsoft.Design",
+ "CA1034:NestedTypesShouldNotBeVisible",
+ Justification = "Reviewed.")]
+ [SuppressMessage(
+ "Microsoft.Naming",
+ "CA1724:TypeNamesShouldNotMatchNamespaces",
+ Justification = "Types are nested, there is no conflict.")]
+ public static partial class ErrorGroups // Nested classes are generated by
ErrorGroupGenerator source generator.
+ {
+ /// <summary>
+ /// Ignite error prefix.
+ /// </summary>
+ public const string ErrPrefix = "IGN-";
+
+ /// <summary>
+ /// Unknown error group name.
+ /// </summary>
+ public const string UnknownGroupName = "UNKNOWN";
+
+ /// <summary>
+ /// Gets error code extracted from the given full error code.
+ /// </summary>
+ /// <param name="fullCode">Full error code.</param>
+ /// <returns>Error code.</returns>
+ public static int GetErrorCode(int fullCode) => fullCode & 0xFFFF;
+
+ /// <summary>
+ /// Returns group code extracted from the given full error code.
+ /// </summary>
+ /// <param name="fullCode">Full error code.</param>
+ /// <returns>Group code.</returns>
+ public static int GetGroupCode(int fullCode) => fullCode >> 16;
+
+ /// <summary>
+ /// Gets the full error code from group and error codes.
+ /// </summary>
+ /// <param name="groupCode">Group code.</param>
+ /// <param name="errorCode">Error code.</param>
+ /// <returns>Combined code.</returns>
+ public static int GetFullCode(int groupCode, int errorCode) =>
(groupCode << 16) | (errorCode & 0xFFFF);
+ }
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/IgniteClientConnectionException.cs
b/modules/platforms/dotnet/Apache.Ignite/IgniteClientConnectionException.cs
new file mode 100644
index 0000000000..eacbc76c12
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite/IgniteClientConnectionException.cs
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Runtime.Serialization;
+
+ /// <summary>
+ /// Ignite thin client exception.
+ /// </summary>
+ [Serializable]
+ [SuppressMessage(
+ "Microsoft.Design",
+ "CA1032:ImplementStandardExceptionConstructors",
+ Justification="Ignite exceptions use a special constructor.")]
+ public class IgniteClientConnectionException : IgniteClientException
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see
cref="IgniteClientConnectionException"/> class.
+ /// </summary>
+ /// <param name="code">Code.</param>
+ /// <param name="message">Message.</param>
+ /// <param name="innerException">Inner exception.</param>
+ public IgniteClientConnectionException(int code, string message,
Exception? innerException = null)
+ : base(Guid.NewGuid(), code, message, innerException)
+ {
+ // No-op.
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see
cref="IgniteClientConnectionException"/> class.
+ /// </summary>
+ /// <param name="traceId">Trace id.</param>
+ /// <param name="code">Code.</param>
+ /// <param name="message">Message.</param>
+ /// <param name="innerException">Inner exception.</param>
+ public IgniteClientConnectionException(Guid traceId, int code, string
message, Exception? innerException = null)
+ : base(traceId, code, message, innerException)
+ {
+ // No-op.
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see
cref="IgniteClientConnectionException"/> class.
+ /// </summary>
+ /// <param name="serializationInfo">Serialization information.</param>
+ /// <param name="streamingContext">Streaming context.</param>
+ protected IgniteClientConnectionException(SerializationInfo
serializationInfo, StreamingContext streamingContext)
+ : base(serializationInfo, streamingContext)
+ {
+ // No-op.
+ }
+ }
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite/IgniteClientException.cs
b/modules/platforms/dotnet/Apache.Ignite/IgniteClientException.cs
index 5c8205afc0..3fb7fe6f32 100644
--- a/modules/platforms/dotnet/Apache.Ignite/IgniteClientException.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/IgniteClientException.cs
@@ -18,87 +18,53 @@
namespace Apache.Ignite
{
using System;
+ using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
/// <summary>
/// Ignite thin client exception.
/// </summary>
[Serializable]
- public class IgniteClientException : Exception
+ [SuppressMessage(
+ "Microsoft.Design",
+ "CA1032:ImplementStandardExceptionConstructors",
+ Justification="Ignite exceptions use a special constructor.")]
+ public class IgniteClientException : IgniteException
{
- /** Error code field. */
- private const string ErrorCodeField = "StatusCode";
-
/// <summary>
/// Initializes a new instance of the <see
cref="IgniteClientException"/> class.
/// </summary>
- public IgniteClientException()
- {
- // No-op.
- }
-
- /// <summary>
- /// Initializes a new instance of the <see
cref="IgniteClientException" /> class.
- /// </summary>
- /// <param name="message">The message that describes the error.</param>
- public IgniteClientException(string message)
- : base(message)
+ /// <param name="code">Code.</param>
+ /// <param name="message">Message.</param>
+ /// <param name="innerException">Inner exception.</param>
+ public IgniteClientException(int code, string message, Exception?
innerException = null)
+ : base(Guid.NewGuid(), code, message, innerException)
{
// No-op.
}
/// <summary>
- /// Initializes a new instance of the <see
cref="IgniteClientException" /> class.
+ /// Initializes a new instance of the <see
cref="IgniteClientException"/> class.
/// </summary>
- /// <param name="message">The message.</param>
- /// <param name="cause">The cause.</param>
- public IgniteClientException(string message, Exception? cause)
- : base(message, cause)
+ /// <param name="traceId">Trace id.</param>
+ /// <param name="code">Code.</param>
+ /// <param name="message">Message.</param>
+ /// <param name="innerException">Inner exception.</param>
+ public IgniteClientException(Guid traceId, int code, string message,
Exception? innerException = null)
+ : base(traceId, code, message, innerException)
{
// No-op.
}
- /// <summary>
- /// Initializes a new instance of the <see
cref="IgniteClientException" /> class.
- /// </summary>
- /// <param name="message">The message.</param>
- /// <param name="cause">The cause.</param>
- /// <param name="statusCode">The error code.</param>
- public IgniteClientException(string message, Exception? cause, int
statusCode)
- : base(message, cause)
- {
- ErrorCode = statusCode;
- }
-
/// <summary>
/// Initializes a new instance of the <see
cref="IgniteClientException"/> class.
/// </summary>
- /// <param name="info">Serialization information.</param>
- /// <param name="ctx">Streaming context.</param>
- protected IgniteClientException(SerializationInfo info,
StreamingContext ctx)
- : base(info, ctx)
+ /// <param name="serializationInfo">Serialization information.</param>
+ /// <param name="streamingContext">Streaming context.</param>
+ protected IgniteClientException(SerializationInfo serializationInfo,
StreamingContext streamingContext)
+ : base(serializationInfo, streamingContext)
{
- ErrorCode = info.GetInt32(ErrorCodeField);
- }
-
- /// <summary>
- /// Gets the error code.
- /// </summary>
- public int ErrorCode { get; }
-
- /// <summary>
- /// When overridden in a derived class, sets the <see
cref="SerializationInfo" />
- /// with information about the exception.
- /// </summary>
- /// <param name="info">The <see cref="SerializationInfo" /> that holds
the serialized object data
- /// about the exception being thrown.</param>
- /// <param name="context">The <see cref="StreamingContext" /> that
contains contextual information
- /// about the source or destination.</param>
- public override void GetObjectData(SerializationInfo info,
StreamingContext context)
- {
- base.GetObjectData(info, context);
-
- info.AddValue(ErrorCodeField, ErrorCode);
+ // No-op.
}
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/IgniteException.cs
b/modules/platforms/dotnet/Apache.Ignite/IgniteException.cs
new file mode 100644
index 0000000000..fb8136018b
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite/IgniteException.cs
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Runtime.Serialization;
+ using Internal.Common;
+
+ /// <summary>
+ /// Ignite exception.
+ /// </summary>
+ [Serializable]
+ [SuppressMessage(
+ "Microsoft.Design",
+ "CA1032:ImplementStandardExceptionConstructors",
+ Justification="Ignite exceptions use a special constructor.")]
+ public class IgniteException : Exception
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IgniteException"/>
class.
+ /// </summary>
+ /// <param name="traceId">Trace id.</param>
+ /// <param name="code">Code.</param>
+ /// <param name="message">Message.</param>
+ /// <param name="innerException">Inner exception.</param>
+ public IgniteException(Guid traceId, int code, string? message,
Exception? innerException = null)
+ : base(message, innerException)
+ {
+ TraceId = traceId;
+ Code = code;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IgniteException"/>
class.
+ /// </summary>
+ /// <param name="serializationInfo">Serialization information.</param>
+ /// <param name="streamingContext">Streaming context.</param>
+ protected IgniteException(SerializationInfo serializationInfo,
StreamingContext streamingContext)
+ : base(serializationInfo, streamingContext)
+ {
+ IgniteArgumentCheck.NotNull(serializationInfo,
nameof(serializationInfo));
+
+ TraceId = (Guid)serializationInfo.GetValue(nameof(TraceId),
typeof(Guid));
+ Code = serializationInfo.GetInt32(nameof(Code));
+ }
+
+ /// <summary>
+ /// Gets the group name.
+ /// </summary>
+ public string GroupName =>
ErrorGroups.GetGroupName(ErrorGroups.GetGroupCode(Code));
+
+ /// <summary>
+ /// Gets the full exception code.
+ /// </summary>
+ public int Code { get; }
+
+ /// <summary>
+ /// Gets the trace id (correlation id).
+ /// </summary>
+ public Guid TraceId { get; }
+
+ /// <summary>
+ /// Gets the error code.
+ /// </summary>
+ public int ErrorCode => ErrorGroups.GetErrorCode(Code);
+
+ /// <summary>
+ /// Gets the code as string.
+ /// </summary>
+ public string CodeAsString => ErrorGroups.ErrPrefix + GroupName + '-'
+ ErrorCode;
+
+ /// <inheritdoc />
+ public override void GetObjectData(SerializationInfo info,
StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+
+ info.AddValue(nameof(Code), Code);
+ info.AddValue(nameof(TraceId), TraceId);
+ }
+ }
+}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientFailoverSocket.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientFailoverSocket.cs
index 8f0849fe39..decaed76b8 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientFailoverSocket.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientFailoverSocket.cs
@@ -71,8 +71,8 @@ namespace Apache.Ignite.Internal
if (configuration.Endpoints.Count == 0)
{
throw new IgniteClientException(
- $"Invalid {nameof(IgniteClientConfiguration)}: " +
- $"{nameof(IgniteClientConfiguration.Endpoints)} is empty.
Nowhere to connect.");
+ ErrorGroups.Client.Configuration,
+ $"Invalid {nameof(IgniteClientConfiguration)}:
{nameof(IgniteClientConfiguration.Endpoints)} is empty. Nowhere to connect.");
}
_logger = configuration.Logger.GetLogger(GetType());
@@ -124,7 +124,7 @@ namespace Apache.Ignite.Internal
if (tx.FailoverSocket != this)
{
- throw new IgniteClientException("Specified transaction belongs
to a different IgniteClient instance.");
+ throw new IgniteClientException(ErrorGroups.Client.Connection,
"Specified transaction belongs to a different IgniteClient instance.");
}
// Use tx-specific socket without retry and failover.
@@ -160,7 +160,7 @@ namespace Apache.Ignite.Internal
{
if (tx.FailoverSocket != this)
{
- throw new IgniteClientException("Specified transaction
belongs to a different IgniteClient instance.");
+ throw new
IgniteClientException(ErrorGroups.Client.Connection, "Specified transaction
belongs to a different IgniteClient instance.");
}
// Use tx-specific socket without retry and failover.
@@ -525,7 +525,8 @@ namespace Apache.Ignite.Internal
errors.Add(exception);
var inner = new AggregateException(errors);
- throw new IgniteClientException(
+ throw new IgniteClientConnectionException(
+ ErrorGroups.Client.Connection,
$"Operation failed after {attempt} retries, examine
InnerException for details.",
inner);
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
index 78cb555984..c4d543b9ad 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
@@ -177,7 +177,10 @@ namespace Apache.Ignite.Internal
if (ex != null)
{
- throw new IgniteClientException("Socket is closed due to an
error, examine inner exception for details.", ex);
+ throw new IgniteClientConnectionException(
+ ErrorGroups.Client.Connection,
+ "Socket is closed due to an error, examine inner exception
for details.",
+ ex);
}
if (_disposeTokenSource.IsCancellationRequested)
@@ -251,8 +254,9 @@ namespace Apache.Ignite.Internal
{
if (responseMagic[i] != ProtoCommon.MagicBytes[i])
{
- throw new IgniteClientException("Invalid magic bytes
returned from the server: " +
-
BitConverter.ToString(responseMagic));
+ throw new IgniteClientConnectionException(
+ ErrorGroups.Client.Protocol,
+ "Invalid magic bytes returned from the server: " +
BitConverter.ToString(responseMagic));
}
}
}
@@ -268,7 +272,7 @@ namespace Apache.Ignite.Internal
if (serverVer != CurrentProtocolVersion)
{
- throw new IgniteClientException("Unexpected server version: "
+ serverVer);
+ throw new
IgniteClientConnectionException(ErrorGroups.Client.Protocol, "Unexpected server
version: " + serverVer);
}
var exception = ReadError(ref reader);
@@ -291,20 +295,19 @@ namespace Apache.Ignite.Internal
new ClusterNode(clusterNodeId, clusterNodeName, endPoint));
}
- private static IgniteClientException? ReadError(ref MessagePackReader
reader)
+ private static IgniteException? ReadError(ref MessagePackReader reader)
{
if (reader.TryReadNil())
{
return null;
}
- // TODO: IGNITE-17390 .NET: Thin 3.0: Unified exception handling -
reconstruct correct exception.
Guid traceId = reader.TryReadNil() ? Guid.NewGuid() :
reader.ReadGuid();
int code = reader.TryReadNil() ? 65537 : reader.ReadInt32();
string className = reader.ReadString();
string? message = reader.ReadString();
- return new IgniteClientException($"{className}: {message} ({code},
{traceId})", null, code);
+ return ExceptionMapper.GetException(traceId, code, className,
message);
}
private static async ValueTask<PooledBuffer> ReadResponseAsync(
@@ -358,7 +361,8 @@ namespace Apache.Ignite.Internal
if (res == 0)
{
// Disconnected.
- throw new IgniteClientException(
+ throw new IgniteClientConnectionException(
+ ErrorGroups.Client.Protocol,
"Connection lost (failed to read data from socket)",
new SocketException((int)
SocketError.ConnectionAborted));
}
@@ -406,8 +410,8 @@ namespace Apache.Ignite.Internal
if (configuredInterval <= TimeSpan.Zero)
{
throw new IgniteClientException(
-
$"{nameof(IgniteClientConfiguration)}.{nameof(IgniteClientConfiguration.HeartbeatInterval)}
" +
- "should be greater than zero.");
+ ErrorGroups.Client.Configuration,
+
$"{nameof(IgniteClientConfiguration)}.{nameof(IgniteClientConfiguration.HeartbeatInterval)}
should be greater than zero.");
}
if (serverIdleTimeout <= TimeSpan.Zero)
@@ -513,7 +517,7 @@ namespace Apache.Ignite.Internal
const string message = "Exception while reading from socket.
Connection closed.";
_logger?.Error(message, e);
- Dispose(new IgniteClientException(message, e));
+ Dispose(new
IgniteClientConnectionException(ErrorGroups.Client.Connection, message, e));
}
}
@@ -535,7 +539,7 @@ namespace Apache.Ignite.Internal
{
var message = $"Unexpected response ID ({requestId}) received
from the server, closing the socket.";
_logger?.Error(message);
- Dispose(new IgniteClientException(message));
+ Dispose(new
IgniteClientConnectionException(ErrorGroups.Client.Protocol, message));
return;
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
index c1db86a743..a019026ae3 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Compute/Compute.cs
@@ -190,7 +190,7 @@ namespace Apache.Ignite.Internal.Compute
_tableCache.TryRemove(tableName, out _);
- throw new IgniteClientException($"Table '{tableName}' does not
exist.");
+ throw new
IgniteClientException(ErrorGroups.Client.TableIdNotFound, $"Table '{tableName}'
does not exist.");
}
private async Task<T> ExecuteColocatedAsync<T, TKey>(
@@ -213,14 +213,13 @@ namespace Apache.Ignite.Internal.Compute
using var bufferWriter = Write(table, schema);
- // TODO: IGNITE-17390 replace magic ErrorCode number with
constant.
try
{
using var res = await
_socket.DoOutInOpAsync(ClientOp.ComputeExecuteColocated,
bufferWriter).ConfigureAwait(false);
return Read(res);
}
- catch (IgniteClientException e) when (e.ErrorCode == 196612)
+ catch (IgniteException e) when (e.Code ==
ErrorGroups.Client.TableIdNotFound)
{
// Table was dropped - remove from cache.
// Try again in case a new table with the same name exists.
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Endpoint.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Endpoint.cs
index 1afd53b51b..47357434f6 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Endpoint.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Endpoint.cs
@@ -82,6 +82,7 @@ namespace Apache.Ignite.Internal
if (string.IsNullOrWhiteSpace(endpoint))
{
throw new IgniteClientException(
+ ErrorGroups.Client.Configuration,
"IgniteClientConfiguration.Endpoints[...] can't be null or
whitespace.");
}
@@ -110,13 +111,16 @@ namespace Apache.Ignite.Internal
if (maxPort < minPort)
{
throw new IgniteClientException(
+ ErrorGroups.Client.Configuration,
"Invalid format of IgniteClientConfiguration.Endpoint,
port range is empty: " + endpoint);
}
return new Endpoint(host, minPort, maxPort - minPort);
}
- throw new IgniteClientException("Unrecognized format of
IgniteClientConfiguration.Endpoint: " + endpoint);
+ throw new IgniteClientException(
+ ErrorGroups.Client.Configuration,
+ "Unrecognized format of IgniteClientConfiguration.Endpoint: "
+ endpoint);
}
/// <summary>
@@ -132,6 +136,7 @@ namespace Apache.Ignite.Internal
}
throw new IgniteClientException(
+ ErrorGroups.Client.Configuration,
"Unrecognized format of IgniteClientConfiguration.Endpoint,
failed to parse port: " + endpoint);
}
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
index 781e51d58b..73a91bf5bb 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs
@@ -335,7 +335,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
case ClientDataType.Decimal:
default:
// TODO: Support all types (IGNITE-15431).
- throw new IgniteClientException("Unsupported type: " +
colType);
+ throw new
IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " +
colType);
}
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
index 7ea6189474..04adff9878 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs
@@ -215,7 +215,7 @@ namespace Apache.Ignite.Internal.Proto.BinaryTuple
ClientDataType.String => GetString(index),
// TODO: Support all types (IGNITE-15431).
- _ => throw new IgniteClientException("Unsupported type: " +
columnType)
+ _ => throw new
IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " +
columnType)
};
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackReaderExtensions.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackReaderExtensions.cs
index 7f51ebad72..5877fc9e0a 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackReaderExtensions.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackReaderExtensions.cs
@@ -63,7 +63,7 @@ namespace Apache.Ignite.Internal.Proto
return reader.ReadString();
default:
- throw new IgniteClientException("Unsupported type: " +
type);
+ throw new
IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " + type);
}
}
@@ -195,12 +195,14 @@ namespace Apache.Ignite.Internal.Proto
if (hdr.TypeCode != (int)expectedType)
{
throw new IgniteClientException(
+ ErrorGroups.Client.Protocol,
$"Expected {expectedType} extension ({(int)expectedType}),
but got {hdr.TypeCode}.");
}
if (expectedLength != null && hdr.Length != expectedLength)
{
throw new IgniteClientException(
+ ErrorGroups.Client.Protocol,
$"Expected {expectedLength} bytes for {expectedType}
extension, but got {hdr.Length}.");
}
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
index b4f7792636..71ebdce32c 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Proto/MessagePackWriterExtensions.cs
@@ -142,7 +142,7 @@ namespace Apache.Ignite.Internal.Proto
return;
}
- throw new IgniteClientException("Unsupported type: " +
obj.GetType());
+ throw new IgniteClientException(ErrorGroups.Client.Protocol,
"Unsupported type: " + obj.GetType());
}
/// <summary>
@@ -220,7 +220,7 @@ namespace Apache.Ignite.Internal.Proto
return;
}
- throw new IgniteClientException("Unsupported type: " +
obj.GetType());
+ throw new IgniteClientException(ErrorGroups.Client.Protocol,
"Unsupported type: " + obj.GetType());
}
/// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
index dddc0cead4..18caa865eb 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Sql/ResultSet.cs
@@ -19,6 +19,7 @@ namespace Apache.Ignite.Internal.Sql
{
using System;
using System.Collections.Generic;
+ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
@@ -349,10 +350,7 @@ namespace Apache.Ignite.Internal.Sql
{
var resourceId = _resourceId;
- if (resourceId == null)
- {
- throw new IgniteClientException("Query has no result set.");
- }
+ Debug.Assert(resourceId != null, "resourceId != null");
if (_resourceClosed)
{
@@ -367,12 +365,12 @@ namespace Apache.Ignite.Internal.Sql
{
if (!HasRowSet)
{
- throw new IgniteClientException("Query has no result set.");
+ throw new
IgniteClientException(ErrorGroups.Sql.QueryNoResultSet, "Query has no result
set.");
}
if (_iterated)
{
- throw new IgniteClientException("Query result set can not be
iterated more than once.");
+ throw new IgniteClientException(ErrorGroups.Sql.CursorClosed,
"Query result set can not be iterated more than once.");
}
_iterated = true;
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
index b461a71ab9..b107a5b6b5 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/BinaryTupleMethods.cs
@@ -84,7 +84,7 @@ namespace Apache.Ignite.Internal.Table.Serialization
public static MethodInfo GetWriteMethod(Type valueType) =>
WriteMethods.TryGetValue(valueType, out var method)
? method
- : throw new IgniteClientException("Unsupported type: " +
valueType);
+ : throw new
IgniteClientException(ErrorGroups.Client.Configuration, "Unsupported type: " +
valueType);
/// <summary>
/// Gets the read method.
@@ -94,6 +94,6 @@ namespace Apache.Ignite.Internal.Table.Serialization
public static MethodInfo GetReadMethod(Type valueType) =>
ReadMethods.TryGetValue(valueType, out var method)
? method
- : throw new IgniteClientException("Unsupported type: " +
valueType);
+ : throw new
IgniteClientException(ErrorGroups.Client.Configuration, "Unsupported type: " +
valueType);
}
}
diff --git
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
index 8fc4f257bc..b0e29bee1b 100644
---
a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
+++
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Serialization/ObjectSerializerHandler.cs
@@ -300,9 +300,10 @@ namespace Apache.Ignite.Internal.Table.Serialization
if (fieldType != columnTypePrimary && fieldType !=
columnTypeAlternative)
{
- throw new IgniteClientException(
- $"Can't map field
'{fieldInfo.DeclaringType?.Name}.{fieldInfo.Name}' of type '{fieldType}' " +
- $"to column '{column.Name}' of type '{columnTypePrimary}'
- types do not match.");
+ var message = $"Can't map field
'{fieldInfo.DeclaringType?.Name}.{fieldInfo.Name}' of type '{fieldType}' " +
+ $"to column '{column.Name}' of type
'{columnTypePrimary}' - types do not match.";
+
+ throw new
IgniteClientException(ErrorGroups.Client.Configuration, message);
}
}
}
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
index d40206d0a1..dfa3f90f07 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Table/Table.cs
@@ -182,7 +182,7 @@ namespace Apache.Ignite.Internal.Table
if (schemaCount == 0)
{
- throw new IgniteClientException("Schema not found: " +
version);
+ throw new
IgniteClientException(ErrorGroups.Client.Protocol, "Schema not found: " +
version);
}
Schema last = null!;
diff --git a/modules/platforms/dotnet/Apache.Ignite/Sql/SqlStatement.cs
b/modules/platforms/dotnet/Apache.Ignite/Sql/SqlStatement.cs
index b87890f7c4..0cbb00f922 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Sql/SqlStatement.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Sql/SqlStatement.cs
@@ -70,7 +70,7 @@ namespace Apache.Ignite.Sql
Timeout = timeout ?? DefaultTimeout;
Schema = schema ?? DefaultSchema;
PageSize = pageSize ?? DefaultPageSize;
- Properties = properties == null || properties == EmptyProperties ?
EmptyProperties : new(properties);
+ Properties = properties == null || ReferenceEquals(properties,
EmptyProperties) ? EmptyProperties : new(properties);
}
/// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite/Table/IgniteTuple.cs
b/modules/platforms/dotnet/Apache.Ignite/Table/IgniteTuple.cs
index d2116985c1..b3c37ee6ff 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Table/IgniteTuple.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Table/IgniteTuple.cs
@@ -126,19 +126,19 @@ namespace Apache.Ignite.Table
return IIgniteTuple.GetHashCode(this);
}
- private static string ParseName(string str)
+ private static string ParseName(string name)
{
- if (string.IsNullOrEmpty(str))
+ if (string.IsNullOrEmpty(name))
{
- throw new IgniteClientException("Column name can not be null
or empty.");
+ throw new ArgumentException("Column name can not be null or
empty.");
}
- if (str.Length > 2 && str.StartsWith('"') && str.EndsWith('"'))
+ if (name.Length > 2 && name.StartsWith('"') && name.EndsWith('"'))
{
- return str.Substring(1, str.Length - 2);
+ return name.Substring(1, name.Length - 2);
}
- return str.ToUpperInvariant();
+ return name.ToUpperInvariant();
}
}
}
diff --git a/modules/platforms/dotnet/DEVNOTES.md
b/modules/platforms/dotnet/DEVNOTES.md
index 80fad8db1f..745c2d1f4c 100644
--- a/modules/platforms/dotnet/DEVNOTES.md
+++ b/modules/platforms/dotnet/DEVNOTES.md
@@ -1,5 +1,5 @@
## Prerequisites
-* .NET Core 3.1 SDK
+* .NET 6 SDK
* Java 11 SDK
* Maven 3.6.0+ (for building)
@@ -24,6 +24,7 @@ then run .NET tests with `dotnet test` or `dotnet test
--filter TEST_NAME`. When
* Library project target `netstandard2.1`
* Test projects target `netcoreapp3.1`
+* .NET 6 is required for source generators
See [IEP-78 .NET Thin
Client](https://cwiki.apache.org/confluence/display/IGNITE/IEP-78+.NET+Thin+Client)
for design considerations.
diff --git a/modules/platforms/dotnet/Directory.Build.props
b/modules/platforms/dotnet/Directory.Build.props
index 96f085e39b..c74a3886f2 100644
--- a/modules/platforms/dotnet/Directory.Build.props
+++ b/modules/platforms/dotnet/Directory.Build.props
@@ -18,7 +18,7 @@
<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- <WarningsAsErrors/>
+ <WarningsAsErrors>CS8785</WarningsAsErrors>
<LangVersion>9</LangVersion>
<Nullable>enable</Nullable>
<Version>3.0.0-alpha3</Version>