Skip to content

Commit

Permalink
[Azure.Core.Amqp] Lazy Allocation for All Sections
Browse files Browse the repository at this point in the history
The focus of these changes is to enable lazy allocation for all sections
of the `AmqpAnnotatedMessage` type, to match the AMQP spec definition.
(see: http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-message-format)

In order to allow downstream consumers to test if a section is populated
without triggering allocation for the property, the `HasSection` member
has been added.
  • Loading branch information
jsquire committed Jun 17, 2021
1 parent 3f38e29 commit 3de1b87
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 10 deletions.
31 changes: 31 additions & 0 deletions sdk/core/Azure.Core.Amqp/Azure.Core.Amqp.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31205.134
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Amqp", "src\Azure.Core.Amqp.csproj", "{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Amqp.Tests", "tests\Azure.Core.Amqp.Tests.csproj", "{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5AD38C8-DAB6-40E6-AB79-41A8ED9D297B}.Release|Any CPU.Build.0 = Release|Any CPU
{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61ABA08F-FA50-4DEA-8C97-00B7B057BDEF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1DC074C7-A8F0-4778-B791-323040BDF4C5}
EndGlobalSection
EndGlobal
12 changes: 4 additions & 8 deletions sdk/core/Azure.Core.Amqp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

## 1.2.0-beta.1 (Unreleased)

### Features Added

### Breaking Changes

### Key Bugs Fixed

### Fixed

### Added
- All section properties of the `AmqpAnnotatedMessage` are now lazily allocated to reflect that they are defined as optional in the AMQP specification, section 3.2.

- The `HasSection` method has been added to `AmqpAnnotatedMessage` to allow inspecting the property for a section to determine if it is populated without triggering an allocation.

## 1.1.0 (2021-06-16)

Expand Down
11 changes: 11 additions & 0 deletions sdk/core/Azure.Core.Amqp/api/Azure.Core.Amqp.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public AmqpAnnotatedMessage(Azure.Core.Amqp.AmqpMessageBody body) { }
public Azure.Core.Amqp.AmqpMessageHeader Header { get { throw null; } }
public System.Collections.Generic.IDictionary<string, object> MessageAnnotations { get { throw null; } }
public Azure.Core.Amqp.AmqpMessageProperties Properties { get { throw null; } }
public bool HasSection(Azure.Core.Amqp.AmqpMessageSection section) { throw null; }
}
public partial class AmqpMessageBody
{
Expand Down Expand Up @@ -87,4 +88,14 @@ internal AmqpMessageProperties() { }
public Azure.Core.Amqp.AmqpAddress? To { get { throw null; } set { } }
public System.ReadOnlyMemory<byte>? UserId { get { throw null; } set { } }
}
public enum AmqpMessageSection
{
Header = 0,
DeliveryAnnotations = 1,
MessageAnnotations = 2,
Properties = 3,
ApplicationProperties = 4,
Body = 5,
Footer = 6,
}
}
46 changes: 44 additions & 2 deletions sdk/core/Azure.Core.Amqp/src/AmqpAnnotatedMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,19 @@ public AmqpAnnotatedMessage(AmqpMessageBody body)
/// Gets the header of the AMQP message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-header" />
/// </summary>
public AmqpMessageHeader Header { get; } = new AmqpMessageHeader();
public AmqpMessageHeader Header
{
get
{
if (_header == null)
{
_header = new AmqpMessageHeader();
}
return _header;
}
}

private AmqpMessageHeader? _header;

/// <summary>
/// Gets the footer of the AMQP message.
Expand Down Expand Up @@ -86,7 +98,19 @@ public IDictionary<string, object> MessageAnnotations
/// <summary>
/// Gets the properties of the AMQP message.
/// </summary>
public AmqpMessageProperties Properties { get; } = new AmqpMessageProperties();
public AmqpMessageProperties Properties
{
get
{
if (_properties == null)
{
_properties = new AmqpMessageProperties();
}
return _properties;
}
}

private AmqpMessageProperties? _properties;

/// <summary>
/// Gets the application properties of the AMQP message.
Expand All @@ -111,5 +135,23 @@ public IDictionary<string, object> ApplicationProperties
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-data"/>
/// </summary>
public AmqpMessageBody Body { get; set; }

