diff --git a/extra-examples/CustomPayload1/CustomPayload1.csproj b/extra-examples/CustomPayload1/CustomPayload1.csproj
new file mode 100644
index 00000000..f376f2e8
--- /dev/null
+++ b/extra-examples/CustomPayload1/CustomPayload1.csproj
@@ -0,0 +1,11 @@
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
diff --git a/extra-examples/CustomPayload1/Function.cs b/extra-examples/CustomPayload1/Function.cs
new file mode 100644
index 00000000..721945da
--- /dev/null
+++ b/extra-examples/CustomPayload1/Function.cs
@@ -0,0 +1,33 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using CloudNative.CloudEvents;
+using Google.Cloud.Functions.Framework;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace CustomPayload1
+{
+ public class Function : ICloudEventFunction
+ {
+ public Task HandleAsync(CloudEvent cloudEvent, Payload data, CancellationToken cancellationToken)
+ {
+ Console.WriteLine($"ID: {cloudEvent.Id}");
+ Console.WriteLine($"Text: {data.Text}");
+ Console.WriteLine($"Number: {data.Number}");
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/extra-examples/CustomPayload1/Payload.cs b/extra-examples/CustomPayload1/Payload.cs
new file mode 100644
index 00000000..7670d8d2
--- /dev/null
+++ b/extra-examples/CustomPayload1/Payload.cs
@@ -0,0 +1,33 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using CloudNative.CloudEvents;
+using CloudNative.CloudEvents.SystemTextJson;
+using System.Text.Json.Serialization;
+
+namespace CustomPayload1
+{
+ [CloudEventFormatter(typeof(JsonEventFormatter))]
+ public class Payload
+ {
+ // Note: System.Text.Json is case-sensitive. The Newtonsoft.Json
+ // deserializer wouldn't require the attributes. (Or if the JSON
+ // matched the property name casing, we wouldn't need them then either.)
+ [JsonPropertyName("text")]
+ public string Text { get; set; }
+
+ [JsonPropertyName("number")]
+ public double Number { get; set; }
+ }
+}
diff --git a/extra-examples/CustomPayload2/CustomPayload2.csproj b/extra-examples/CustomPayload2/CustomPayload2.csproj
new file mode 100644
index 00000000..a53bc36d
--- /dev/null
+++ b/extra-examples/CustomPayload2/CustomPayload2.csproj
@@ -0,0 +1,12 @@
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
+
diff --git a/extra-examples/CustomPayload2/Function.cs b/extra-examples/CustomPayload2/Function.cs
new file mode 100644
index 00000000..81811941
--- /dev/null
+++ b/extra-examples/CustomPayload2/Function.cs
@@ -0,0 +1,45 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using CloudNative.CloudEvents;
+using CloudNative.CloudEvents.NewtonsoftJson;
+using Google.Cloud.Functions.Framework;
+using Google.Cloud.Functions.Hosting;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using ThirdPartyPackage;
+
+namespace CustomPayload2
+{
+ public class Startup : FunctionsStartup
+ {
+ public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) =>
+ services.AddSingleton>();
+ }
+
+ [FunctionsStartup(typeof(Startup))]
+ public class Function : ICloudEventFunction
+ {
+ public Task HandleAsync(CloudEvent cloudEvent, Payload data, CancellationToken cancellationToken)
+ {
+ Console.WriteLine($"ID: {cloudEvent.Id}");
+ Console.WriteLine($"Text: {data.Text}");
+ Console.WriteLine($"Number: {data.Number}");
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/extra-examples/CustomPayload2/Payload.cs b/extra-examples/CustomPayload2/Payload.cs
new file mode 100644
index 00000000..ae80f671
--- /dev/null
+++ b/extra-examples/CustomPayload2/Payload.cs
@@ -0,0 +1,24 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace ThirdPartyPackage
+{
+ // The event payload, as defined by a third-party. The third-party
+ // doesn't depend on the CloudEvents SDK at all.
+ public class Payload
+ {
+ public string Text { get; set; }
+ public double Number { get; set; }
+ }
+}
diff --git a/extra-examples/CustomPayload3/CloudEventFormatterStartup.cs b/extra-examples/CustomPayload3/CloudEventFormatterStartup.cs
new file mode 100644
index 00000000..ed29bb79
--- /dev/null
+++ b/extra-examples/CustomPayload3/CloudEventFormatterStartup.cs
@@ -0,0 +1,29 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using CloudNative.CloudEvents;
+using CloudNative.CloudEvents.NewtonsoftJson;
+using Google.Cloud.Functions.Hosting;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using ThirdPartyPackage1;
+
+namespace ThirdPartyPackage2
+{
+ public class CloudEventFormatterStartup : FunctionsStartup
+ {
+ public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) =>
+ services.AddSingleton>();
+ }
+}
diff --git a/extra-examples/CustomPayload3/CustomPayload3.csproj b/extra-examples/CustomPayload3/CustomPayload3.csproj
new file mode 100644
index 00000000..a53bc36d
--- /dev/null
+++ b/extra-examples/CustomPayload3/CustomPayload3.csproj
@@ -0,0 +1,12 @@
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
+
diff --git a/extra-examples/CustomPayload3/Function.cs b/extra-examples/CustomPayload3/Function.cs
new file mode 100644
index 00000000..353be7bb
--- /dev/null
+++ b/extra-examples/CustomPayload3/Function.cs
@@ -0,0 +1,37 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using CloudNative.CloudEvents;
+using Google.Cloud.Functions.Framework;
+using Google.Cloud.Functions.Hosting;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using ThirdPartyPackage1;
+using ThirdPartyPackage2;
+
+namespace CustomPayload3
+{
+ [FunctionsStartup(typeof(CloudEventFormatterStartup))]
+ public class Function : ICloudEventFunction
+ {
+ public Task HandleAsync(CloudEvent cloudEvent, Payload data, CancellationToken cancellationToken)
+ {
+ Console.WriteLine($"ID: {cloudEvent.Id}");
+ Console.WriteLine($"Text: {data.Text}");
+ Console.WriteLine($"Number: {data.Number}");
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/extra-examples/CustomPayload3/Payload.cs b/extra-examples/CustomPayload3/Payload.cs
new file mode 100644
index 00000000..e28e164b
--- /dev/null
+++ b/extra-examples/CustomPayload3/Payload.cs
@@ -0,0 +1,24 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace ThirdPartyPackage1
+{
+ // The event payload, as defined by a third-party. The package
+ // defining the payload doesn't depend on the CloudEvents SDK.
+ public class Payload
+ {
+ public string Text { get; set; }
+ public double Number { get; set; }
+ }
+}
diff --git a/extra-examples/GoogleTypeDetection/Function.cs b/extra-examples/GoogleTypeDetection/Function.cs
new file mode 100644
index 00000000..65dd76a8
--- /dev/null
+++ b/extra-examples/GoogleTypeDetection/Function.cs
@@ -0,0 +1,55 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using CloudNative.CloudEvents;
+using Google.Cloud.Functions.Framework;
+using Google.Cloud.Functions.Hosting;
+using Google.Events.Protobuf.Cloud.PubSub.V1;
+using Google.Events.Protobuf.Cloud.Storage.V1;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GoogleTypeDetection
+{
+ public class Startup : FunctionsStartup
+ {
+ public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) =>
+ services.AddSingleton();
+ }
+
+ [FunctionsStartup(typeof(Startup))]
+ public class Function : ICloudEventFunction
+ {
+ public Task HandleAsync(CloudEvent cloudEvent, CancellationToken cancellationToken)
+ {
+ Console.WriteLine($"ID: {cloudEvent.Id}");
+ switch (cloudEvent.Data)
+ {
+ case StorageObjectData storage:
+ Console.WriteLine($"Storage Bucket: {storage.Bucket}");
+ break;
+ case MessagePublishedData pubsub:
+ Console.WriteLine($"PubSub subscription: {pubsub.Subscription}");
+ break;
+ default:
+ Console.WriteLine($"Unhandled event type {cloudEvent.Type}");
+ break;
+ }
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/extra-examples/GoogleTypeDetection/GoogleEventFormatter.cs b/extra-examples/GoogleTypeDetection/GoogleEventFormatter.cs
new file mode 100644
index 00000000..3278bb24
--- /dev/null
+++ b/extra-examples/GoogleTypeDetection/GoogleEventFormatter.cs
@@ -0,0 +1,138 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using CloudNative.CloudEvents;
+using CloudNative.CloudEvents.Core;
+using CloudNative.CloudEvents.SystemTextJson;
+using Google.Events.Protobuf.Cloud.Firestore.V1;
+using Google.Events.Protobuf.Cloud.PubSub.V1;
+using Google.Events.Protobuf.Cloud.Storage.V1;
+using Google.Events.Protobuf.Firebase.Database.V1;
+using Google.Protobuf;
+using Google.Protobuf.Reflection;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Mime;
+using System.Text;
+using System.Text.Json;
+
+namespace GoogleTypeDetection
+{
+ ///
+ /// A CloudEvent formatter that knows about all the events in the Google.Events.Protobuf package.
+ /// This would be in that package.
+ ///
+ public class GoogleEventFormatter : CloudEventFormatter
+ {
+ private static Dictionary s_eventTypeToPayloadType = new Dictionary
+ {
+ { StorageObjectData.ArchivedCloudEventType, StorageObjectData.Descriptor },
+ { StorageObjectData.DeletedCloudEventType, StorageObjectData.Descriptor },
+ { StorageObjectData.FinalizedCloudEventType, StorageObjectData.Descriptor },
+ { StorageObjectData.MetadataUpdatedCloudEventType, StorageObjectData.Descriptor },
+
+ { MessagePublishedData.MessagePublishedCloudEventType, MessagePublishedData.Descriptor },
+
+ { DocumentEventData.CreatedCloudEventType, DocumentEventData.Descriptor },
+ { DocumentEventData.DeletedCloudEventType, DocumentEventData.Descriptor },
+
+ { ReferenceEventData.CreatedCloudEventType, ReferenceEventData.Descriptor },
+ { ReferenceEventData.DeletedCloudEventType, ReferenceEventData.Descriptor },
+ { ReferenceEventData.WrittenCloudEventType, ReferenceEventData.Descriptor },
+ { ReferenceEventData.UpdatedCloudEventType, ReferenceEventData.Descriptor },
+
+ // Etc... this would all be generated.
+ };
+
+ private static readonly JsonParser s_jsonParser = new JsonParser(JsonParser.Settings.Default.WithIgnoreUnknownFields(true));
+
+ // Note: although we delegate to a JsonEventFormatter (via StructuredEventFormatter) and *could* just derive from JsonEventFormatter,
+ // that would provide less flexibility for the future.
+ private static readonly CloudEventFormatter s_structuredEventFormatter = new StructuredEventFormatter();
+
+ ///
+ public override void DecodeBinaryModeEventData(ReadOnlyMemory body, CloudEvent cloudEvent)
+ {
+ if (cloudEvent.Type is string eventType && s_eventTypeToPayloadType.TryGetValue(eventType, out var descriptor))
+ {
+ cloudEvent.Data = body.Length == 0
+ ? null
+ : s_jsonParser.Parse(new StreamReader(BinaryDataUtilities.AsStream(body)), descriptor);
+ return;
+ }
+ throw new ArgumentException("Unknown CloudEvent type: " + cloudEvent.Type);
+ }
+
+ ///
+ public override CloudEvent DecodeStructuredModeMessage(ReadOnlyMemory body, ContentType contentType, IEnumerable extensionAttributes) =>
+ s_structuredEventFormatter.DecodeStructuredModeMessage(body, contentType, extensionAttributes);
+
+ ///
+ public override IReadOnlyList DecodeBatchModeMessage(ReadOnlyMemory body, ContentType contentType, IEnumerable extensionAttributes) =>
+ s_structuredEventFormatter.DecodeBatchModeMessage(body, contentType, extensionAttributes);
+
+ ///
+ public override ReadOnlyMemory EncodeBinaryModeEventData(CloudEvent cloudEvent)
+ {
+ var data = (IMessage) cloudEvent.Data;
+ if (data is null)
+ {
+ return Array.Empty();
+ }
+ var json = data.ToString();
+ return Encoding.UTF8.GetBytes(json);
+ }
+
+ ///
+ public override ReadOnlyMemory EncodeStructuredModeMessage(CloudEvent cloudEvent, out ContentType contentType) =>
+ s_structuredEventFormatter.EncodeStructuredModeMessage(cloudEvent, out contentType);
+
+ ///
+ public override ReadOnlyMemory EncodeBatchModeMessage(IEnumerable cloudEvents, out ContentType contentType) =>
+ s_structuredEventFormatter.EncodeBatchModeMessage(cloudEvents, out contentType);
+
+ // This is basically a slightly-modified copy of the code within ProtobufJsonCloudEventFormatter.
+ private class StructuredEventFormatter : JsonEventFormatter
+ {
+ protected override void EncodeStructuredModeData(CloudEvent cloudEvent, Utf8JsonWriter writer)
+ {
+ writer.WritePropertyName("data");
+ // TODO: Try to make this cleaner. It's annoying that we have to convert to a JSON string,
+ // reparse, then reserialize.
+ using var document = JsonDocument.Parse(cloudEvent.Data!.ToString());
+ document.WriteTo(writer);
+ }
+
+ protected override void DecodeStructuredModeDataProperty(JsonElement dataElement, CloudEvent cloudEvent)
+ {
+ if (dataElement.ValueKind != JsonValueKind.Object)
+ {
+ throw new InvalidOperationException("Expected Data property to have an object value");
+ }
+ if (cloudEvent.Type is string eventType && s_eventTypeToPayloadType.TryGetValue(eventType, out var descriptor))
+ {
+ string json = dataElement.GetRawText();
+ cloudEvent.Data = s_jsonParser.Parse(json, descriptor);
+ return;
+ }
+ }
+
+ protected override void DecodeStructuredModeDataBase64Property(JsonElement dataBase64Element, CloudEvent cloudEvent)
+ {
+ throw new InvalidOperationException("Expected Data property to be set");
+ }
+ }
+ }
+}
diff --git a/extra-examples/GoogleTypeDetection/GoogleTypeDetection.csproj b/extra-examples/GoogleTypeDetection/GoogleTypeDetection.csproj
new file mode 100644
index 00000000..5242316e
--- /dev/null
+++ b/extra-examples/GoogleTypeDetection/GoogleTypeDetection.csproj
@@ -0,0 +1,12 @@
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
+
diff --git a/extra-examples/LoggingFunction/Function.cs b/extra-examples/LoggingFunction/Function.cs
new file mode 100644
index 00000000..a40035d5
--- /dev/null
+++ b/extra-examples/LoggingFunction/Function.cs
@@ -0,0 +1,47 @@
+// Copyright 2021, Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using Google.Cloud.Functions.Framework;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace LoggingFunction
+{
+ public class Function : IHttpFunction
+ {
+ private readonly ILogger _logger;
+
+ public Function(ILogger logger) => _logger = logger;
+
+ public async Task HandleAsync(HttpContext context)
+ {
+ _logger.LogInformation("Start of request");
+ var request = context.Request;
+ _logger.LogInformation("HTTP method: {method}", request.Method);
+ _logger.LogInformation("Headers:");
+ foreach (var header in request.Headers)
+ {
+ _logger.LogInformation("{key}: {values}", header.Key, string.Join(",", header.Value));
+ }
+ using var reader = new StreamReader(request.Body);
+ string text = await reader.ReadToEndAsync();
+ _logger.LogInformation("Body:");
+ _logger.LogInformation("{body}", text);
+ _logger.LogInformation("End of request");
+ }
+ }
+}
diff --git a/extra-examples/LoggingFunction/LoggingFunction.csproj b/extra-examples/LoggingFunction/LoggingFunction.csproj
new file mode 100644
index 00000000..f376f2e8
--- /dev/null
+++ b/extra-examples/LoggingFunction/LoggingFunction.csproj
@@ -0,0 +1,11 @@
+
+
+ Exe
+ netcoreapp3.1
+
+
+
+
+
+
+
diff --git a/extra-examples/README.md b/extra-examples/README.md
new file mode 100644
index 00000000..ed3274f7
--- /dev/null
+++ b/extra-examples/README.md
@@ -0,0 +1,96 @@
+These are extra examples to see if the existing Functions Framework
+and CloudEvent integration paths can easily meet the use cases we
+want to support, or if additional functionality is required.
+
+## Testing the functions
+
+Start the desired function (e.g. set it as the startup project and
+hit Run in Visual Studio, or cd into the directory and run `dotnet
+run`) then use curl to fire requests. There are sample requests in
+this directory:
+
+- `custom-payload-request.json` for all the custom payload examples
+- Google payloads for the GoogleTypeDetection example:
+ - `google-storage-request.json`
+ - `google-pubsub-request.json`
+ - `google-firestore-request.json`
+
+Sample curl command:
+
+```sh
+curl -X POST -H 'Content-Type:application/cloudevents' http://localhost:8080 -d @custom-payload-request.json
+```
+
+## Examples in this directory
+
+### CustomPayload1
+
+This is for a customer-defined payload, where the customer is able
+and willing to add the `[CloudEventFormatter]` attribute which
+enables the Functions Framework (and anything else using the SDK) to
+"know" how to deserialize.
+
+In this case, we use
+CloudNative.CloudEvents.SystemTextJson.JsonEventFormatter, which
+uses System.Text.Json to deserialize. We need to add attributes to
+the payload properties as System.Text.Json is case-sensitive, but
+that's not CloudEvent-specific at all.
+
+### CustomPayload2
+
+This is for a third-party-defined payload, where the customer is
+unable or unwilling to add the `[CloudEventFormatter]` attribute.
+Instead, they specify a startup class (see [the repo
+docs](../docs/customization.md) for more info) to inject a
+`CloudEventFormatter` for the Functions Framework to use.
+
+In this case, we've added a reference to
+CloudNative.CloudEvents.NewtonsoftJson and used the
+JsonEventFormatter from that package. That's case-insensitive, so
+the payload class is really just a POCO with no extra attributes.
+
+Note that although the payload is in the same project in the example
+(just for simplicity), it could easily be in a third-party package.
+
+### CustomPayload3
+
+This is for a third-party-defined payload where the third-party
+doesn't want to add a CloudEvents reference in their main package
+for whatever reason, but is happy to have a separate package to
+enable simple integration with the Functions Framework. This
+separate package depends on the Functions Framework, and provides
+the same kind of startup class as in CustomPayload2 - the customer
+just needs to refer to that startup class rather than writing it
+themselves.
+
+(The code in this example would be spread across three separate
+packages in reality.)
+
+### GoogleTypeDetection
+
+This is an example of a function intended to handle any Google event
+type, with automatic deserializing to the "right" kind of object.
+
+The GoogleEventFormatter code would be added to
+Google.Events.Protobuf, with the dictionary part being generated (C#
+makes this easy via partial classes).
+
+The customer code requiring a startup class isn't ideal, although
+it's really not very much code. We can't add that class to either
+the Functions Framework or the Google.Events.Protobuf package as
+they don't know about each other (at the moment). We *could* create
+an additional package just to contain that startup code... or we
+could try to work out another approach.
+
+(One possible option would be to create a "marker" IGoogleEvent
+interface, make all the Google event classes implement it. That
+could be decorated with the CloudEventFormatter attribute to let it
+all just work. I'm not sure how I feel about that idea, but we could
+test it.)
+
+## Supporting code
+
+### LoggingFunction: used to log requests
+
+This is just an HTTP function that logs the headers and body of a
+request.
\ No newline at end of file
diff --git a/extra-examples/custom-payload-request.json b/extra-examples/custom-payload-request.json
new file mode 100644
index 00000000..2143c541
--- /dev/null
+++ b/extra-examples/custom-payload-request.json
@@ -0,0 +1,11 @@
+{
+ "specversion": "1.0",
+ "id": "event-id",
+ "datacontenttype": "application/json",
+ "source": "//local",
+ "type": "example",
+ "data": {
+ "text":"Some text",
+ "number":20
+ }
+}
\ No newline at end of file
diff --git a/extra-examples/extra-examples.sln b/extra-examples/extra-examples.sln
new file mode 100644
index 00000000..165e7f74
--- /dev/null
+++ b/extra-examples/extra-examples.sln
@@ -0,0 +1,93 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31424.327
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomPayload1", "CustomPayload1\CustomPayload1.csproj", "{D783AF8C-5998-48FC-8CCD-8EF122802C9C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggingFunction", "LoggingFunction\LoggingFunction.csproj", "{12F37179-ABE2-4EA1-9118-2D1709D86C42}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomPayload2", "CustomPayload2\CustomPayload2.csproj", "{A44CB536-4BBB-4BD3-8A56-AB972BE86D07}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomPayload3", "CustomPayload3\CustomPayload3.csproj", "{3D7844C3-557B-45AF-86E7-85F6F1D85C02}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoogleTypeDetection", "GoogleTypeDetection\GoogleTypeDetection.csproj", "{D326FE7F-8D5A-4F92-8090-E58A2B983668}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Debug|x64.Build.0 = Debug|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Debug|x86.Build.0 = Debug|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Release|x64.ActiveCfg = Release|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Release|x64.Build.0 = Release|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Release|x86.ActiveCfg = Release|Any CPU
+ {D783AF8C-5998-48FC-8CCD-8EF122802C9C}.Release|x86.Build.0 = Release|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Debug|x64.Build.0 = Debug|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Debug|x86.Build.0 = Debug|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Release|Any CPU.Build.0 = Release|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Release|x64.ActiveCfg = Release|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Release|x64.Build.0 = Release|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Release|x86.ActiveCfg = Release|Any CPU
+ {12F37179-ABE2-4EA1-9118-2D1709D86C42}.Release|x86.Build.0 = Release|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Debug|x64.Build.0 = Debug|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Debug|x86.Build.0 = Debug|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Release|x64.ActiveCfg = Release|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Release|x64.Build.0 = Release|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Release|x86.ActiveCfg = Release|Any CPU
+ {A44CB536-4BBB-4BD3-8A56-AB972BE86D07}.Release|x86.Build.0 = Release|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Debug|x64.Build.0 = Debug|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Debug|x86.Build.0 = Debug|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Release|x64.ActiveCfg = Release|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Release|x64.Build.0 = Release|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Release|x86.ActiveCfg = Release|Any CPU
+ {3D7844C3-557B-45AF-86E7-85F6F1D85C02}.Release|x86.Build.0 = Release|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Debug|x64.Build.0 = Debug|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Debug|x86.Build.0 = Debug|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Release|x64.ActiveCfg = Release|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Release|x64.Build.0 = Release|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Release|x86.ActiveCfg = Release|Any CPU
+ {D326FE7F-8D5A-4F92-8090-E58A2B983668}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {AFEF0FCE-1771-40CE-A2C6-25477479B0CA}
+ EndGlobalSection
+EndGlobal
diff --git a/extra-examples/google-firestore-request.json b/extra-examples/google-firestore-request.json
new file mode 100644
index 00000000..d5967caa
--- /dev/null
+++ b/extra-examples/google-firestore-request.json
@@ -0,0 +1,46 @@
+{
+ "specversion": "1.0",
+ "id": "firestore-event-id",
+ "datacontenttype": "application/json",
+ "source": "//local",
+ "type": "google.cloud.firestore.document.v1.updated",
+ "data": {
+ "oldValue":{
+ "createTime":"2020-04-23T09:58:53.211035Z",
+ "fields":{
+ "another test":{
+ "stringValue":"asd"
+ },
+ "count":{
+ "integerValue":"3"
+ },
+ "foo":{
+ "stringValue":"bar"
+ }
+ },
+ "name":"projects/project-id/databases/(default)/documents/gcf-test/2Vm2mI1d0wIaK2Waj5to",
+ "updateTime":"2020-04-23T12:00:27.247187Z"
+ },
+ "updateMask":{
+ "fieldPaths":[
+ "count"
+ ]
+ },
+ "value":{
+ "createTime":"2020-04-23T09:58:53.211035Z",
+ "fields":{
+ "another test":{
+ "stringValue":"asd"
+ },
+ "count":{
+ "integerValue":"4"
+ },
+ "foo":{
+ "stringValue":"bar"
+ }
+ },
+ "name":"projects/project-id/databases/(default)/documents/gcf-test/2Vm2mI1d0wIaK2Waj5to",
+ "updateTime":"2020-04-23T12:00:27.247187Z"
+ }
+ }
+}
\ No newline at end of file
diff --git a/extra-examples/google-pubsub-request.json b/extra-examples/google-pubsub-request.json
new file mode 100644
index 00000000..14e4600e
--- /dev/null
+++ b/extra-examples/google-pubsub-request.json
@@ -0,0 +1,19 @@
+{
+ "specversion": "1.0",
+ "id": "pubsub-event-id",
+ "datacontenttype": "application/json",
+ "source": "//local",
+ "type": "google.cloud.pubsub.topic.v1.messagePublished",
+ "data": {
+ "subscription": "projects/my-project/subscriptions/my-subscription",
+ "message": {
+ "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage",
+ "attributes": {
+ "attr1":"attr1-value"
+ },
+ "data": "dGVzdCBtZXNzYWdlIDM=",
+ "messageId": "message-id",
+ "publishTime":"2021-02-05T04:06:14.109Z"
+ }
+ }
+}
\ No newline at end of file
diff --git a/extra-examples/google-storage-request.json b/extra-examples/google-storage-request.json
new file mode 100644
index 00000000..cb43e19d
--- /dev/null
+++ b/extra-examples/google-storage-request.json
@@ -0,0 +1,11 @@
+{
+ "specversion": "1.0",
+ "id": "storage-event-id",
+ "datacontenttype": "application/json",
+ "source": "//local",
+ "type": "google.cloud.storage.object.v1.finalized",
+ "data": {
+ "bucket": "some-bucket",
+ "name": "folder/Test.cs"
+ }
+}
\ No newline at end of file