From 0349eeb274c023fe288a648eedffc8217cf9c04f Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Fri, 28 Jan 2022 14:38:27 +0000 Subject: [PATCH] feat: Add a Protobuf event formatter Further features may be added later, e.g. convenience methods. Signed-off-by: Jon Skeet --- CloudEvents.sln | 16 +- generate_protos.sh | 67 + .../CloudNative.CloudEvents.Protobuf.csproj | 23 + .../ProtoSchema.g.cs | 1215 +++++++++++++++++ .../ProtobufEventFormatter.cs | 400 ++++++ .../README.md | 6 + .../CloudNative.CloudEvents.UnitTests.csproj | 1 + .../Protobuf/ProtobufEventFormatterTest.cs | 713 ++++++++++ .../Protobuf/TestMessages.g.cs | 233 ++++ .../Protobuf/test_messages.proto | 9 + 10 files changed, 2682 insertions(+), 1 deletion(-) create mode 100644 generate_protos.sh create mode 100644 src/CloudNative.CloudEvents.Protobuf/CloudNative.CloudEvents.Protobuf.csproj create mode 100644 src/CloudNative.CloudEvents.Protobuf/ProtoSchema.g.cs create mode 100644 src/CloudNative.CloudEvents.Protobuf/ProtobufEventFormatter.cs create mode 100644 src/CloudNative.CloudEvents.Protobuf/README.md create mode 100644 test/CloudNative.CloudEvents.UnitTests/Protobuf/ProtobufEventFormatterTest.cs create mode 100644 test/CloudNative.CloudEvents.UnitTests/Protobuf/TestMessages.g.cs create mode 100644 test/CloudNative.CloudEvents.UnitTests/Protobuf/test_messages.proto diff --git a/CloudEvents.sln b/CloudEvents.sln index ad5262d..4aa8625 100644 --- a/CloudEvents.sln +++ b/CloudEvents.sln @@ -33,7 +33,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudNative.CloudEvents.Avr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudNative.CloudEvents.NewtonsoftJson", "src\CloudNative.CloudEvents.NewtonsoftJson\CloudNative.CloudEvents.NewtonsoftJson.csproj", "{9DC17081-21D8-4123-8650-D97C2153DB8C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudNative.CloudEvents.SystemTextJson", "src\CloudNative.CloudEvents.SystemTextJson\CloudNative.CloudEvents.SystemTextJson.csproj", "{FACB3EF2-F078-479A-A91C-719894CB66BF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudNative.CloudEvents.SystemTextJson", "src\CloudNative.CloudEvents.SystemTextJson\CloudNative.CloudEvents.SystemTextJson.csproj", "{FACB3EF2-F078-479A-A91C-719894CB66BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudNative.CloudEvents.Protobuf", "src\CloudNative.CloudEvents.Protobuf\CloudNative.CloudEvents.Protobuf.csproj", "{9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -189,6 +191,18 @@ Global {FACB3EF2-F078-479A-A91C-719894CB66BF}.Release|x64.Build.0 = Release|Any CPU {FACB3EF2-F078-479A-A91C-719894CB66BF}.Release|x86.ActiveCfg = Release|Any CPU {FACB3EF2-F078-479A-A91C-719894CB66BF}.Release|x86.Build.0 = Release|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Debug|x64.ActiveCfg = Debug|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Debug|x64.Build.0 = Debug|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Debug|x86.ActiveCfg = Debug|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Debug|x86.Build.0 = Debug|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Release|Any CPU.Build.0 = Release|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Release|x64.ActiveCfg = Release|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Release|x64.Build.0 = Release|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Release|x86.ActiveCfg = Release|Any CPU + {9D82AC2B-0075-4161-AE0E-4A6629C9FF2A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/generate_protos.sh b/generate_protos.sh new file mode 100644 index 0000000..973db3c --- /dev/null +++ b/generate_protos.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# Copyright 2021 Cloud Native Foundation. +# Licensed under the Apache 2.0 license. +# See LICENSE file in the project root for full license information. + +set -e +PROTOBUF_VERSION=3.19.3 + +# Generates the classes for the protobuf event format + +case "$OSTYPE" in + linux*) + PROTOBUF_PLATFORM=linux-x86_64 + PROTOC=tmp/bin/protoc + ;; + win* | msys* | cygwin*) + PROTOBUF_PLATFORM=win64 + PROTOC=tmp/bin/protoc.exe + ;; + darwin*) + PROTOBUF_PLATFORM=osx-x86_64 + PROTOC=tmp/bin/protoc + ;; + *) + echo "Unknown OSTYPE: $OSTYPE" + exit 1 +esac + +# Clean up previous generation results +rm -f src/CloudNative.CloudEvents.Protobuf/*.g.cs +rm -f test/CloudNative.CloudEvents.UnitTests/Protobuf/*.g.cs + +rm -rf tmp +mkdir tmp +cd tmp + +echo "- Downloading protobuf@$PROTOBUF_VERSION" +curl -sSL \ + https://github.com/protocolbuffers/protobuf/releases/download/v$PROTOBUF_VERSION/protoc-$PROTOBUF_VERSION-$PROTOBUF_PLATFORM.zip \ + --output protobuf.zip +unzip -q protobuf.zip + +echo "- Downloading schema" +# TODO: Use the 1.0.2 branch when it exists. +mkdir cloudevents +curl -sSL https://raw.githubusercontent.com/cloudevents/spec/main/cloudevents/formats/cloudevents.proto -o cloudevents/ProtoSchema.proto + +cd .. + +# Schema proto +$PROTOC \ + -I tmp/include \ + -I tmp/cloudevents \ + --csharp_out=src/CloudNative.CloudEvents.Protobuf \ + --csharp_opt=file_extension=.g.cs \ + tmp/cloudevents/ProtoSchema.proto + +# Test protos +$PROTOC \ + -I tmp/include \ + -I test/CloudNative.CloudEvents.UnitTests/Protobuf \ + --csharp_out=test/CloudNative.CloudEvents.UnitTests/Protobuf \ + --csharp_opt=file_extension=.g.cs \ + test/CloudNative.CloudEvents.UnitTests/Protobuf/*.proto + +echo "Generated code." +rm -rf tmp diff --git a/src/CloudNative.CloudEvents.Protobuf/CloudNative.CloudEvents.Protobuf.csproj b/src/CloudNative.CloudEvents.Protobuf/CloudNative.CloudEvents.Protobuf.csproj new file mode 100644 index 0000000..22d2bff --- /dev/null +++ b/src/CloudNative.CloudEvents.Protobuf/CloudNative.CloudEvents.Protobuf.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0;netstandard2.1 + Support for the Protobuf event format in for CloudNative.CloudEvents + cncf;cloudnative;cloudevents;events;protobuf + 8.0 + enable + 2.0.0-local.1 + + + + + + + + + + + + + + diff --git a/src/CloudNative.CloudEvents.Protobuf/ProtoSchema.g.cs b/src/CloudNative.CloudEvents.Protobuf/ProtoSchema.g.cs new file mode 100644 index 0000000..794cd47 --- /dev/null +++ b/src/CloudNative.CloudEvents.Protobuf/ProtoSchema.g.cs @@ -0,0 +1,1215 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: ProtoSchema.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace CloudNative.CloudEvents.V1 { + + /// Holder for reflection information generated from ProtoSchema.proto + public static partial class ProtoSchemaReflection { + + #region Descriptor + /// File descriptor for ProtoSchema.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static ProtoSchemaReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "ChFQcm90b1NjaGVtYS5wcm90bxIRaW8uY2xvdWRldmVudHMudjEaGWdvb2ds", + "ZS9wcm90b2J1Zi9hbnkucHJvdG8aH2dvb2dsZS9wcm90b2J1Zi90aW1lc3Rh", + "bXAucHJvdG8isAQKCkNsb3VkRXZlbnQSCgoCaWQYASABKAkSDgoGc291cmNl", + "GAIgASgJEhQKDHNwZWNfdmVyc2lvbhgDIAEoCRIMCgR0eXBlGAQgASgJEkEK", + "CmF0dHJpYnV0ZXMYBSADKAsyLS5pby5jbG91ZGV2ZW50cy52MS5DbG91ZEV2", + "ZW50LkF0dHJpYnV0ZXNFbnRyeRIVCgtiaW5hcnlfZGF0YRgGIAEoDEgAEhMK", + "CXRleHRfZGF0YRgHIAEoCUgAEioKCnByb3RvX2RhdGEYCCABKAsyFC5nb29n", + "bGUucHJvdG9idWYuQW55SAAaaQoPQXR0cmlidXRlc0VudHJ5EgsKA2tleRgB", + "IAEoCRJFCgV2YWx1ZRgCIAEoCzI2LmlvLmNsb3VkZXZlbnRzLnYxLkNsb3Vk", + "RXZlbnQuQ2xvdWRFdmVudEF0dHJpYnV0ZVZhbHVlOgI4ARrTAQoYQ2xvdWRF", + "dmVudEF0dHJpYnV0ZVZhbHVlEhQKCmNlX2Jvb2xlYW4YASABKAhIABIUCgpj", + "ZV9pbnRlZ2VyGAIgASgFSAASEwoJY2Vfc3RyaW5nGAMgASgJSAASEgoIY2Vf", + "Ynl0ZXMYBCABKAxIABIQCgZjZV91cmkYBSABKAlIABIUCgpjZV91cmlfcmVm", + "GAYgASgJSAASMgoMY2VfdGltZXN0YW1wGAcgASgLMhouZ29vZ2xlLnByb3Rv", + "YnVmLlRpbWVzdGFtcEgAQgYKBGF0dHJCBgoEZGF0YSJACg9DbG91ZEV2ZW50", + "QmF0Y2gSLQoGZXZlbnRzGAEgAygLMh0uaW8uY2xvdWRldmVudHMudjEuQ2xv", + "dWRFdmVudEKLAQoXaW8uY2xvdWRldmVudHMudjEucHJvdG9QAVoaY2xvdWRl", + "dmVudHMuaW8vZ2VucHJvdG8vdjGqAhpDbG91ZE5hdGl2ZS5DbG91ZEV2ZW50", + "cy5WMcoCF0lvXENsb3VkRXZlbnRzXFYxXFByb3Rv6gIaSW86OkNsb3VkRXZl", + "bnRzOjpWMTo6UHJvdG9iBnByb3RvMw==")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.AnyReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::CloudNative.CloudEvents.V1.CloudEvent), global::CloudNative.CloudEvents.V1.CloudEvent.Parser, new[]{ "Id", "Source", "SpecVersion", "Type", "Attributes", "BinaryData", "TextData", "ProtoData" }, new[]{ "Data" }, null, null, new pbr::GeneratedClrTypeInfo[] { null, new pbr::GeneratedClrTypeInfo(typeof(global::CloudNative.CloudEvents.V1.CloudEvent.Types.CloudEventAttributeValue), global::CloudNative.CloudEvents.V1.CloudEvent.Types.CloudEventAttributeValue.Parser, new[]{ "CeBoolean", "CeInteger", "CeString", "CeBytes", "CeUri", "CeUriRef", "CeTimestamp" }, new[]{ "Attr" }, null, null, null)}), + new pbr::GeneratedClrTypeInfo(typeof(global::CloudNative.CloudEvents.V1.CloudEventBatch), global::CloudNative.CloudEvents.V1.CloudEventBatch.Parser, new[]{ "Events" }, null, null, null, null) + })); + } + #endregion + + } + #region Messages + public sealed partial class CloudEvent : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CloudEvent()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::CloudNative.CloudEvents.V1.ProtoSchemaReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CloudEvent() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CloudEvent(CloudEvent other) : this() { + id_ = other.id_; + source_ = other.source_; + specVersion_ = other.specVersion_; + type_ = other.type_; + attributes_ = other.attributes_.Clone(); + switch (other.DataCase) { + case DataOneofCase.BinaryData: + BinaryData = other.BinaryData; + break; + case DataOneofCase.TextData: + TextData = other.TextData; + break; + case DataOneofCase.ProtoData: + ProtoData = other.ProtoData.Clone(); + break; + } + + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CloudEvent Clone() { + return new CloudEvent(this); + } + + /// Field number for the "id" field. + public const int IdFieldNumber = 1; + private string id_ = ""; + /// + /// Required Attributes + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string Id { + get { return id_; } + set { + id_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "source" field. + public const int SourceFieldNumber = 2; + private string source_ = ""; + /// + /// URI-reference + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string Source { + get { return source_; } + set { + source_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "spec_version" field. + public const int SpecVersionFieldNumber = 3; + private string specVersion_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string SpecVersion { + get { return specVersion_; } + set { + specVersion_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "type" field. + public const int TypeFieldNumber = 4; + private string type_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string Type { + get { return type_; } + set { + type_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "attributes" field. + public const int AttributesFieldNumber = 5; + private static readonly pbc::MapField.Codec _map_attributes_codec + = new pbc::MapField.Codec(pb::FieldCodec.ForString(10, ""), pb::FieldCodec.ForMessage(18, global::CloudNative.CloudEvents.V1.CloudEvent.Types.CloudEventAttributeValue.Parser), 42); + private readonly pbc::MapField attributes_ = new pbc::MapField(); + /// + /// Optional & Extension Attributes + /// + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pbc::MapField Attributes { + get { return attributes_; } + } + + /// Field number for the "binary_data" field. + public const int BinaryDataFieldNumber = 6; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString BinaryData { + get { return dataCase_ == DataOneofCase.BinaryData ? (pb::ByteString) data_ : pb::ByteString.Empty; } + set { + data_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + dataCase_ = DataOneofCase.BinaryData; + } + } + + /// Field number for the "text_data" field. + public const int TextDataFieldNumber = 7; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string TextData { + get { return dataCase_ == DataOneofCase.TextData ? (string) data_ : ""; } + set { + data_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + dataCase_ = DataOneofCase.TextData; + } + } + + /// Field number for the "proto_data" field. + public const int ProtoDataFieldNumber = 8; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Google.Protobuf.WellKnownTypes.Any ProtoData { + get { return dataCase_ == DataOneofCase.ProtoData ? (global::Google.Protobuf.WellKnownTypes.Any) data_ : null; } + set { + data_ = value; + dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.ProtoData; + } + } + + private object data_; + /// Enum of possible cases for the "data" oneof. + public enum DataOneofCase { + None = 0, + BinaryData = 6, + TextData = 7, + ProtoData = 8, + } + private DataOneofCase dataCase_ = DataOneofCase.None; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DataOneofCase DataCase { + get { return dataCase_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void ClearData() { + dataCase_ = DataOneofCase.None; + data_ = null; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as CloudEvent); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(CloudEvent other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Id != other.Id) return false; + if (Source != other.Source) return false; + if (SpecVersion != other.SpecVersion) return false; + if (Type != other.Type) return false; + if (!Attributes.Equals(other.Attributes)) return false; + if (BinaryData != other.BinaryData) return false; + if (TextData != other.TextData) return false; + if (!object.Equals(ProtoData, other.ProtoData)) return false; + if (DataCase != other.DataCase) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (Id.Length != 0) hash ^= Id.GetHashCode(); + if (Source.Length != 0) hash ^= Source.GetHashCode(); + if (SpecVersion.Length != 0) hash ^= SpecVersion.GetHashCode(); + if (Type.Length != 0) hash ^= Type.GetHashCode(); + hash ^= Attributes.GetHashCode(); + if (dataCase_ == DataOneofCase.BinaryData) hash ^= BinaryData.GetHashCode(); + if (dataCase_ == DataOneofCase.TextData) hash ^= TextData.GetHashCode(); + if (dataCase_ == DataOneofCase.ProtoData) hash ^= ProtoData.GetHashCode(); + hash ^= (int) dataCase_; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (Id.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Id); + } + if (Source.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Source); + } + if (SpecVersion.Length != 0) { + output.WriteRawTag(26); + output.WriteString(SpecVersion); + } + if (Type.Length != 0) { + output.WriteRawTag(34); + output.WriteString(Type); + } + attributes_.WriteTo(output, _map_attributes_codec); + if (dataCase_ == DataOneofCase.BinaryData) { + output.WriteRawTag(50); + output.WriteBytes(BinaryData); + } + if (dataCase_ == DataOneofCase.TextData) { + output.WriteRawTag(58); + output.WriteString(TextData); + } + if (dataCase_ == DataOneofCase.ProtoData) { + output.WriteRawTag(66); + output.WriteMessage(ProtoData); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (Id.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Id); + } + if (Source.Length != 0) { + output.WriteRawTag(18); + output.WriteString(Source); + } + if (SpecVersion.Length != 0) { + output.WriteRawTag(26); + output.WriteString(SpecVersion); + } + if (Type.Length != 0) { + output.WriteRawTag(34); + output.WriteString(Type); + } + attributes_.WriteTo(ref output, _map_attributes_codec); + if (dataCase_ == DataOneofCase.BinaryData) { + output.WriteRawTag(50); + output.WriteBytes(BinaryData); + } + if (dataCase_ == DataOneofCase.TextData) { + output.WriteRawTag(58); + output.WriteString(TextData); + } + if (dataCase_ == DataOneofCase.ProtoData) { + output.WriteRawTag(66); + output.WriteMessage(ProtoData); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (Id.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Id); + } + if (Source.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Source); + } + if (SpecVersion.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(SpecVersion); + } + if (Type.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Type); + } + size += attributes_.CalculateSize(_map_attributes_codec); + if (dataCase_ == DataOneofCase.BinaryData) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(BinaryData); + } + if (dataCase_ == DataOneofCase.TextData) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TextData); + } + if (dataCase_ == DataOneofCase.ProtoData) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(ProtoData); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(CloudEvent other) { + if (other == null) { + return; + } + if (other.Id.Length != 0) { + Id = other.Id; + } + if (other.Source.Length != 0) { + Source = other.Source; + } + if (other.SpecVersion.Length != 0) { + SpecVersion = other.SpecVersion; + } + if (other.Type.Length != 0) { + Type = other.Type; + } + attributes_.Add(other.attributes_); + switch (other.DataCase) { + case DataOneofCase.BinaryData: + BinaryData = other.BinaryData; + break; + case DataOneofCase.TextData: + TextData = other.TextData; + break; + case DataOneofCase.ProtoData: + if (ProtoData == null) { + ProtoData = new global::Google.Protobuf.WellKnownTypes.Any(); + } + ProtoData.MergeFrom(other.ProtoData); + break; + } + + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Id = input.ReadString(); + break; + } + case 18: { + Source = input.ReadString(); + break; + } + case 26: { + SpecVersion = input.ReadString(); + break; + } + case 34: { + Type = input.ReadString(); + break; + } + case 42: { + attributes_.AddEntriesFrom(input, _map_attributes_codec); + break; + } + case 50: { + BinaryData = input.ReadBytes(); + break; + } + case 58: { + TextData = input.ReadString(); + break; + } + case 66: { + global::Google.Protobuf.WellKnownTypes.Any subBuilder = new global::Google.Protobuf.WellKnownTypes.Any(); + if (dataCase_ == DataOneofCase.ProtoData) { + subBuilder.MergeFrom(ProtoData); + } + input.ReadMessage(subBuilder); + ProtoData = subBuilder; + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + Id = input.ReadString(); + break; + } + case 18: { + Source = input.ReadString(); + break; + } + case 26: { + SpecVersion = input.ReadString(); + break; + } + case 34: { + Type = input.ReadString(); + break; + } + case 42: { + attributes_.AddEntriesFrom(ref input, _map_attributes_codec); + break; + } + case 50: { + BinaryData = input.ReadBytes(); + break; + } + case 58: { + TextData = input.ReadString(); + break; + } + case 66: { + global::Google.Protobuf.WellKnownTypes.Any subBuilder = new global::Google.Protobuf.WellKnownTypes.Any(); + if (dataCase_ == DataOneofCase.ProtoData) { + subBuilder.MergeFrom(ProtoData); + } + input.ReadMessage(subBuilder); + ProtoData = subBuilder; + break; + } + } + } + } + #endif + + #region Nested types + /// Container for nested types declared in the CloudEvent message type. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static partial class Types { + public sealed partial class CloudEventAttributeValue : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CloudEventAttributeValue()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::CloudNative.CloudEvents.V1.CloudEvent.Descriptor.NestedTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CloudEventAttributeValue() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CloudEventAttributeValue(CloudEventAttributeValue other) : this() { + switch (other.AttrCase) { + case AttrOneofCase.CeBoolean: + CeBoolean = other.CeBoolean; + break; + case AttrOneofCase.CeInteger: + CeInteger = other.CeInteger; + break; + case AttrOneofCase.CeString: + CeString = other.CeString; + break; + case AttrOneofCase.CeBytes: + CeBytes = other.CeBytes; + break; + case AttrOneofCase.CeUri: + CeUri = other.CeUri; + break; + case AttrOneofCase.CeUriRef: + CeUriRef = other.CeUriRef; + break; + case AttrOneofCase.CeTimestamp: + CeTimestamp = other.CeTimestamp.Clone(); + break; + } + + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CloudEventAttributeValue Clone() { + return new CloudEventAttributeValue(this); + } + + /// Field number for the "ce_boolean" field. + public const int CeBooleanFieldNumber = 1; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool CeBoolean { + get { return attrCase_ == AttrOneofCase.CeBoolean ? (bool) attr_ : false; } + set { + attr_ = value; + attrCase_ = AttrOneofCase.CeBoolean; + } + } + + /// Field number for the "ce_integer" field. + public const int CeIntegerFieldNumber = 2; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CeInteger { + get { return attrCase_ == AttrOneofCase.CeInteger ? (int) attr_ : 0; } + set { + attr_ = value; + attrCase_ = AttrOneofCase.CeInteger; + } + } + + /// Field number for the "ce_string" field. + public const int CeStringFieldNumber = 3; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string CeString { + get { return attrCase_ == AttrOneofCase.CeString ? (string) attr_ : ""; } + set { + attr_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + attrCase_ = AttrOneofCase.CeString; + } + } + + /// Field number for the "ce_bytes" field. + public const int CeBytesFieldNumber = 4; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString CeBytes { + get { return attrCase_ == AttrOneofCase.CeBytes ? (pb::ByteString) attr_ : pb::ByteString.Empty; } + set { + attr_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + attrCase_ = AttrOneofCase.CeBytes; + } + } + + /// Field number for the "ce_uri" field. + public const int CeUriFieldNumber = 5; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string CeUri { + get { return attrCase_ == AttrOneofCase.CeUri ? (string) attr_ : ""; } + set { + attr_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + attrCase_ = AttrOneofCase.CeUri; + } + } + + /// Field number for the "ce_uri_ref" field. + public const int CeUriRefFieldNumber = 6; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string CeUriRef { + get { return attrCase_ == AttrOneofCase.CeUriRef ? (string) attr_ : ""; } + set { + attr_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + attrCase_ = AttrOneofCase.CeUriRef; + } + } + + /// Field number for the "ce_timestamp" field. + public const int CeTimestampFieldNumber = 7; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Google.Protobuf.WellKnownTypes.Timestamp CeTimestamp { + get { return attrCase_ == AttrOneofCase.CeTimestamp ? (global::Google.Protobuf.WellKnownTypes.Timestamp) attr_ : null; } + set { + attr_ = value; + attrCase_ = value == null ? AttrOneofCase.None : AttrOneofCase.CeTimestamp; + } + } + + private object attr_; + /// Enum of possible cases for the "attr" oneof. + public enum AttrOneofCase { + None = 0, + CeBoolean = 1, + CeInteger = 2, + CeString = 3, + CeBytes = 4, + CeUri = 5, + CeUriRef = 6, + CeTimestamp = 7, + } + private AttrOneofCase attrCase_ = AttrOneofCase.None; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public AttrOneofCase AttrCase { + get { return attrCase_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void ClearAttr() { + attrCase_ = AttrOneofCase.None; + attr_ = null; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as CloudEventAttributeValue); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(CloudEventAttributeValue other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (CeBoolean != other.CeBoolean) return false; + if (CeInteger != other.CeInteger) return false; + if (CeString != other.CeString) return false; + if (CeBytes != other.CeBytes) return false; + if (CeUri != other.CeUri) return false; + if (CeUriRef != other.CeUriRef) return false; + if (!object.Equals(CeTimestamp, other.CeTimestamp)) return false; + if (AttrCase != other.AttrCase) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (attrCase_ == AttrOneofCase.CeBoolean) hash ^= CeBoolean.GetHashCode(); + if (attrCase_ == AttrOneofCase.CeInteger) hash ^= CeInteger.GetHashCode(); + if (attrCase_ == AttrOneofCase.CeString) hash ^= CeString.GetHashCode(); + if (attrCase_ == AttrOneofCase.CeBytes) hash ^= CeBytes.GetHashCode(); + if (attrCase_ == AttrOneofCase.CeUri) hash ^= CeUri.GetHashCode(); + if (attrCase_ == AttrOneofCase.CeUriRef) hash ^= CeUriRef.GetHashCode(); + if (attrCase_ == AttrOneofCase.CeTimestamp) hash ^= CeTimestamp.GetHashCode(); + hash ^= (int) attrCase_; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (attrCase_ == AttrOneofCase.CeBoolean) { + output.WriteRawTag(8); + output.WriteBool(CeBoolean); + } + if (attrCase_ == AttrOneofCase.CeInteger) { + output.WriteRawTag(16); + output.WriteInt32(CeInteger); + } + if (attrCase_ == AttrOneofCase.CeString) { + output.WriteRawTag(26); + output.WriteString(CeString); + } + if (attrCase_ == AttrOneofCase.CeBytes) { + output.WriteRawTag(34); + output.WriteBytes(CeBytes); + } + if (attrCase_ == AttrOneofCase.CeUri) { + output.WriteRawTag(42); + output.WriteString(CeUri); + } + if (attrCase_ == AttrOneofCase.CeUriRef) { + output.WriteRawTag(50); + output.WriteString(CeUriRef); + } + if (attrCase_ == AttrOneofCase.CeTimestamp) { + output.WriteRawTag(58); + output.WriteMessage(CeTimestamp); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (attrCase_ == AttrOneofCase.CeBoolean) { + output.WriteRawTag(8); + output.WriteBool(CeBoolean); + } + if (attrCase_ == AttrOneofCase.CeInteger) { + output.WriteRawTag(16); + output.WriteInt32(CeInteger); + } + if (attrCase_ == AttrOneofCase.CeString) { + output.WriteRawTag(26); + output.WriteString(CeString); + } + if (attrCase_ == AttrOneofCase.CeBytes) { + output.WriteRawTag(34); + output.WriteBytes(CeBytes); + } + if (attrCase_ == AttrOneofCase.CeUri) { + output.WriteRawTag(42); + output.WriteString(CeUri); + } + if (attrCase_ == AttrOneofCase.CeUriRef) { + output.WriteRawTag(50); + output.WriteString(CeUriRef); + } + if (attrCase_ == AttrOneofCase.CeTimestamp) { + output.WriteRawTag(58); + output.WriteMessage(CeTimestamp); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (attrCase_ == AttrOneofCase.CeBoolean) { + size += 1 + 1; + } + if (attrCase_ == AttrOneofCase.CeInteger) { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(CeInteger); + } + if (attrCase_ == AttrOneofCase.CeString) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(CeString); + } + if (attrCase_ == AttrOneofCase.CeBytes) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(CeBytes); + } + if (attrCase_ == AttrOneofCase.CeUri) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(CeUri); + } + if (attrCase_ == AttrOneofCase.CeUriRef) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(CeUriRef); + } + if (attrCase_ == AttrOneofCase.CeTimestamp) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(CeTimestamp); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(CloudEventAttributeValue other) { + if (other == null) { + return; + } + switch (other.AttrCase) { + case AttrOneofCase.CeBoolean: + CeBoolean = other.CeBoolean; + break; + case AttrOneofCase.CeInteger: + CeInteger = other.CeInteger; + break; + case AttrOneofCase.CeString: + CeString = other.CeString; + break; + case AttrOneofCase.CeBytes: + CeBytes = other.CeBytes; + break; + case AttrOneofCase.CeUri: + CeUri = other.CeUri; + break; + case AttrOneofCase.CeUriRef: + CeUriRef = other.CeUriRef; + break; + case AttrOneofCase.CeTimestamp: + if (CeTimestamp == null) { + CeTimestamp = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + } + CeTimestamp.MergeFrom(other.CeTimestamp); + break; + } + + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + CeBoolean = input.ReadBool(); + break; + } + case 16: { + CeInteger = input.ReadInt32(); + break; + } + case 26: { + CeString = input.ReadString(); + break; + } + case 34: { + CeBytes = input.ReadBytes(); + break; + } + case 42: { + CeUri = input.ReadString(); + break; + } + case 50: { + CeUriRef = input.ReadString(); + break; + } + case 58: { + global::Google.Protobuf.WellKnownTypes.Timestamp subBuilder = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + if (attrCase_ == AttrOneofCase.CeTimestamp) { + subBuilder.MergeFrom(CeTimestamp); + } + input.ReadMessage(subBuilder); + CeTimestamp = subBuilder; + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 8: { + CeBoolean = input.ReadBool(); + break; + } + case 16: { + CeInteger = input.ReadInt32(); + break; + } + case 26: { + CeString = input.ReadString(); + break; + } + case 34: { + CeBytes = input.ReadBytes(); + break; + } + case 42: { + CeUri = input.ReadString(); + break; + } + case 50: { + CeUriRef = input.ReadString(); + break; + } + case 58: { + global::Google.Protobuf.WellKnownTypes.Timestamp subBuilder = new global::Google.Protobuf.WellKnownTypes.Timestamp(); + if (attrCase_ == AttrOneofCase.CeTimestamp) { + subBuilder.MergeFrom(CeTimestamp); + } + input.ReadMessage(subBuilder); + CeTimestamp = subBuilder; + break; + } + } + } + } + #endif + + } + + } + #endregion + + } + + public sealed partial class CloudEventBatch : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new CloudEventBatch()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::CloudNative.CloudEvents.V1.ProtoSchemaReflection.Descriptor.MessageTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CloudEventBatch() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CloudEventBatch(CloudEventBatch other) : this() { + events_ = other.events_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public CloudEventBatch Clone() { + return new CloudEventBatch(this); + } + + /// Field number for the "events" field. + public const int EventsFieldNumber = 1; + private static readonly pb::FieldCodec _repeated_events_codec + = pb::FieldCodec.ForMessage(10, global::CloudNative.CloudEvents.V1.CloudEvent.Parser); + private readonly pbc::RepeatedField events_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pbc::RepeatedField Events { + get { return events_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as CloudEventBatch); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(CloudEventBatch other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if(!events_.Equals(other.events_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + hash ^= events_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + events_.WriteTo(output, _repeated_events_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + events_.WriteTo(ref output, _repeated_events_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + size += events_.CalculateSize(_repeated_events_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(CloudEventBatch other) { + if (other == null) { + return; + } + events_.Add(other.events_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + events_.AddEntriesFrom(input, _repeated_events_codec); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + events_.AddEntriesFrom(ref input, _repeated_events_codec); + break; + } + } + } + } + #endif + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/src/CloudNative.CloudEvents.Protobuf/ProtobufEventFormatter.cs b/src/CloudNative.CloudEvents.Protobuf/ProtobufEventFormatter.cs new file mode 100644 index 0000000..9d041cc --- /dev/null +++ b/src/CloudNative.CloudEvents.Protobuf/ProtobufEventFormatter.cs @@ -0,0 +1,400 @@ +// Copyright 2021 Cloud Native Foundation. +// Licensed under the Apache 2.0 license. +// See LICENSE file in the project root for full license information. + +using CloudNative.CloudEvents.Core; +using CloudNative.CloudEvents.V1; +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Text; +using System.Threading.Tasks; +using static CloudNative.CloudEvents.V1.CloudEvent; +using static CloudNative.CloudEvents.V1.CloudEvent.Types; +using static CloudNative.CloudEvents.V1.CloudEvent.Types.CloudEventAttributeValue; + +namespace CloudNative.CloudEvents.Protobuf +{ + // TODO: Derived type which expects to only receive protobuf message data with a particular message type, + // so is able to unpack it. + + /// + /// Formatter that implements the Protobuf Event Format, using the Google.Protobuf library for serialization. + /// + /// + /// + /// When encoding CloudEvents in structured mode, three kinds of data are supported, as indicated in the + /// event format. Text is stored in the field; binary data is stored + /// in the field; protobuf messages are stored in the + /// field. In the last case, the message is packed in an + /// message, to preserve information about which message is encoded, unless the message + /// is already an in which case it is stored directly. (This prevents "double-encoding" + /// when a CloudEvent is decoded and then re-encoded.) Attempts to serialize CloudEvents with any other data type + /// will fail. Derived classes can specialize all of this behavior by overriding + /// . + /// + /// + /// When decoding CloudEvents in structured mode, text and binary data payloads are represented as strings and byte + /// arrays respectively. Protobuf message payloads are represented using the wrapper, without + /// attempting to "unpack" the message. This avoids any requirement for the underlying message type to be + /// known by the application consuming the CloudEvent. (The data may be stored for later processing by another + /// application with more awareness, for example.) Derived classes can specialize all of this behavior by + /// overriding . + /// + /// + /// When encoding CloudEvent data in binary mode, this implementation only supports plain binary and text data. + /// (Even text data is only supported when the begins with "text/".) + /// While it might be expected that protobuf messages would be serialized into the binary mode data, there is + /// no clear standard as to whether they should be directly serialized, or packed into an + /// message first, and no standardized content type to use to distinguish these options. Users are encouraged + /// to either use structured mode where possible, or explicitly encode the data as a byte array first. Derived + /// classes can specialize this behavior by overriding . + /// + /// + /// When decoding CloudEvent data in binary mode, if the data content type begins with "text/" it is decoded as + /// a string, otherwise it is left as a byte array. Derived classes can specialize this behavior by overriding + /// . + /// + /// + public class ProtobufEventFormatter : CloudEventFormatter + { + /// + /// The default value for . This is the value used by Protobuf libraries + /// when no prefix is specifically provided. + /// + public const string DefaultTypeUrlPrefix = "type.googleapis.com"; + + private const string MediaTypeSuffix = "+protobuf"; + + private static readonly string StructuredMediaType = MimeUtilities.MediaType + MediaTypeSuffix; + private static readonly string BatchMediaType = MimeUtilities.BatchMediaType + MediaTypeSuffix; + + /// + /// The type URL prefix this event formatter uses when packing messages into . + /// The value is never null. Note: the type URL prefix is not used when the data within a CloudEvent + /// is already an Any message, as the message is propagated directly. + /// + public string TypeUrlPrefix { get; } + + private static readonly Dictionary protoToCloudEventAttributeType = + new Dictionary + { + { AttrOneofCase.CeBoolean, CloudEventAttributeType.Boolean }, + { AttrOneofCase.CeBytes, CloudEventAttributeType.Binary }, + { AttrOneofCase.CeInteger, CloudEventAttributeType.Integer }, + { AttrOneofCase.CeString, CloudEventAttributeType.String }, + { AttrOneofCase.CeTimestamp, CloudEventAttributeType.Timestamp }, + { AttrOneofCase.CeUri, CloudEventAttributeType.Uri }, + { AttrOneofCase.CeUriRef, CloudEventAttributeType.UriReference } + }; + + /// + /// Constructs an instance of the formatter, using a type URL prefix of + /// "type.googleapis.com" (the default for ). + /// + public ProtobufEventFormatter() : this(DefaultTypeUrlPrefix) + { + } + + /// + /// Constructs an instance of the formatter, using the specified type URL prefix + /// when packing messages. + /// + /// The type URL prefix to use when packing messages + /// into . Must not be null. + public ProtobufEventFormatter(string typeUrlPrefix) + { + TypeUrlPrefix = Validation.CheckNotNull(typeUrlPrefix, nameof(typeUrlPrefix)); + } + + /// + public override IReadOnlyList DecodeBatchModeMessage(ReadOnlyMemory body, ContentType? contentType, IEnumerable? extensionAttributes) => + DecodeBatchModeMessage(BinaryDataUtilities.AsStream(body), contentType, extensionAttributes); + + /// + public override void DecodeBinaryModeEventData(ReadOnlyMemory body, CloudEvent cloudEvent) + { + Validation.CheckNotNull(cloudEvent, nameof(cloudEvent)); + if (cloudEvent.DataContentType is string dataContentType && dataContentType.StartsWith("text/")) + { + Encoding encoding = MimeUtilities.GetEncoding(new ContentType(dataContentType)); + cloudEvent.Data = BinaryDataUtilities.GetString(body, encoding); + } + else + { + cloudEvent.Data = body.ToArray(); + } + } + + /// + public override CloudEvent DecodeStructuredModeMessage(ReadOnlyMemory body, ContentType? contentType, IEnumerable? extensionAttributes) => + DecodeStructuredModeMessage(BinaryDataUtilities.AsStream(body), contentType, extensionAttributes); + + /// + public override ReadOnlyMemory EncodeBatchModeMessage(IEnumerable cloudEvents, out ContentType contentType) + { + Validation.CheckNotNull(cloudEvents, nameof(cloudEvents)); + + contentType = new ContentType(BatchMediaType) + { + CharSet = Encoding.UTF8.WebName + }; + + var batch = new CloudEventBatch + { + Events = { cloudEvents.Select(cloudEvent => ConvertToProto(cloudEvent, nameof(cloudEvents))) } + }; + return batch.ToByteArray(); + } + + // TODO: Put the boiler-plate code here into CloudEventFormatter + + /// + public override ReadOnlyMemory EncodeBinaryModeEventData(CloudEvent cloudEvent) + { + Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent)); + + if (cloudEvent.Data is null) + { + return Array.Empty(); + } + if (cloudEvent.DataContentType is string dataContentType && dataContentType.StartsWith("text/") && cloudEvent.Data is string text) + { + ContentType contentType = new ContentType(dataContentType); + return MimeUtilities.GetEncoding(contentType).GetBytes(text); + } + if (cloudEvent.Data is byte[] bytes) + { + return bytes; + } + throw new ArgumentException($"{nameof(ProtobufEventFormatter)} cannot serialize data of type {cloudEvent.Data.GetType()} with content type '{cloudEvent.DataContentType}'"); + } + + /// + public override ReadOnlyMemory EncodeStructuredModeMessage(CloudEvent cloudEvent, out ContentType contentType) + { + var proto = ConvertToProto(cloudEvent, nameof(cloudEvent)); + contentType = new ContentType(StructuredMediaType) + { + CharSet = Encoding.UTF8.WebName + }; + return proto.ToByteArray(); + } + + /// + public override IReadOnlyList DecodeBatchModeMessage(Stream body, ContentType? contentType, IEnumerable? extensionAttributes) + { + Validation.CheckNotNull(body, nameof(body)); + var batchProto = CloudEventBatch.Parser.ParseFrom(body); + return batchProto.Events.Select(proto => ConvertFromProto(proto, extensionAttributes, nameof(body))).ToList(); + } + + /// + public override CloudEvent DecodeStructuredModeMessage(Stream messageBody, ContentType? contentType, IEnumerable? extensionAttributes) + { + Validation.CheckNotNull(messageBody, nameof(messageBody)); + return ConvertFromProto(V1.CloudEvent.Parser.ParseFrom(messageBody), extensionAttributes, nameof(messageBody)); + } + + /// + /// Converts the given protobuf representation of a CloudEvent into an SDK representation. + /// + /// The protobuf representation of a CloudEvent. Must not be null. + /// The extension attributes to use when populating the CloudEvent. May be null. + /// The SDK representation of the CloudEvent. + public CloudEvent ConvertFromProto(V1.CloudEvent proto, IEnumerable? extensionAttributes) => + ConvertFromProto(Validation.CheckNotNull(proto, nameof(proto)), extensionAttributes, nameof(proto)); + + private CloudEvent ConvertFromProto(V1.CloudEvent proto, IEnumerable? extensionAttributes, string paramName) + { + var specVersion = CloudEventsSpecVersion.FromVersionId(proto.SpecVersion) + ?? throw new ArgumentException($"Unsupported CloudEvents spec version '{proto.SpecVersion}'", paramName); + + var cloudEvent = new CloudEvent(specVersion, extensionAttributes) + { + Id = proto.Id, + Source = (Uri) specVersion.SourceAttribute.Parse(proto.Source), + Type = proto.Type + }; + foreach (var pair in proto.Attributes) + { + if (!protoToCloudEventAttributeType.TryGetValue(pair.Value.AttrCase, out var attrTypeFromProto)) + { + // Note: impossible to cover in tests + throw new ArgumentException($"Unhandled protobuf attribute case: {pair.Value.AttrCase}", paramName); + } + + // If we've already got an extension attribute specified for this name, + // we validate against it and require the value in the proto to have the right + // type. Otherwise, we create a new extension attribute of the correct type. + var attr = cloudEvent.GetAttribute(pair.Key); + if (attr is null) + { + attr = CloudEventAttribute.CreateExtension(pair.Key, attrTypeFromProto); + } + // Note: if CloudEvents spec version 2.0 contains different required attributes, we may want to + // change exactly how this is specified. For the moment, this is the simplest way of implementing the requirement. + else if (attr.IsRequired) + { + // The required attributes are all specified as proto fields. + // They can't appear in the Attributes repeated field as well. + throw new ArgumentException( + $"Attribute '{attr.Name}' is a required attribute, and must only be specified via the top-level proto field."); + } + else if (attr.Type != attrTypeFromProto) + { + // This prevents any type changes, even those which might validate correctly + // otherwise (e.g. between Uri and UriRef). + throw new ArgumentException( + $"Attribute '{attr.Name}' was specified with type '{attr.Type}', but has type '{attrTypeFromProto}' in the protobuf representation."); + } + + // Note: the indexer performs validation. + cloudEvent[attr] = pair.Value.AttrCase switch + { + AttrOneofCase.CeBoolean => pair.Value.CeBoolean, + AttrOneofCase.CeBytes => pair.Value.CeBytes.ToByteArray(), + AttrOneofCase.CeInteger => pair.Value.CeInteger, + AttrOneofCase.CeString => pair.Value.CeString, + AttrOneofCase.CeTimestamp => pair.Value.CeTimestamp.ToDateTimeOffset(), + AttrOneofCase.CeUri => CloudEventAttributeType.Uri.Parse(pair.Value.CeUri), + AttrOneofCase.CeUriRef => CloudEventAttributeType.UriReference.Parse(pair.Value.CeUriRef), + _ => throw new ArgumentException($"Unhandled protobuf attribute case: {pair.Value.AttrCase}") + }; + } + + DecodeStructuredModeData(proto, cloudEvent); + + return Validation.CheckCloudEventArgument(cloudEvent, paramName); + } + + /// + /// Decodes the "data" property provided within a structured-mode message, + /// populating the property accordingly. + /// + /// + /// + /// This implementation simply converts binary data to a byte array, leaves proto data + /// as an , and converts text data to a string. + /// + /// + /// Override this method to provide more specialized conversions, such as to use + /// instead of a byte array, or to "unwrap" the proto data to generated code. + /// + /// + /// The protobuf representation of the CloudEvent. Will not be null. + /// The event being decoded. This should not be modified except to + /// populate the property, but may be used to provide extra + /// information such as the data content type. Will not be null. + /// The data to populate in the property. + protected virtual void DecodeStructuredModeData(V1.CloudEvent proto, CloudEvent cloudEvent) => + cloudEvent.Data = proto.DataCase switch + { + DataOneofCase.BinaryData => proto.BinaryData.ToByteArray(), + DataOneofCase.ProtoData => proto.ProtoData, + DataOneofCase.TextData => proto.TextData, + DataOneofCase.None => null, + // Note: impossible to cover in tests + _ => throw new ArgumentException($"Unhandled protobuf data case: {proto.DataCase}") + }; + + /// + /// Encodes structured (or batch) mode data within a CloudEvent, storing it in the specified . + /// + /// The CloudEvent being encoded, which will have a non-null value for + /// its property. + /// The protobuf representation of the CloudEvent, which will be non-null. + protected virtual void EncodeStructuredModeData(CloudEvent cloudEvent, V1.CloudEvent proto) + { + switch (cloudEvent.Data) + { + case IMessage message: + proto.ProtoData = message is Any any ? any : Any.Pack(message, TypeUrlPrefix); + break; + case string text: + proto.TextData = text; + break; + case byte[] binary: + proto.BinaryData = ByteString.CopyFrom(binary); + break; + default: + throw new ArgumentException($"{nameof(ProtobufEventFormatter)} cannot serialize data of type {cloudEvent.Data!.GetType()}"); + } + } + + /// + /// Converts the given SDK representation of a CloudEvent to a protobuf representation. + /// + /// The CloudEvent to convert. Must not be null, and must be a valid CloudEvent. + /// The protobuf representation of the CloudEvent. + public V1.CloudEvent ConvertToProto(CloudEvent cloudEvent) => ConvertToProto(cloudEvent, nameof(cloudEvent)); + + private V1.CloudEvent ConvertToProto(CloudEvent cloudEvent, string paramName) + { + Validation.CheckCloudEventArgument(cloudEvent, paramName); + var specVersion = cloudEvent.SpecVersion; + var proto = new V1.CloudEvent + { + Id = cloudEvent.Id, + // Source is a required attribute, and we've validated the CloudEvent, + // so it really should be non-null. + Source = specVersion.SourceAttribute.Format(cloudEvent.Source!), + Type = cloudEvent.Type, + SpecVersion = cloudEvent.SpecVersion.VersionId + }; + + foreach (var pair in cloudEvent.GetPopulatedAttributes()) + { + var attr = pair.Key; + // Skip attributes already handled above. + if (attr == specVersion.IdAttribute || + attr == specVersion.SourceAttribute || + attr == specVersion.TypeAttribute) + { + continue; + } + + var value = new CloudEventAttributeValue(); + switch (CloudEventAttributeTypes.GetOrdinal(attr.Type)) + { + case CloudEventAttributeTypeOrdinal.Binary: + value.CeBytes = ByteString.CopyFrom((byte[]) pair.Value); + break; + case CloudEventAttributeTypeOrdinal.Boolean: + value.CeBoolean = (bool) pair.Value; + break; + case CloudEventAttributeTypeOrdinal.Integer: + value.CeInteger = (int) pair.Value; + break; + case CloudEventAttributeTypeOrdinal.String: + value.CeString = (string) pair.Value; + break; + case CloudEventAttributeTypeOrdinal.Timestamp: + value.CeTimestamp = Timestamp.FromDateTimeOffset((DateTimeOffset) pair.Value); + break; + case CloudEventAttributeTypeOrdinal.Uri: + value.CeUri = attr.Format(pair.Value); + break; + case CloudEventAttributeTypeOrdinal.UriReference: + value.CeUriRef = attr.Format(pair.Value); + break; + default: + // Note: impossible to cover in tests + throw new ArgumentException($"Unhandled attribute type: {attr.Type}"); + } + proto.Attributes.Add(attr.Name, value); + } + + if (cloudEvent.Data is object) + { + EncodeStructuredModeData(cloudEvent, proto); + } + + return proto; + } + } +} diff --git a/src/CloudNative.CloudEvents.Protobuf/README.md b/src/CloudNative.CloudEvents.Protobuf/README.md new file mode 100644 index 0000000..6ace1fd --- /dev/null +++ b/src/CloudNative.CloudEvents.Protobuf/README.md @@ -0,0 +1,6 @@ +# CloudNative.CloudEvents.Protobuf + +This implements the Protobuf Event Format for CloudEvents. + +The [../../generate_protos.sh]() script is used to generate the C# code from the +spec, as well as any messages used for testing. diff --git a/test/CloudNative.CloudEvents.UnitTests/CloudNative.CloudEvents.UnitTests.csproj b/test/CloudNative.CloudEvents.UnitTests/CloudNative.CloudEvents.UnitTests.csproj index 2f241a3..60ad5df 100644 --- a/test/CloudNative.CloudEvents.UnitTests/CloudNative.CloudEvents.UnitTests.csproj +++ b/test/CloudNative.CloudEvents.UnitTests/CloudNative.CloudEvents.UnitTests.csproj @@ -24,6 +24,7 @@ + diff --git a/test/CloudNative.CloudEvents.UnitTests/Protobuf/ProtobufEventFormatterTest.cs b/test/CloudNative.CloudEvents.UnitTests/Protobuf/ProtobufEventFormatterTest.cs new file mode 100644 index 0000000..087c2da --- /dev/null +++ b/test/CloudNative.CloudEvents.UnitTests/Protobuf/ProtobufEventFormatterTest.cs @@ -0,0 +1,713 @@ +// Copyright 2022 Cloud Native Foundation. +// Licensed under the Apache 2.0 license. +// See LICENSE file in the project root for full license information. + +using Google.Protobuf; +using Google.Protobuf.Collections; +using Google.Protobuf.WellKnownTypes; +using System; +using System.IO; +using System.Net.Mime; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static CloudNative.CloudEvents.UnitTests.TestHelpers; +using static CloudNative.CloudEvents.V1.CloudEvent.Types; + +namespace CloudNative.CloudEvents.Protobuf.UnitTests +{ + /// + /// Tests for ProtobufEventFormatter. Note that most tests for encoding/decoding of + /// structured mode events (and batches) are performed via the public methods that + /// perform proto/CloudEvent conversions - the regular event formatter methods are + /// only wrappers around those, covered by minimal tests here. + /// + public class ProtobufEventFormatterTest + { + private static readonly ContentType s_protobufCloudEventContentType = new ContentType("application/cloudevents+protobuf"); + private static readonly ContentType s_protobufCloudEventBatchContentType = new ContentType("application/cloudevents-batch+protobuf"); + + [Fact] + public void EncodeStructuredModeMessage_Minimal() + { + var cloudEvent = new CloudEvent(CloudEventsSpecVersion.V1_0) + { + Id = "event-id", + Source = new Uri("https://event-source"), + Type = "event-type", + }; + + var encoded = new ProtobufEventFormatter().EncodeStructuredModeMessage(cloudEvent, out var contentType); + Assert.Equal("application/cloudevents+protobuf; charset=utf-8", contentType.ToString()); + var actualProto = V1.CloudEvent.Parser.ParseFrom(encoded.ToArray()); + + var expectedProto = new V1.CloudEvent + { + SpecVersion = "1.0", + Id = "event-id", + Source = "https://event-source", + Type = "event-type" + }; + Assert.Equal(expectedProto, actualProto); + } + + /// + /// A simple test that populates all known v1.0 attributes, so we don't need to test that + /// aspect in the future. + /// + [Fact] + public void ConvertToProto_V1Attributes() + { + var cloudEvent = new CloudEvent(CloudEventsSpecVersion.V1_0) + { + Data = "text", + DataContentType = "text/plain", + DataSchema = new Uri("https://data-schema"), + Id = "event-id", + Source = new Uri("https://event-source"), + Subject = "event-subject", + Time = new DateTimeOffset(2021, 2, 19, 12, 34, 56, 789, TimeSpan.FromHours(1)), + Type = "event-type" + }; + + var actualProto = new ProtobufEventFormatter().ConvertToProto(cloudEvent); + var expectedProto = new V1.CloudEvent + { + SpecVersion = "1.0", + Id = "event-id", + Source = "https://event-source", + Type = "event-type", + Attributes = + { + { "datacontenttype", StringAttribute("text/plain") }, + { "dataschema", UriAttribute("https://data-schema") }, + { "subject", StringAttribute("event-subject") }, + // Deliberately not reusing cloudEvent.Time: this demonstrates that only the instant in time + // is relevant, not the UTC offset. + { "time", TimestampAttribute(new DateTimeOffset(2021, 2, 19, 11, 34, 56, 789, TimeSpan.Zero)) } + }, + TextData = "text" + }; + Assert.Equal(expectedProto, actualProto); + } + + [Fact] + public void ConvertToProto_AllAttributeTypes() + { + var cloudEvent = new CloudEvent(AllTypesExtensions) + { + ["binary"] = SampleBinaryData, + ["boolean"] = true, + ["integer"] = 10, + ["string"] = "text", + ["timestamp"] = SampleTimestamp, + ["uri"] = SampleUri, + ["urireference"] = SampleUriReference + }; + // We're not going to check these. + cloudEvent.PopulateRequiredAttributes(); + + var proto = new ProtobufEventFormatter().ConvertToProto(cloudEvent); + + var expectedAttributes = new MapField + { + { "binary", BinaryAttribute(SampleBinaryData) }, + { "boolean", BooleanAttribute(true) }, + { "integer", IntegerAttribute(10) }, + { "string", StringAttribute("text") }, + { "timestamp", TimestampAttribute(SampleTimestamp) }, + { "uri", UriAttribute(SampleUriText) }, + { "urireference", UriRefAttribute(SampleUriReferenceText) } + }; + Assert.Equal(proto.Attributes, expectedAttributes); + } + + [Fact] + public void ConvertToProto_NoData() + { + var cloudEvent = new CloudEvent().PopulateRequiredAttributes(); + var proto = new ProtobufEventFormatter().ConvertToProto(cloudEvent); + Assert.Equal(V1.CloudEvent.DataOneofCase.None, proto.DataCase); + } + + [Fact] + public void ConvertToProto_TextData() + { + var cloudEvent = new CloudEvent { Data = "text" }.PopulateRequiredAttributes(); + var proto = new ProtobufEventFormatter().ConvertToProto(cloudEvent); + Assert.Equal("text", proto.TextData); + } + + [Fact] + public void ConvertToProto_BinaryData() + { + var cloudEvent = new CloudEvent { Data = SampleBinaryData }.PopulateRequiredAttributes(); + var proto = new ProtobufEventFormatter().ConvertToProto(cloudEvent); + Assert.Equal(SampleBinaryData, proto.BinaryData.ToByteArray()); + } + + [Fact] + public void ConvertToProto_MessageData() + { + var data = new PayloadData1 { Name = "test" }; + var cloudEvent = new CloudEvent { Data = data }.PopulateRequiredAttributes(); + var proto = new ProtobufEventFormatter().ConvertToProto(cloudEvent); + Assert.Equal(Any.Pack(data), proto.ProtoData); + } + + [Fact] + public void ConvertToProto_MessageData_AlreadyPacked() + { + var data = new PayloadData1 { Name = "test" }; + var packedData = Any.Pack(data); + var cloudEvent = new CloudEvent { Data = packedData }.PopulateRequiredAttributes(); + var proto = new ProtobufEventFormatter().ConvertToProto(cloudEvent); + // This verifies that the formatter doesn't "double-encode". + Assert.Equal(packedData, proto.ProtoData); + } + + [Fact] + public void ConvertToProto_MessageData_CustomTypeUrlPrefix() + { + string typeUrlPrefix = "cloudevents.io/xyz"; + var data = new PayloadData1 { Name = "test" }; + var cloudEvent = new CloudEvent { Data = data }.PopulateRequiredAttributes(); + var proto = new ProtobufEventFormatter(typeUrlPrefix).ConvertToProto(cloudEvent); + Assert.Equal(Any.Pack(data, typeUrlPrefix), proto.ProtoData); + } + + [Fact] + public void ConvertToProto_InvalidData() + { + var cloudEvent = new CloudEvent { Data = new object() }.PopulateRequiredAttributes(); + var formatter = new ProtobufEventFormatter(); + Assert.Throws(() => formatter.ConvertToProto(cloudEvent)); + } + + [Fact] + public void EncodeBinaryModeData_Bytes() + { + var cloudEvent = new CloudEvent + { + Data = SampleBinaryData + }.PopulateRequiredAttributes(); + var formatter = new ProtobufEventFormatter(); + + var result = formatter.EncodeBinaryModeEventData(cloudEvent); + Assert.Equal(SampleBinaryData, result.ToArray()); + } + + [Theory] + [InlineData("utf-8")] + [InlineData("iso-8859-1")] + [InlineData(null)] + public void EncodeBinaryModeData_String_TextContentType(string charset) + { + string text = "caf\u00e9"; // Valid in both UTF-8 and ISO-8859-1, but with different representations + var encoding = charset is null ? Encoding.UTF8 : Encoding.GetEncoding(charset); + string contentType = charset is null ? "text/plain" : $"text/plain; charset={charset}"; + var cloudEvent = new CloudEvent + { + Data = text, + DataContentType = contentType + }.PopulateRequiredAttributes(); + + var formatter = new ProtobufEventFormatter(); + var result = formatter.EncodeBinaryModeEventData(cloudEvent); + Assert.Equal(encoding.GetBytes(text), result.ToArray()); + } + + [Fact] + public void EncodeBinaryModeData_String_NonTextContentType() + { + var cloudEvent = new CloudEvent + { + Data = "text", + DataContentType = "application/json" + }.PopulateRequiredAttributes(); + var formatter = new ProtobufEventFormatter(); + Assert.Throws(() => formatter.EncodeBinaryModeEventData(cloudEvent)); + } + + [Fact] + public void EncodeBinaryModeData_ProtoMessage() + { + var cloudEvent = new CloudEvent + { + Data = new PayloadData1 { Name = "fail" }, + DataContentType = "application/protobuf" + }.PopulateRequiredAttributes(); + var formatter = new ProtobufEventFormatter(); + // See summary documentation for ProtobufEventFormatter for the reasoning for this + Assert.Throws(() => formatter.EncodeBinaryModeEventData(cloudEvent)); + } + + [Fact] + public void EncodeBinaryModeData_ArbitraryObject() + { + var cloudEvent = new CloudEvent + { + Data = new object(), + DataContentType = "application/octet-stream" + }.PopulateRequiredAttributes(); + var formatter = new ProtobufEventFormatter(); + // See summary documentation for ProtobufEventFormatter for the reasoning for this + Assert.Throws(() => formatter.EncodeBinaryModeEventData(cloudEvent)); + } + + [Fact] + public void EncodeBinaryModeData_NoData() + { + var cloudEvent = new CloudEvent().PopulateRequiredAttributes(); + var formatter = new ProtobufEventFormatter(); + Assert.Empty(formatter.EncodeBinaryModeEventData(cloudEvent).ToArray()); + } + + // Just a single test for the code that parses asynchronously... the guts are all the same. + [Fact] + public async Task DecodeStructuredModeMessageAsync_Minimal() + { + var proto = CreateMinimalCloudEventProto(); + byte[] bytes = proto.ToByteArray(); + var stream = new MemoryStream(bytes); + var formatter = new ProtobufEventFormatter(); + var cloudEvent = await formatter.DecodeStructuredModeMessageAsync(stream, s_protobufCloudEventContentType, null); + Assert.Equal("test-type", cloudEvent.Type); + Assert.Equal("test-id", cloudEvent.Id); + Assert.Equal(SampleUri, cloudEvent.Source); + } + + [Fact] + public void DecodeStructuredModeMessage_Minimal() + { + var proto = CreateMinimalCloudEventProto(); + byte[] bytes = proto.ToByteArray(); + var stream = new MemoryStream(bytes); + var formatter = new ProtobufEventFormatter(); + var cloudEvent = formatter.DecodeStructuredModeMessage(stream, s_protobufCloudEventContentType, null); + Assert.Equal("test-type", cloudEvent.Type); + Assert.Equal("test-id", cloudEvent.Id); + Assert.Equal(SampleUri, cloudEvent.Source); + } + + [Fact] + public void EncodeBatchModeMessage_Empty() + { + var formatter = new ProtobufEventFormatter(); + var bytes = formatter.EncodeBatchModeMessage(new CloudEvent[0], out var contentType); + Assert.Equal("application/cloudevents-batch+protobuf; charset=utf-8", contentType.ToString()); + var batch = V1.CloudEventBatch.Parser.ParseFrom(bytes.ToArray()); + Assert.Empty(batch.Events); + } + + [Fact] + public void EncodeBatchModeMessage_TwoEvents() + { + var event1 = new CloudEvent + { + Id = "event1", + Type = "type1", + Source = new Uri("//event-source1", UriKind.RelativeOrAbsolute), + Data = "simple text", + DataContentType = "text/plain" + }; + var event2 = new CloudEvent + { + Id = "event2", + Type = "type2", + Source = new Uri("//event-source2", UriKind.RelativeOrAbsolute), + }; + + var cloudEvents = new[] { event1, event2 }; + var formatter = new ProtobufEventFormatter(); + var bytes = formatter.EncodeBatchModeMessage(cloudEvents, out var contentType); + Assert.Equal("application/cloudevents-batch+protobuf; charset=utf-8", contentType.ToString()); + var actualBatch = V1.CloudEventBatch.Parser.ParseFrom(bytes.ToArray()); + var expectedBatch = new V1.CloudEventBatch + { + Events = + { + new V1.CloudEvent + { + SpecVersion = "1.0", + Type = "type1", + Id = "event1", + Source = "//event-source1", + TextData = "simple text", + Attributes = { { "datacontenttype", StringAttribute("text/plain") } } + }, + new V1.CloudEvent + { + SpecVersion = "1.0", + Type = "type2", + Id = "event2", + Source = "//event-source2" + } + } + }; + Assert.Equal(expectedBatch, actualBatch); + } + + [Fact] + public void EncodeBatchModeMessage_Invalid() + { + var formatter = new ProtobufEventFormatter(); + // Invalid CloudEvent + Assert.Throws(() => formatter.EncodeBatchModeMessage(new[] { new CloudEvent() }, out _)); + // Null argument + Assert.Throws(() => formatter.EncodeBatchModeMessage(null!, out _)); + // Null value within the argument. Arguably this should throw ArgumentException instead of + // ArgumentNullException, but it's unlikely to cause confusion. + Assert.Throws(() => formatter.EncodeBatchModeMessage(new CloudEvent[1], out _)); + } + + [Fact] + public void ConvertFromProto_V1Attributes() + { + var proto = new V1.CloudEvent + { + SpecVersion = "1.0", + Type = "test-type", + Id = "test-id", + TextData = "text", + Source = "//event-source", + Attributes = + { + { "datacontenttype", StringAttribute("text/plain") }, + { "dataschema", UriAttribute("https://data-schema") }, + { "subject", StringAttribute("event-subject") }, + { "time", TimestampAttribute(SampleTimestamp) } + } + }; + var cloudEvent = new ProtobufEventFormatter().ConvertFromProto(proto, null); + Assert.Equal(CloudEventsSpecVersion.V1_0, cloudEvent.SpecVersion); + Assert.Equal("test-type", cloudEvent.Type); + Assert.Equal("test-id", cloudEvent.Id); + Assert.Equal("text/plain", cloudEvent.DataContentType); + Assert.Equal(new Uri("https://data-schema"), cloudEvent.DataSchema); + Assert.Equal("event-subject", cloudEvent.Subject); + Assert.Equal(new Uri("//event-source", UriKind.RelativeOrAbsolute), cloudEvent.Source); + // The protobuf timestamp loses the offset information, but is still the correct instant. + AssertTimestampsEqual(SampleTimestamp.ToUniversalTime(), cloudEvent.Time); + } + + [Theory] + // These are required, so have to be specified in the dedicated protobuf field + [InlineData("id")] + [InlineData("type")] + [InlineData("source")] + // These are generally invalid attribute names + [InlineData("specversion")] + [InlineData("a b c")] + [InlineData("ABC")] + public void ConvertFromProto_InvalidAttributeNames(string attributeName) + { + var proto = CreateMinimalCloudEventProto(); + proto.Attributes.Add(attributeName, StringAttribute("value")); + var formatter = new ProtobufEventFormatter(); + Assert.Throws(() => formatter.ConvertFromProto(proto, null)); + } + + [Fact] + public void ConvertFromProto_AllAttributeTypes() + { + var proto = CreateMinimalCloudEventProto(); + proto.Attributes.Add("binary", BinaryAttribute(SampleBinaryData)); + proto.Attributes.Add("boolean", BooleanAttribute(true)); + proto.Attributes.Add("integer", IntegerAttribute(10)); + proto.Attributes.Add("string", StringAttribute("text")); + proto.Attributes.Add("timestamp", TimestampAttribute(SampleTimestamp)); + proto.Attributes.Add("uri", UriAttribute(SampleUriText)); + proto.Attributes.Add("urireference", UriRefAttribute(SampleUriReferenceText)); + + var cloudEvent = new ProtobufEventFormatter().ConvertFromProto(proto, null); + Assert.Equal(SampleBinaryData, cloudEvent["binary"]); + Assert.True((bool) cloudEvent["boolean"]!); + Assert.Equal(10, cloudEvent["integer"]); + Assert.Equal("text", cloudEvent["string"]); + // The protobuf timestamp loses the offset information, but is still the correct instant. + AssertTimestampsEqual(SampleTimestamp.ToUniversalTime(), (DateTimeOffset) cloudEvent["timestamp"]!); + Assert.Equal(SampleUri, cloudEvent["uri"]); + Assert.Equal(SampleUriReference, cloudEvent["urireference"]); + } + + [Fact] + public void ConvertFromProto_NoData() + { + var proto = CreateMinimalCloudEventProto(); + var cloudEvent = new ProtobufEventFormatter().ConvertFromProto(proto, null); + Assert.Null(cloudEvent.Data); + } + + [Fact] + public void ConvertFromProto_TextData() + { + var proto = CreateMinimalCloudEventProto(); + proto.TextData = "text"; + var cloudEvent = new ProtobufEventFormatter().ConvertFromProto(proto, null); + Assert.Equal("text", cloudEvent.Data); + } + + [Fact] + public void ConvertFromProto_BinaryData() + { + var proto = CreateMinimalCloudEventProto(); + proto.BinaryData = ByteString.CopyFrom(SampleBinaryData); + var cloudEvent = new ProtobufEventFormatter().ConvertFromProto(proto, null); + Assert.Equal(SampleBinaryData, cloudEvent.Data); + } + + [Fact] + public void ConvertFromProto_MessageData() + { + var message = new PayloadData1 { Name = "testing" }; + var proto = CreateMinimalCloudEventProto(); + proto.ProtoData = Any.Pack(message); + var cloudEvent = new ProtobufEventFormatter().ConvertFromProto(proto, null); + // Note: this isn't unpacked automatically. + Assert.Equal(Any.Pack(message), cloudEvent.Data); + } + + [Fact] + public void ConvertFromProto_UnspecifiedExtensionAttributes() + { + var proto = CreateMinimalCloudEventProto(); + proto.Attributes.Add("xyz", StringAttribute("abc")); + var cloudEvent = new ProtobufEventFormatter().ConvertFromProto(proto, null); + Assert.Equal("abc", cloudEvent["xyz"]); + } + + [Fact] + public void ConvertFromProto_SpecifiedExtensionAttributes_Valid() + { + var attribute = CloudEventAttribute.CreateExtension("xyz", CloudEventAttributeType.String); + var proto = CreateMinimalCloudEventProto(); + proto.Attributes.Add(attribute.Name, StringAttribute("abc")); + var cloudEvent = new ProtobufEventFormatter().ConvertFromProto(proto, new[] { attribute }); + Assert.Equal("abc", cloudEvent[attribute]); + } + + [Fact] + public void ConvertFromProto_SpecifiedExtensionAttributes_UnexpectedType() + { + var attribute = CloudEventAttribute.CreateExtension("xyz", CloudEventAttributeType.UriReference); + var proto = CreateMinimalCloudEventProto(); + proto.Attributes.Add(attribute.Name, UriAttribute("https://xyz")); + // Even though the value would be valid as a URI reference, we fail because + // the type in the proto message is not the same as the type we've specified in the method argument. + Assert.Throws(() => new ProtobufEventFormatter().ConvertFromProto(proto, new[] { attribute })); + } + + [Fact] + public void ConvertFromProto_SpecifiedExtensionAttributes_InvalidValue() + { + var attribute = CloudEventAttribute.CreateExtension("xyz", CloudEventAttributeType.Integer, ValidateValue); + var proto = CreateMinimalCloudEventProto(); + proto.Attributes.Add(attribute.Name, IntegerAttribute(1000)); + var exception = Assert.Throws(() => new ProtobufEventFormatter().ConvertFromProto(proto, new[] { attribute })); + Assert.Equal("Boom!", exception!.InnerException!.Message); + + void ValidateValue(object value) + { + if ((int) value > 100) + { + throw new Exception("Boom!"); + } + } + } + + [Fact] + public void ConvertFromProto_Invalid_NoSpecVersion() + { + var proto = CreateMinimalCloudEventProto(); + proto.SpecVersion = ""; + var exception = Assert.Throws(() => new ProtobufEventFormatter().ConvertFromProto(proto, null)); + } + + [Fact] + public void ConvertFromProto_Invalid_NoType() + { + var proto = CreateMinimalCloudEventProto(); + proto.SpecVersion = ""; + var exception = Assert.Throws(() => new ProtobufEventFormatter().ConvertFromProto(proto, null)); + } + + [Fact] + public void ConvertFromProto_Invalid_NoId() + { + var proto = CreateMinimalCloudEventProto(); + proto.Id = ""; + var exception = Assert.Throws(() => new ProtobufEventFormatter().ConvertFromProto(proto, null)); + } + + [Fact] + public void ConvertFromProto_Invalid_NoSource() + { + var proto = CreateMinimalCloudEventProto(); + proto.Source = ""; + var exception = Assert.Throws(() => new ProtobufEventFormatter().ConvertFromProto(proto, null)); + } + + [Theory] + [InlineData("utf-8")] + [InlineData("iso-8859-1")] + [InlineData(null)] + public void DecodeBinaryModeEventData_Text(string charset) + { + string text = "caf\u00e9"; // Valid in both UTF-8 and ISO-8859-1, but with different representations + var encoding = charset is null ? Encoding.UTF8 : Encoding.GetEncoding(charset); + var bytes = encoding.GetBytes(text); + string contentType = charset is null ? "text/plain" : $"text/plain; charset={charset}"; + var data = DecodeBinaryModeEventData(bytes, contentType); + string actualText = Assert.IsType(data); + Assert.Equal(text, actualText); + } + + [Theory] + [InlineData("application/json")] + [InlineData(null)] + public void DecodeBinaryModeData_NonTextContentType(string contentType) + { + var bytes = Encoding.UTF8.GetBytes("{}"); + var data = DecodeBinaryModeEventData(bytes, contentType); + byte[] actualBytes = Assert.IsType(data); + Assert.Equal(bytes, actualBytes); + } + + [Fact] + public void DecodeBatchMode_Minimal() + { + var batchProto = new V1.CloudEventBatch + { + Events = { CreateMinimalCloudEventProto() } + }; + byte[] bytes = batchProto.ToByteArray(); + var stream = new MemoryStream(bytes); + var formatter = new ProtobufEventFormatter(); + var cloudEvents = formatter.DecodeBatchModeMessage(stream, s_protobufCloudEventBatchContentType, null); + var cloudEvent = Assert.Single(cloudEvents); + Assert.Equal("test-type", cloudEvent.Type); + Assert.Equal("test-id", cloudEvent.Id); + Assert.Equal(SampleUri, cloudEvent.Source); + } + + // Just a single test for the code that parses asynchronously... the guts are all the same. + [Fact] + public async Task DecodeBatchModeMessageAsync_Minimal() + { + var batchProto = new V1.CloudEventBatch + { + Events = { CreateMinimalCloudEventProto() } + }; + byte[] bytes = batchProto.ToByteArray(); + var stream = new MemoryStream(bytes); + var formatter = new ProtobufEventFormatter(); + var cloudEvents = await formatter.DecodeBatchModeMessageAsync(stream, s_protobufCloudEventBatchContentType, null); + var cloudEvent = Assert.Single(cloudEvents); + Assert.Equal("test-type", cloudEvent.Type); + Assert.Equal("test-id", cloudEvent.Id); + Assert.Equal(SampleUri, cloudEvent.Source); + } + + [Fact] + public void DecodeBatchMode_Empty() + { + var batchProto = new V1.CloudEventBatch(); + byte[] bytes = batchProto.ToByteArray(); + var stream = new MemoryStream(bytes); + var formatter = new ProtobufEventFormatter(); + var cloudEvents = formatter.DecodeBatchModeMessage(stream, s_protobufCloudEventBatchContentType, null); + Assert.Empty(cloudEvents); + } + + [Fact] + public void DecodeBatchMode_Multiple() + { + var batchProto = new V1.CloudEventBatch + { + Events = + { + new V1.CloudEvent + { + SpecVersion = "1.0", + Type = "type1", + Id = "event1", + Source = "//event-source1", + TextData = "simple text", + Attributes = { { "datacontenttype", StringAttribute("text/plain") } } + }, + new V1.CloudEvent + { + SpecVersion = "1.0", + Type = "type2", + Id = "event2", + Source = "//event-source2" + } + } + }; + + + byte[] bytes = batchProto.ToByteArray(); + var stream = new MemoryStream(bytes); + var formatter = new ProtobufEventFormatter(); + var cloudEvents = formatter.DecodeBatchModeMessage(stream, s_protobufCloudEventBatchContentType, null); + Assert.Equal(2, cloudEvents.Count); + + var event1 = cloudEvents[0]; + Assert.Equal("type1", event1.Type); + Assert.Equal("event1", event1.Id); + Assert.Equal(new Uri("//event-source1", UriKind.RelativeOrAbsolute), event1.Source); + Assert.Equal("simple text", event1.Data); + Assert.Equal("text/plain", event1.DataContentType); + + var event2 = cloudEvents[1]; + Assert.Equal("type2", event2.Type); + Assert.Equal("event2", event2.Id); + Assert.Equal(new Uri("//event-source2", UriKind.RelativeOrAbsolute), event2.Source); + Assert.Null(event2.Data); + Assert.Null(event2.DataContentType); + } + + // Utility methods + + private static object? DecodeBinaryModeEventData(byte[] bytes, string contentType) + { + var cloudEvent = new CloudEvent().PopulateRequiredAttributes(); + cloudEvent.DataContentType = contentType; + new ProtobufEventFormatter().DecodeBinaryModeEventData(bytes, cloudEvent); + return cloudEvent.Data; + } + + private static CloudEventAttributeValue StringAttribute(string value) => + new CloudEventAttributeValue { CeString = value }; + + private static CloudEventAttributeValue BinaryAttribute(byte[] value) => + new CloudEventAttributeValue { CeBytes = ByteString.CopyFrom(value) }; + + private static CloudEventAttributeValue BooleanAttribute(bool value) => + new CloudEventAttributeValue { CeBoolean = value }; + + private static CloudEventAttributeValue IntegerAttribute(int value) => + new CloudEventAttributeValue { CeInteger = value }; + + private static CloudEventAttributeValue UriAttribute(string value) => + new CloudEventAttributeValue { CeUri = value }; + + private static CloudEventAttributeValue UriRefAttribute(string value) => + new CloudEventAttributeValue { CeUriRef = value }; + + private static CloudEventAttributeValue TimestampAttribute(Timestamp value) => + new CloudEventAttributeValue { CeTimestamp = value }; + + private static CloudEventAttributeValue TimestampAttribute(DateTimeOffset value) => + TimestampAttribute(Timestamp.FromDateTimeOffset(value)); + + private static V1.CloudEvent CreateMinimalCloudEventProto() => new V1.CloudEvent + { + SpecVersion = "1.0", + Type = "test-type", + Id = "test-id", + Source = SampleUriText + }; + } +} diff --git a/test/CloudNative.CloudEvents.UnitTests/Protobuf/TestMessages.g.cs b/test/CloudNative.CloudEvents.UnitTests/Protobuf/TestMessages.g.cs new file mode 100644 index 0000000..c218464 --- /dev/null +++ b/test/CloudNative.CloudEvents.UnitTests/Protobuf/TestMessages.g.cs @@ -0,0 +1,233 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: test_messages.proto +// +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace CloudNative.CloudEvents.Protobuf.UnitTests { + + /// Holder for reflection information generated from test_messages.proto + public static partial class TestMessagesReflection { + + #region Descriptor + /// File descriptor for test_messages.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static TestMessagesReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "ChN0ZXN0X21lc3NhZ2VzLnByb3RvEhdpby5jbG91ZGV2ZW50cy52MS50ZXN0", + "cyIcCgxQYXlsb2FkRGF0YTESDAoEbmFtZRgBIAEoCUItqgIqQ2xvdWROYXRp", + "dmUuQ2xvdWRFdmVudHMuUHJvdG9idWYuVW5pdFRlc3RzYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::CloudNative.CloudEvents.Protobuf.UnitTests.PayloadData1), global::CloudNative.CloudEvents.Protobuf.UnitTests.PayloadData1.Parser, new[]{ "Name" }, null, null, null, null) + })); + } + #endregion + + } + #region Messages + public sealed partial class PayloadData1 : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new PayloadData1()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::CloudNative.CloudEvents.Protobuf.UnitTests.TestMessagesReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public PayloadData1() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public PayloadData1(PayloadData1 other) : this() { + name_ = other.name_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public PayloadData1 Clone() { + return new PayloadData1(this); + } + + /// Field number for the "name" field. + public const int NameFieldNumber = 1; + private string name_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string Name { + get { return name_; } + set { + name_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as PayloadData1); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(PayloadData1 other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Name != other.Name) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (Name.Length != 0) hash ^= Name.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (Name.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Name); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (Name.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Name); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (Name.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Name); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(PayloadData1 other) { + if (other == null) { + return; + } + if (other.Name.Length != 0) { + Name = other.Name; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Name = input.ReadString(); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + Name = input.ReadString(); + break; + } + } + } + } + #endif + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/test/CloudNative.CloudEvents.UnitTests/Protobuf/test_messages.proto b/test/CloudNative.CloudEvents.UnitTests/Protobuf/test_messages.proto new file mode 100644 index 0000000..7edff47 --- /dev/null +++ b/test/CloudNative.CloudEvents.UnitTests/Protobuf/test_messages.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package io.cloudevents.v1.tests; + +option csharp_namespace = "CloudNative.CloudEvents.Protobuf.UnitTests"; + +message PayloadData1 { + string name = 1; +}