Skip to content

Commit fad7fb3

Browse files
authored
Fix issue with search attribute keyword list type (#529)
Fixes #526
1 parent f99bca3 commit fad7fb3

File tree

6 files changed

+42
-14
lines changed

6 files changed

+42
-14
lines changed

src/Temporalio/Common/SearchAttributeCollection.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class SearchAttributeCollection : IReadOnlyCollection<SearchAttributeKey>
2525
/// <summary>
2626
/// An empty search attribute collection.
2727
/// </summary>
28-
public static readonly SearchAttributeCollection Empty = new(new());
28+
public static readonly SearchAttributeCollection Empty = new();
2929

3030
private static readonly Dictionary<ByteString, IndexedValueType> NameToIndexType =
3131
Enum.GetValues(typeof(IndexedValueType)).Cast<IndexedValueType>().ToDictionary(
@@ -41,9 +41,12 @@ public class SearchAttributeCollection : IReadOnlyCollection<SearchAttributeKey>
4141
/// <summary>
4242
/// Initializes a new instance of the <see cref="SearchAttributeCollection"/> class.
4343
/// </summary>
44-
/// <param name="values">The values that are actually used. Warning, this is not copied.
45-
/// </param>
46-
internal SearchAttributeCollection(SortedDictionary<SearchAttributeKey, object> values) =>
44+
internal SearchAttributeCollection()
45+
: this(new())
46+
{
47+
}
48+
49+
private SearchAttributeCollection(SortedDictionary<SearchAttributeKey, object> values) =>
4750
this.values = values;
4851

4952
/// <summary>
@@ -200,7 +203,7 @@ internal static (object Value, IndexedValueType Type)? PayloadToObject(Payload p
200203
static object JsonElementToObject(JsonElement elem, IndexedValueType valueType) => elem.ValueKind switch
201204
{
202205
JsonValueKind.Array =>
203-
elem.EnumerateArray().Select(j => JsonElementToObject(j, valueType)).ToList(),
206+
elem.EnumerateArray().Select(j => JsonElementToObject(j, valueType)).OfType<string>().ToList(),
204207
JsonValueKind.False or JsonValueKind.True =>
205208
elem.GetBoolean(),
206209
JsonValueKind.Number when valueType == IndexedValueType.Int =>
@@ -369,7 +372,8 @@ private Builder InternalSet(SearchAttributeKey key, object value)
369372
{
370373
values.Remove(existingKey);
371374
}
372-
values[key] = value ?? throw new ArgumentException("Null value", nameof(value));
375+
values[key] = key.NormalizeValue(
376+
value ?? throw new ArgumentException("Null value", nameof(value)));
373377
return this;
374378
}
375379
}

src/Temporalio/Common/SearchAttributeKey.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections.Generic;
5+
using System.Linq;
56
using Temporalio.Api.Enums.V1;
67

78
namespace Temporalio.Common
@@ -166,6 +167,24 @@ public int CompareTo(SearchAttributeKey? other)
166167
/// <returns>Whether equal.</returns>
167168
public bool Equals(SearchAttributeKey? other) =>
168169
other != null && other.Name == Name && other.ValueType == ValueType;
170+
171+
/// <summary>
172+
/// Normalize value into a canonical representation based on its value type.
173+
/// </summary>
174+
/// <typeparam name="T">Expected return type.</typeparam>
175+
/// <param name="value">Value to normalize.</param>
176+
/// <returns>Normalized value.</returns>
177+
internal T NormalizeValue<T>(T value)
178+
where T : notnull
179+
{
180+
// For keyword list, it will always be IReadOnlyCollection<string>, but we need to
181+
// normalize as a list of strings
182+
if (ValueType == IndexedValueType.KeywordList && value is IReadOnlyCollection<string> strColl)
183+
{
184+
return (T)(object)strColl.ToList();
185+
}
186+
return value;
187+
}
169188
}
170189

171190
/// <summary>

src/Temporalio/Worker/WorkflowInstance.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public WorkflowInstance(WorkflowInstanceDetails details)
143143
false);
144144
var initialSearchAttributes = details.Init.SearchAttributes;
145145
typedSearchAttributes = new(
146-
() => initialSearchAttributes == null ? new(new()) :
146+
() => initialSearchAttributes == null ? new() :
147147
SearchAttributeCollection.FromProto(initialSearchAttributes),
148148
false);
149149
var act = details.InitialActivation;