/// <summary>
/// Determines whether the specified section is present for the AMQP message.
/// </summary>
/// <param name="section">The section to consider.</param>
/// <returns><c>true</c> if the specified <paramref name="section"/> is populated for the AMQP message; otherwise, <c>false</c>.</returns>
public bool HasSection(AmqpMessageSection section) =>
section switch
{
AmqpMessageSection.Header => (_header != null),
AmqpMessageSection.DeliveryAnnotations => (_deliveryAnnotations != null),
AmqpMessageSection.MessageAnnotations => (_messageAnnotations != null),
AmqpMessageSection.Properties => (_properties != null),
AmqpMessageSection.ApplicationProperties => (_applicationProperties != null),
AmqpMessageSection.Body => (Body != null),
AmqpMessageSection.Footer => (_footer != null),
_ => throw new ArgumentException($"Unknown AMQP message section: { section }.", nameof(section))
};
}
}
56 changes: 56 additions & 0 deletions sdk/core/Azure.Core.Amqp/src/AmqpMessageSection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Azure.Core.Amqp
{
/// <summary>
/// Represents the sections of an AMQP message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#section-message-format"/>
/// </summary>
public enum AmqpMessageSection
{
/// <summary>
/// The header section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-header"/>
/// <seealso cref="AmqpMessageHeader" />
/// </summary>
Header,

/// <summary>
/// The delivery annotations section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-delivery-annotations"/>
/// </summary>
DeliveryAnnotations,

/// <summary>
/// The message annotations section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-message-annotations"/>
/// </summary>
MessageAnnotations,

/// <summary>
/// The properties section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-properties"/>
/// <seealso cref="AmqpMessageProperties"/>
/// </summary>
Properties,

/// <summary>
/// The application properties section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-application-properties"/>
/// </summary>
ApplicationProperties,

/// <summary>
/// The body of the message, representing one specific <see cref="AmqpMessageBodyType" /> sections.
/// <seealso cref="AmqpMessageBody"/>
/// </summary>
Body,

/// <summary>
/// The footer section of the message.
/// <seealso href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-footer"/>
/// </summary>
Footer
}
}
89 changes: 89 additions & 0 deletions sdk/core/Azure.Core.Amqp/tests/AmqpAnnotatedMessageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace Azure.Core.Amqp.Tests
{
public class AmqpAnnotatedMessageTests
{
private static readonly AmqpMessageBody EmptyDataBody = AmqpMessageBody.FromData(new ReadOnlyMemory<byte>[] { Array.Empty<byte>() });

[Test]
public void CanCreateAnnotatedMessage()
{
Expand Down Expand Up @@ -65,5 +67,92 @@ public void CanCreateAnnotatedMessage()
Assert.AreEqual("to", message.Properties.To.ToString());
Assert.AreEqual("userId", Encoding.UTF8.GetString(message.Properties.UserId.Value.ToArray()));
}

[Test]
public void HeaderIsCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.Header));

message.Header.DeliveryCount = 99;
Assert.True(message.HasSection(AmqpMessageSection.Header));
Assert.NotNull(message.Header);
}

[Test]
public void DeliveryAnnotationsAreCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.DeliveryAnnotations));

message.DeliveryAnnotations.Add("test", new object());
Assert.True(message.HasSection(AmqpMessageSection.DeliveryAnnotations));
Assert.NotNull(message.DeliveryAnnotations);
}

[Test]
public void MessageAnnotationsAreCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.MessageAnnotations));

message.MessageAnnotations.Add("test", new object());
Assert.True(message.HasSection(AmqpMessageSection.MessageAnnotations));
Assert.NotNull(message.MessageAnnotations);
}

[Test]
public void PropertiesAreCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.Properties));

message.Properties.ContentType = "test/unit";
Assert.True(message.HasSection(AmqpMessageSection.Properties));
Assert.NotNull(message.Properties);
}

[Test]
public void ApplicationPropertiesAreCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.ApplicationProperties));

message.ApplicationProperties.Add("test", new object());
Assert.True(message.HasSection(AmqpMessageSection.ApplicationProperties));
Assert.NotNull(message.ApplicationProperties);
}

[Test]
public void FooterIsCreatedOnDemand()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);
Assert.False(message.HasSection(AmqpMessageSection.Footer));

message.Footer.Add("test", new object());
Assert.True(message.HasSection(AmqpMessageSection.Footer));
Assert.NotNull(message.Footer);
}

[Test]
public void BodyIsDetectedByHasSection()
{
var message = new AmqpAnnotatedMessage(EmptyDataBody);

message.Body = null;
Assert.False(message.HasSection(AmqpMessageSection.Body));

message.Body = AmqpMessageBody.FromValue("this is a string value");
Assert.True(message.HasSection(AmqpMessageSection.Body));
}

[Test]
public void HasSectionValidatesTheSection()
{
var invalidSection = (AmqpMessageSection)int.MinValue;
var message = new AmqpAnnotatedMessage(EmptyDataBody);

Assert.Throws<ArgumentException>(() => message.HasSection(invalidSection));
}
}
}

0 comments on commit 3de1b87

Please sign in to comment.