Skip to content
This repository has been archived by the owner on Aug 3, 2024. It is now read-only.
/ ServerCommon Public archive

Commit

Permalink
Added generic Service Bus serializer (#70)
Browse files Browse the repository at this point in the history
Added `BrokedMessageSerializer`, a JSON serializer that enforces versioned schemas. Updated the `PackageValidationMessageData` serializer to use this new serializer. This will be used by `PackageSigningValidator` and `PackageCertificatesValidator` to queue messages to their downstream validation jobs.

NOTE: The `BrokedMessageSerializer` does not gracefully handle messages with a different version that what is expected. This will have to be added in later.
  • Loading branch information
loic-sharma authored Oct 11, 2017
1 parent 2b04171 commit 6506885
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 84 deletions.
3 changes: 1 addition & 2 deletions NuGet.Server.Common.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27005.2
VisualStudioVersion = 15.0.26730.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8415FED7-1BED-4227-8B4F-BB7C24E041CD}"
EndProject
Expand Down Expand Up @@ -162,7 +162,6 @@ Global
{C1E36A2C-1C1B-4521-B256-AD42505D9EFB} = {8415FED7-1BED-4227-8B4F-BB7C24E041CD}
{E29F54DF-DFB8-4E27-940D-21ECCB9B6FC1} = {7783A106-0F4C-4055-9AB4-413FB2C7B8F0}
{79F72C83-E94D-4D04-B904-5A4DA161168E} = {7783A106-0F4C-4055-9AB4-413FB2C7B8F0}
{FF5CA51A-CD6A-463F-AE9A-5737FF0FCFA7} = {7783A106-0F4C-4055-9AB4-413FB2C7B8F0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AA413DB0-5475-4B5D-A3AF-6323DA8D538B}
Expand Down
95 changes: 95 additions & 0 deletions src/NuGet.Services.ServiceBus/BrokeredMessageSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Newtonsoft.Json;

