Skip to content

Commit

Permalink
[Azure.Core.Amqp] Lazy Allocation for All Sections (Azure#21791)
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 authored Jun 17, 2021
1 parent 2265494 commit a551ee5
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 a551ee5

Please sign in to comment.