src/Temporalio/Workflows/SearchAttributeUpdate.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public class SearchAttributeUpdate<T> : SearchAttributeUpdate
9090
internal SearchAttributeUpdate(SearchAttributeKey<T> key, T value)
9191
{
9292
Key = key;
93-
this.value = value;
93+
this.value = key.NormalizeValue(value);
9494
HasValue = true;
9595
}
9696

tests/Temporalio.Tests/Worker/WorkflowWorkerTests.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,8 +1404,7 @@ public class SearchAttributesWorkflow
14041404
Set(AttrDateTime, new DateTimeOffset(2001, 1, 1, 0, 0, 0, TimeSpan.Zero)).
14051405
Set(AttrDouble, 123.45).
14061406
Set(AttrKeyword, "SomeKeyword").
1407-
// TODO(cretz): Fix after Temporal dev server upgraded
1408-
// Set(AttrKeywordList, new[] { "SomeKeyword1", "SomeKeyword2" }).
1407+
Set(AttrKeywordList, new[] { "SomeKeyword1", "SomeKeyword2" }).
14091408
Set(AttrLong, 678).
14101409
Set(AttrText, "SomeText").
14111410
ToSearchAttributeCollection();
@@ -1424,6 +1423,7 @@ public class SearchAttributesWorkflow
14241423
public static readonly SearchAttributeCollection AttributesFirstUpdated = new SearchAttributeCollection.Builder().
14251424
Set(AttrBool, false).
14261425
Set(AttrDateTime, new DateTimeOffset(2002, 1, 1, 0, 0, 0, TimeSpan.Zero)).
1426+
Set(AttrKeywordList, new[] { "SomeKeyword1", "SomeKeyword2" }).
14271427
Set(AttrDouble, 234.56).
14281428
ToSearchAttributeCollection();
14291429

@@ -1434,21 +1434,27 @@ public class SearchAttributesWorkflow
14341434
AttrDateTime.ValueUnset(),
14351435
AttrDouble.ValueUnset(),
14361436
AttrKeyword.ValueSet("AnotherKeyword"),
1437+
AttrKeywordList.ValueSet(new[] { "SomeKeyword3", "SomeKeyword4" }),
14371438
AttrLong.ValueSet(789),
14381439
AttrText.ValueSet("SomeOtherText"),
14391440
};
14401441

14411442
public static readonly SearchAttributeCollection AttributesSecondUpdated = new SearchAttributeCollection.Builder().
14421443
Set(AttrKeyword, "AnotherKeyword").
1444+
Set(AttrKeywordList, new[] { "SomeKeyword3", "SomeKeyword4" }).
14431445
Set(AttrLong, 789).
14441446
Set(AttrText, "SomeOtherText").
14451447
ToSearchAttributeCollection();
14461448

14471449
public static void AssertAttributesEqual(
14481450
SearchAttributeCollection expected, SearchAttributeCollection actual) =>
1451+
// xUnit compares dictionaries using Equals on key and value properly even if they have
1452+
// differing subtypes
14491453
Assert.Equal(
1450-
expected.UntypedValues.Where(kvp => kvp.Key.Name.StartsWith("DotNet")),
1451-
actual.UntypedValues.Where(kvp => kvp.Key.Name.StartsWith("DotNet")));
1454+
expected.UntypedValues.Where(kvp => kvp.Key.Name.StartsWith("DotNet")).
1455+
ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
1456+
actual.UntypedValues.Where(kvp => kvp.Key.Name.StartsWith("DotNet")).
1457+
ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
14521458

14531459
private bool proceed;
14541460

tests/Temporalio.Tests/WorkflowEnvironmentTestBase.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,7 @@ await Client.OperatorService.AddSearchAttributesAsync(new()
8484
[AttrDateTime.Name] = AttrDateTime.ValueType,
8585
[AttrDouble.Name] = AttrDouble.ValueType,
8686
[AttrKeyword.Name] = AttrKeyword.ValueType,
87-
// TODO(cretz): Fix after Temporal dev server upgraded
88-
// [AttrKeywordList.Name] = AttrKeywordList.ValueType,
87+
[AttrKeywordList.Name] = AttrKeywordList.ValueType,
8988
[AttrLong.Name] = AttrLong.ValueType,
9089
[AttrText.Name] = AttrText.ValueType,
9190
},

0 commit comments

Comments
 (0)