namespace NuGet.Services.ServiceBus
{
/// <summary>
/// Serializes objects into Service Bus <see cref="IBrokeredMessage"/>. This serializer will
/// throw <see cref="FormatException"/> if the message does not contain a message with the expected
/// type and schema version.
/// </summary>
/// <typeparam name="TMessage">A type decorated with a <see cref="SchemaAttribute"/>.</typeparam>
public class BrokeredMessageSerializer<TMessage>
{
private const string SchemaNameKey = "SchemaName";
private const string SchemaVersionKey = "SchemaVersion";

private static readonly string SchemaName;
private static readonly int SchemaVersion;

static BrokeredMessageSerializer()
{
var attributes = typeof(TMessage).GetCustomAttributes(typeof(SchemaAttribute), inherit: false);

if (attributes.Length != 1)
{
throw new InvalidOperationException($"{typeof(TMessage)} must have exactly one {nameof(SchemaAttribute)}");
}

var schemaAttribute = (SchemaAttribute)attributes[0];

SchemaName = schemaAttribute.Name;
SchemaVersion = schemaAttribute.Version;
}

public IBrokeredMessage Serialize(TMessage message)
{
var json = JsonConvert.SerializeObject(message);
var brokeredMessage = new BrokeredMessageWrapper(json);

brokeredMessage.Properties[SchemaNameKey] = SchemaName;
brokeredMessage.Properties[SchemaVersionKey] = SchemaVersion;

return brokeredMessage;
}

public TMessage Deserialize(IBrokeredMessage message)
{
AssertTypeAndSchemaVersion(message, SchemaName, SchemaVersion);

return JsonConvert.DeserializeObject<TMessage>(message.GetBody());
}

private static void AssertTypeAndSchemaVersion(IBrokeredMessage message, string type, int schemaVersion)
{
if (GetType(message) != type)
{
throw new FormatException($"The provided message should have {SchemaNameKey} property '{type}'.");
}

if (GetSchemaVersion(message) != schemaVersion)
{
throw new FormatException($"The provided message should have {SchemaVersionKey} property '{schemaVersion}'.");
}
}

private static int GetSchemaVersion(IBrokeredMessage message)
{
return GetProperty<int>(message, SchemaVersionKey, "an integer");
}

private static string GetType(IBrokeredMessage message)
{
return GetProperty<string>(message, SchemaNameKey, "a string");
}

private static T GetProperty<T>(IBrokeredMessage message, string key, string typeLabel)
{
object value;
if (!message.Properties.TryGetValue(key, out value))
{
throw new FormatException($"The provided message does not have a {key} property.");
}

if (!(value is T))
{
throw new FormatException($"The provided message contains a {key} property that is not {typeLabel}.");
}

return (T)value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="BrokeredMessageSerializer.cs" />
<Compile Include="BrokeredMessageWrapper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\AssemblyInfo.*.cs" />
<Compile Include="SchemaAttribute.cs" />
<Compile Include="SubscriptionClientWrapper.cs" />
<Compile Include="TopicClientWrapper.cs" />
</ItemGroup>
Expand Down
24 changes: 24 additions & 0 deletions src/NuGet.Services.ServiceBus/SchemaAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace NuGet.Services.ServiceBus
{
/// <summary>
/// The attribute used to define a schema.
/// </summary>
public class SchemaAttribute : Attribute
{
/// <summary>
/// The name of a message's schema. This should NEVER change for a single message.
/// </summary>
public string Name { get; set; }

/// <summary>
/// The schema's version. This should be bumped whenever a schema's property
/// is added, removed, or modified.
/// </summary>
public int Version { get; set; }
}
}
3 changes: 2 additions & 1 deletion src/NuGet.Services.ServiceBus/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"dependencies": {
"Newtonsoft.Json": "9.0.1",
"WindowsAzure.ServiceBus": "4.1.3"
},
"frameworks": {
Expand All @@ -8,4 +9,4 @@
"runtimes": {
"win": {}
}
}
}
76 changes: 7 additions & 69 deletions src/NuGet.Services.Validation/ServiceBusMessageSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,99 +2,37 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Newtonsoft.Json;
using NuGet.Services.ServiceBus;

namespace NuGet.Services.Validation
{
public class ServiceBusMessageSerializer : IServiceBusMessageSerializer
{
private const string SchemaVersionKey = "SchemaVersion";
private const string TypeKey = "Type";
private const string PackageValidationType = nameof(PackageValidationMessageData);
private const int SchemaVersion1 = 1;
private const string PackageValidationSchemaName = "PackageValidationMessageData";

private static readonly BrokeredMessageSerializer<PackageValidationMessageData1> _serializer = new BrokeredMessageSerializer<PackageValidationMessageData1>();

public IBrokeredMessage SerializePackageValidationMessageData(PackageValidationMessageData message)
{
var body = new PackageValidationMessageData1
return _serializer.Serialize(new PackageValidationMessageData1
{
PackageId = message.PackageId,
PackageVersion = message.PackageVersion,
ValidationTrackingId = message.ValidationTrackingId,
};

var brokeredMessage = Serialize(body, PackageValidationType, SchemaVersion1);

return brokeredMessage;
});
}

public PackageValidationMessageData DeserializePackageValidationMessageData(IBrokeredMessage message)
{
AssertTypeAndSchemaVersion(message, PackageValidationType, SchemaVersion1);

var json = message.GetBody();
var data = Deserialize<PackageValidationMessageData1>(json);
var data = _serializer.Deserialize(message);

return new PackageValidationMessageData(
data.PackageId,
data.PackageVersion,
data.ValidationTrackingId);
}

private static void AssertTypeAndSchemaVersion(IBrokeredMessage message, string type, int schemaVersion)
{
if (GetType(message) != type)
{
throw new FormatException($"The provided message should have {TypeKey} property '{type}'.");
}

if (GetSchemaVersion(message) != schemaVersion)
{
throw new FormatException($"The provided message should have {SchemaVersionKey} property '{schemaVersion}'.");
}
}

private static int GetSchemaVersion(IBrokeredMessage message)
{
return GetProperty<int>(message, SchemaVersionKey, "an integer");
}

private static string GetType(IBrokeredMessage message)
{
return GetProperty<string>(message, TypeKey, "a string");
}

private static T GetProperty<T>(IBrokeredMessage message, string key, string typeLabel)
{
object value;
if (!message.Properties.TryGetValue(key, out value))
{
throw new FormatException($"The provided message does not have a {key} property.");
}

if (!(value is T))
{
throw new FormatException($"The provided message contains a {key} property that is not {typeLabel}.");
}

return (T)value;
}

private static IBrokeredMessage Serialize<T>(T data, string type, int schemaVersion)
{
var json = JsonConvert.SerializeObject(data);
var brokeredMessage = new BrokeredMessageWrapper(json);
brokeredMessage.Properties[TypeKey] = type;
brokeredMessage.Properties[SchemaVersionKey] = schemaVersion;

return brokeredMessage;
}

private static T Deserialize<T>(string json)
{
return JsonConvert.DeserializeObject<T>(json);
}

[Schema(Name = PackageValidationSchemaName, Version = 1)]
private class PackageValidationMessageData1
{
public string PackageId { get; set; }
Expand Down
Loading

0 comments on commit 6506885

Please sign in to comment.