Skip to content

Commit

Permalink
CF/P AVAD: Fixes Deserialization of ChangeFeedItem and ChangeFeedMeta…
Browse files Browse the repository at this point in the history
…data to support System.Text.Json and Newtonsoft.Json (#4618)

* checkin in

* support for both STJ and NSJ

* update contracts.

* name change PreviousLsn

* STJ TypeConverter support for ChangeFeedMetadata

* adding bacl StringEnumConverter

* test for Writes ChangeFeedMetadata

* removing DateTimeOffset as results are inconsistent.

* trying to get GMT, not local

* static UnixEpoch

* static qualifier in tests

* PropertyNameCaseInsensitive = false tests. copy of True tests.

* setting PropertyNameCaseInsensitive correctly for tests

* removed duplication for propertyNameCaseInsensitive tests

* remove JsonStringEnumConverter(), from tests

---------

Co-authored-by: Matias Quaranta <ealsur@users.noreply.github.com>
  • Loading branch information
philipthomas-MSFT and ealsur authored Aug 21, 2024
1 parent 2fbc278 commit a1144f4
Show file tree
Hide file tree
Showing 8 changed files with 947 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Microsoft.Azure.Cosmos
{
using System.Text.Json.Serialization;
using Newtonsoft.Json;

/// <summary>
Expand Down Expand Up @@ -59,18 +60,21 @@ class ChangeFeedItem<T>
/// The full fidelity change feed current item.
/// </summary>
[JsonProperty(PropertyName = "current")]
[JsonPropertyName("current")]
public T Current { get; set; }

/// <summary>
/// The full fidelity change feed metadata.
/// </summary>
[JsonProperty(PropertyName = "metadata", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("metadata")]
public ChangeFeedMetadata Metadata { get; set; }

/// <summary>
/// For delete operations, previous image is always going to be provided. The previous image on replace operations is not going to be exposed by default and requires account-level or container-level opt-in.
/// </summary>
[JsonProperty(PropertyName = "previous", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("previous")]
public T Previous { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,54 @@
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Text.Json;
using Microsoft.Azure.Cosmos.Resource.FullFidelity;
using Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters;
using Microsoft.Azure.Documents;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

/// <summary>
/// The metadata of a change feed resource with <see cref="ChangeFeedMode"/> is initialized to <see cref="ChangeFeedMode.AllVersionsAndDeletes"/>.
/// </summary>
[System.Text.Json.Serialization.JsonConverter(typeof(ChangeFeedMetadataConverter))]
#if PREVIEW
public
#else
internal
#endif
#endif
class ChangeFeedMetadata
{
/// <summary>
/// The conflict resolution timestamp.
/// The change's conflict resolution timestamp.
/// </summary>
[JsonProperty(PropertyName = "crts", NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.ConflictResolutionTimestamp, NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime ConflictResolutionTimestamp { get; internal set; }

/// <summary>
/// The current logical sequence number.
/// The current change's logical sequence number.
/// </summary>
[JsonProperty(PropertyName = "lsn", NullValueHandling = NullValueHandling.Ignore)]
public long Lsn { get; internal set; }
[JsonProperty(PropertyName = ChangeFeedMetadataFields.Lsn, NullValueHandling = NullValueHandling.Ignore)]
public long Lsn { get; internal set; }

/// <summary>
/// The change feed operation type.
/// The change's feed operation type <see cref="ChangeFeedOperationType"/>.
/// </summary>
[JsonProperty(PropertyName = "operationType")]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.OperationType, NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
public ChangeFeedOperationType OperationType { get; internal set; }

/// <summary>
/// The previous logical sequence number.
/// The previous change's logical sequence number.
/// </summary>
[JsonProperty(PropertyName = "previousImageLSN", NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.PreviousImageLSN, NullValueHandling = NullValueHandling.Ignore)]
public long PreviousLsn { get; internal set; }

/// <summary>
/// Used to distinquish explicit deletes (e.g. via DeleteItem) from deletes caused by TTL expiration (a collection may define time-to-live policy for documents).
/// </summary>
[JsonProperty(PropertyName = "timeToLiveExpired", NullValueHandling= NullValueHandling.Ignore)]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.TimeToLiveExpired, NullValueHandling = NullValueHandling.Ignore)]
public bool IsTimeToLiveExpired { get; internal set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Resource.FullFidelity
{
internal class ChangeFeedMetadataFields
{
public const string ConflictResolutionTimestamp = "crts";
public const string Lsn = "lsn";
public const string OperationType = "operationType";
public const string PreviousImageLSN = "previousImageLSN";
public const string TimeToLiveExpired = "timeToLiveExpired";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters
{
using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Azure.Cosmos.Resource.FullFidelity;
using Microsoft.Azure.Documents;

/// <summary>
/// Converter used to support System.Text.Json de/serialization of type ChangeFeedMetadata/>.
/// </summary>
internal class ChangeFeedMetadataConverter : JsonConverter<ChangeFeedMetadata>
{
private readonly static DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

public override ChangeFeedMetadata Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}

if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException(string.Format(CultureInfo.CurrentCulture, RMResources.JsonUnexpectedToken));
}

JsonElement element = JsonDocument.ParseValue(ref reader).RootElement;

ChangeFeedMetadata metadata = new ();

foreach (JsonProperty property in element.EnumerateObject())
{
if (property.NameEquals(ChangeFeedMetadataFields.Lsn))
{
metadata.Lsn = property.Value.GetInt64();
}
else if (property.NameEquals(ChangeFeedMetadataFields.ConflictResolutionTimestamp))
{
metadata.ConflictResolutionTimestamp = ChangeFeedMetadataConverter.ToDateTimeFromUnixTimeInSeconds(property.Value.GetInt64());
}
else if (property.NameEquals(ChangeFeedMetadataFields.OperationType))
{
metadata.OperationType = (ChangeFeedOperationType)Enum.Parse(enumType: typeof(ChangeFeedOperationType), value: property.Value.GetString(), ignoreCase: true);
}
else if (property.NameEquals(ChangeFeedMetadataFields.TimeToLiveExpired))
{
metadata.IsTimeToLiveExpired = property.Value.GetBoolean();
}
else if (property.NameEquals(ChangeFeedMetadataFields.PreviousImageLSN))
{
metadata.PreviousLsn = property.Value.GetInt64();
}
}

return metadata;
}

public override void Write(Utf8JsonWriter writer, ChangeFeedMetadata value, JsonSerializerOptions options)
{
if (value == null)
{
return;
}

writer.WriteStartObject();

writer.WriteNumber(ChangeFeedMetadataFields.ConflictResolutionTimestamp, ChangeFeedMetadataConverter.ToUnixTimeInSecondsFromDateTime(value.ConflictResolutionTimestamp));
writer.WriteBoolean(ChangeFeedMetadataFields.TimeToLiveExpired, value.IsTimeToLiveExpired);
writer.WriteNumber(ChangeFeedMetadataFields.Lsn, value.Lsn);
writer.WriteString(ChangeFeedMetadataFields.OperationType, value.OperationType.ToString());
writer.WriteNumber(ChangeFeedMetadataFields.PreviousImageLSN, value.PreviousLsn);

writer.WriteEndObject();
}

private static long ToUnixTimeInSecondsFromDateTime(DateTime date)
{
return (long)(date - ChangeFeedMetadataConverter.UnixEpoch).TotalSeconds;
}

private static DateTime ToDateTimeFromUnixTimeInSeconds(long unixTimeInSeconds)
{
return ChangeFeedMetadataConverter.UnixEpoch.AddSeconds(unixTimeInSeconds);
}
}
}
Loading

0 comments on commit a1144f4

Please sign in to comment.