diff --git a/CHANGELOG.md b/CHANGELOG.md index e7772486..8730a50c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased - Change the default large payload externalization threshold (`LargePayloadStorageOptions.ThresholdBytes`) from 900,000 bytes to 256 KiB (262,144 bytes) +- **BREAKING**: `EntityInstanceId` now throws if its string form exceeds 100 characters (backend limit). Migration: shorten entity name/key; if entities already exist with overlong IDs, use the truncated 100-character value when addressing them. ## v1.25.0-preview.2 diff --git a/src/Abstractions/Entities/EntityInstanceId.cs b/src/Abstractions/Entities/EntityInstanceId.cs index fceaaaee..0b7836d4 100644 --- a/src/Abstractions/Entities/EntityInstanceId.cs +++ b/src/Abstractions/Entities/EntityInstanceId.cs @@ -12,6 +12,8 @@ namespace Microsoft.DurableTask.Entities; [JsonConverter(typeof(EntityInstanceId.JsonConverter))] public readonly record struct EntityInstanceId { + const int MaxInstanceIdLength = 100; + /// /// Initializes a new instance of the struct. /// @@ -28,6 +30,12 @@ public EntityInstanceId(string name, string key) Check.NotNull(key); this.Name = name.ToLowerInvariant(); this.Key = key; + + int length = this.ToString().Length; + if (length > MaxInstanceIdLength) + { + throw new ArgumentException($"entity instance ids may not exceed 100 characters in length, actual length {length}."); + } } /// diff --git a/test/Abstractions.Tests/Entities/EntityInstanceIdTests.cs b/test/Abstractions.Tests/Entities/EntityInstanceIdTests.cs new file mode 100644 index 00000000..b5f4c24b --- /dev/null +++ b/test/Abstractions.Tests/Entities/EntityInstanceIdTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.DurableTask.Entities; + +namespace Microsoft.DurableTask.Abstractions.Tests.Entities; + +public class EntityInstanceIdTests +{ + [Fact] + public void TestValidEntityInstanceId() + { + var entityId = new EntityInstanceId("entity", "key1"); + Assert.Equal("@entity@key1", entityId.ToString()); + } + + [Fact] + public void TestEntityNameLowercased() + { + var entityId = new EntityInstanceId("Entity", "key1"); + Assert.Equal("entity", entityId.Name); + Assert.Equal("@entity@key1", entityId.ToString()); + } + + [Fact] + public void TestEntityNameWithAtSymbolThrows() + { + Assert.Throws(() => new EntityInstanceId("entity@name", "key1")); + } + + [Fact] + public void TestEntityInstanceIdExceedsMaxLengthThrows() + { + string longName = new string('a', 50); + string longKey = new string('b', 51); + Assert.Throws(() => new EntityInstanceId(longName, longKey)); + } + + [Fact] + public void TestEntityInstanceIdEqualsMaxLength() + { + string longName = new string('a', 49); + string longKey = new string('b', 49); + var entityId = new EntityInstanceId(longName, longKey); + Assert.Equal($"@{longName}@{longKey}", entityId.ToString()); + } + + [Fact] + public void TestFromStringValid() + { + var entityId = EntityInstanceId.FromString("@entity@key1"); + Assert.Equal("entity", entityId.Name); + Assert.Equal("key1", entityId.Key); + } + + [Fact] + public void TestFromStringInvalidThrows() + { + Assert.Throws(() => EntityInstanceId.FromString("invalid")); + Assert.Throws(() => EntityInstanceId.FromString("@entitykey1")); + Assert.Throws(() => EntityInstanceId.FromString("@@key1")); + Assert.Throws(() => EntityInstanceId.FromString($"@entity@{new string('a', 100)}")); + } +}