Add .NET 7, 8, trimming and AOT support
Also fixed several nullability warnings. Signed-off-by: Giovanni Bassi <giggio@giggio.net>
This commit is contained in:
parent
6f484339e8
commit
6b24309fc9
|
@ -1,6 +1,7 @@
|
|||
name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
@ -15,15 +16,15 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out our repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
# Build with .NET 6.0 SDK
|
||||
- name: Setup .NET 6.0
|
||||
- name: Setup .NET 8.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
|
|
@ -13,7 +13,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out our repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
@ -21,7 +21,7 @@ jobs:
|
|||
- name: Setup .NET 6.0
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
|
|
@ -72,6 +72,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "xml", "xml", "{4012C753-68D
|
|||
conformance\format\xml\valid-events.xml = conformance\format\xml\valid-events.xml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpSendJson", "samples\HttpSendJson\HttpSendJson.csproj", "{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudNative.CloudEvents.MinApiSample", "samples\CloudNative.CloudEvents.MinApiSample\CloudNative.CloudEvents.MinApiSample.csproj", "{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -238,15 +244,43 @@ Global
|
|||
{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
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|x64.Build.0 = Release|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8}.Release|x86.Build.0 = Release|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|x64.Build.0 = Release|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{F1B9B769-DB6B-481F-905C-24FE3B12E00E} = {5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}
|
||||
{9760D744-D1BF-40E3-BD6F-7F639BFB9188} = {5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}
|
||||
{A5906FBA-D73A-4A09-8539-CB10D7B586AE} = {8CCC98B3-1776-49FF-96D6-947A9E5DFB0A}
|
||||
{D8055631-E6BB-4CD2-8162-F674D6D30E76} = {A5906FBA-D73A-4A09-8539-CB10D7B586AE}
|
||||
{119AD438-878B-4383-BC9F-779F1605E711} = {A5906FBA-D73A-4A09-8539-CB10D7B586AE}
|
||||
{4012C753-68DE-4737-936F-F5DBC485C51B} = {A5906FBA-D73A-4A09-8539-CB10D7B586AE}
|
||||
{730D4C5E-DC5B-498C-ADFB-05CB81ECCEC8} = {5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}
|
||||
{1566A665-9FFF-4D87-9C7B-CC06C72C9BFF} = {5AD5E051-9A8E-46D9-B0C5-8933718C6D1F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F77A454C-CC17-4AD6-823A-64E1A94FDA0A}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\CloudNative.CloudEvents.AspNetCore\CloudNative.CloudEvents.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\src\CloudNative.CloudEvents\CloudNative.CloudEvents.csproj" />
|
||||
<ProjectReference Include="..\..\src\CloudNative.CloudEvents.SystemTextJson\CloudNative.CloudEvents.SystemTextJson.csproj" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishAot>true</PublishAot>
|
||||
<DebugType>None</DebugType>
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) Cloud Native Foundation.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using CloudNative.CloudEvents;
|
||||
using CloudNative.CloudEvents.Http;
|
||||
using CloudNative.CloudEvents.SystemTextJson;
|
||||
using CloudNative.CloudEvents.AspNetCore;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var app = builder.Build();
|
||||
var formatter = new JsonEventFormatter<Message>(MyJsonContext.Default);
|
||||
|
||||
app.MapPost("/api/events/receive/", async (HttpRequest request) =>
|
||||
{
|
||||
var cloudEvent = await request.ToCloudEventAsync(formatter);
|
||||
using var ms = new MemoryStream();
|
||||
using var writer = new Utf8JsonWriter(ms, new() { Indented = true });
|
||||
writer.WriteStartObject();
|
||||
foreach (var (attribute, value) in cloudEvent.GetPopulatedAttributes())
|
||||
writer.WriteString(attribute.Name, attribute.Format(value));
|
||||
writer.WriteEndObject();
|
||||
await writer.FlushAsync();
|
||||
var attributeMap = Encoding.UTF8.GetString(ms.ToArray());
|
||||
return Results.Text($"Received event with ID {cloudEvent.Id}, attributes: {attributeMap}");
|
||||
});
|
||||
|
||||
app.MapPost("/api/events/receive2/", (Event e) => Results.Json(e.CloudEvent.Data, MyJsonContext.Default));
|
||||
|
||||
app.MapPost("/api/events/receive3/", (Message message) => Results.Json(message, MyJsonContext.Default));
|
||||
|
||||
app.MapGet("/api/events/generate/", () =>
|
||||
{
|
||||
var evt = new CloudEvent
|
||||
{
|
||||
Type = "CloudNative.CloudEvents.MinApiSample",
|
||||
Source = new Uri("https://github.com/cloudevents/sdk-csharp"),
|
||||
Time = DateTimeOffset.Now,
|
||||
DataContentType = "application/json",
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Data = new Message("C#", Environment.Version.ToString())
|
||||
};
|
||||
// Format the event as the body of the response. This is UTF-8 JSON because of
|
||||
// the CloudEventFormatter we're using, but EncodeStructuredModeMessage always
|
||||
// returns binary data. We could return the data directly, but for debugging
|
||||
// purposes it's useful to have the JSON string.
|
||||
var bytes = formatter.EncodeStructuredModeMessage(evt, out var contentType);
|
||||
string json = Encoding.UTF8.GetString(bytes.Span);
|
||||
// Specify the content type of the response: this is what makes it a CloudEvent.
|
||||
// (In "binary mode", the content type is the content type of the data, and headers
|
||||
// indicate that it's a CloudEvent.)
|
||||
return Results.Content(json, contentType.MediaType, Encoding.UTF8);
|
||||
});
|
||||
|
||||
app.Run();
|
||||
|
||||
[JsonSerializable(typeof(Message))]
|
||||
internal partial class MyJsonContext : JsonSerializerContext { }
|
||||
|
||||
public class Event
|
||||
{
|
||||
private readonly static JsonEventFormatter formatter = new JsonEventFormatter<Message>(MyJsonContext.Default);
|
||||
// required for receive2
|
||||
public static async ValueTask<Event?> BindAsync(HttpContext context)
|
||||
{
|
||||
var cloudEvent = await context.Request.ToCloudEventAsync(formatter);
|
||||
return new Event { CloudEvent = cloudEvent };
|
||||
}
|
||||
public required CloudEvent CloudEvent { get; init; }
|
||||
}
|
||||
|
||||
record class Message(string Language, string EnvironmentVersion)
|
||||
{
|
||||
private readonly static JsonEventFormatter formatter = new JsonEventFormatter<Message>(MyJsonContext.Default);
|
||||
// required for receive3
|
||||
public static async ValueTask<Message?> BindAsync(HttpContext context)
|
||||
{
|
||||
var cloudEvent = await context.Request.ToCloudEventAsync(formatter);
|
||||
return cloudEvent.Data is Message message ? message : null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/events/generate",
|
||||
"applicationUrl": "http://localhost:5002",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
|
@ -14,5 +14,6 @@
|
|||
|
||||
<!-- Never pack any sample projects -->
|
||||
<IsPackable>False</IsPackable>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="docopt.net" Version="0.8.1" />
|
||||
<ProjectReference Include="..\..\src\CloudNative.CloudEvents\CloudNative.CloudEvents.csproj" />
|
||||
<ProjectReference Include="..\..\src\CloudNative.CloudEvents.SystemTextJson\CloudNative.CloudEvents.SystemTextJson.csproj" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishAot>true</PublishAot>
|
||||
<DebugType>None</DebugType>
|
||||
<DebugSymbols>False</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -0,0 +1,70 @@
|
|||
// Copyright (c) Cloud Native Foundation.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using CloudNative.CloudEvents;
|
||||
using CloudNative.CloudEvents.Http;
|
||||
using CloudNative.CloudEvents.SystemTextJson;
|
||||
using DocoptNet;
|
||||
using System.Net.Mime;
|
||||
using static System.Console;
|
||||
|
||||
// This application uses the docopt.net library for parsing the command
|
||||
// line and calling the application code.
|
||||
ProgramArguments programArguments = new();
|
||||
var result = await ProgramArguments.CreateParserWithVersion()
|
||||
.Parse(args)
|
||||
.Match(RunAsync,
|
||||
result => { WriteLine(result.Help); return Task.FromResult(1); },
|
||||
result => { WriteLine(result.Version); return Task.FromResult(0); },
|
||||
result => { Error.WriteLine(result.Usage); return Task.FromResult(1); });
|
||||
return result;
|
||||
|
||||
static async Task<int> RunAsync(ProgramArguments args)
|
||||
{
|
||||
var cloudEvent = new CloudEvent
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Type = args.OptType,
|
||||
Source = new Uri(args.OptSource),
|
||||
DataContentType = MediaTypeNames.Application.Json,
|
||||
Data = System.Text.Json.JsonSerializer.Serialize("hey there!", GeneratedJsonContext.Default.String)
|
||||
};
|
||||
|
||||
var content = cloudEvent.ToHttpContent(ContentMode.Structured, new JsonEventFormatter(GeneratedJsonContext.Default));
|
||||
|
||||
var httpClient = new HttpClient();
|
||||
// Your application remains in charge of adding any further headers or
|
||||
// other information required to authenticate/authorize or otherwise
|
||||
// dispatch the call at the server.
|
||||
var result = await httpClient.PostAsync(args.OptUrl, content);
|
||||
|
||||
WriteLine(result.StatusCode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
[System.Text.Json.Serialization.JsonSerializable(typeof(string))]
|
||||
internal partial class GeneratedJsonContext : System.Text.Json.Serialization.JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
[DocoptArguments]
|
||||
partial class ProgramArguments
|
||||
{
|
||||
const string Help = @"HttpSendJson.
|
||||
|
||||
Usage:
|
||||
HttpSendJson --url=URL [--type=TYPE] [--source=SOURCE]
|
||||
HttpSendJson (-h | --help)
|
||||
HttpSendJson --version
|
||||
|
||||
Options:
|
||||
--url=URL HTTP(S) address to send the event to.
|
||||
--type=TYPE CloudEvents 'type' [default: com.example.myevent].
|
||||
--source=SOURCE CloudEvents 'source' [default: urn:example-com:mysource:abc].
|
||||
-h --help Show this screen.
|
||||
--version Show version.
|
||||
";
|
||||
public static string Version => $"producer {typeof(ProgramArguments).Assembly.GetName().Version}";
|
||||
public static IParser<ProgramArguments> CreateParserWithVersion() => CreateParser().WithVersion(Version);
|
||||
}
|
|
@ -8,6 +8,7 @@ using Amqp.Types;
|
|||
using CloudNative.CloudEvents.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net.Mime;
|
||||
|
||||
|
@ -145,7 +146,7 @@ namespace CloudNative.CloudEvents.Amqp
|
|||
}
|
||||
}
|
||||
|
||||
private static bool HasCloudEventsContentType(Message message, out string? contentType)
|
||||
private static bool HasCloudEventsContentType(Message message, [NotNullWhen(true)] out string? contentType)
|
||||
{
|
||||
contentType = message.Properties.ContentType?.ToString();
|
||||
return MimeUtilities.IsCloudEventsContentType(contentType);
|
||||
|
@ -249,4 +250,23 @@ namespace CloudNative.CloudEvents.Amqp
|
|||
return applicationProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
internal sealed class NotNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||
/// </param>
|
||||
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
|
||||
|
||||
/// <summary>Gets the return value condition.</summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,9 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||
<Description>AMQP extensions for CloudNative.CloudEvents</Description>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageTags>cncf;cloudnative;cloudevents;events;amqp</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||
<Description>ASP.Net Core extensions for CloudNative.CloudEvents</Description>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageTags>cncf;cloudnative;cloudevents;events;aspnetcore;aspnet</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -185,7 +185,7 @@ namespace CloudNative.CloudEvents.Avro
|
|||
// will fail and that's okay since the type is useless without the proper schema.
|
||||
using var sr = new StreamReader(typeof(AvroEventFormatter)
|
||||
.Assembly
|
||||
.GetManifestResourceStream("CloudNative.CloudEvents.Avro.AvroSchema.json"));
|
||||
.GetManifestResourceStream("CloudNative.CloudEvents.Avro.AvroSchema.json")!);
|
||||
|
||||
return (RecordSchema) Schema.Parse(sr.ReadToEnd());
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||
<Description>Avro extensions for CloudNative.CloudEvents</Description>
|
||||
<PackageTags>cncf;cloudnative;cloudevents;events;avro</PackageTags>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||
<Description>Kafka extensions for CloudNative.CloudEvents</Description>
|
||||
<PackageTags>cncf;cloudnative;cloudevents;events;kafka</PackageTags>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||
<Description>MQTT extensions for CloudNative.CloudEvents</Description>
|
||||
<PackageTags>cncf;cloudnative;cloudevents;events;mqtt</PackageTags>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||
<Description>JSON support for the CNCF CloudEvents SDK, based on Newtonsoft.Json.</Description>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageTags>cncf;cloudnative;cloudevents;events;json;newtonsoft</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -345,7 +345,9 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
|
|||
{
|
||||
throw new ArgumentException($"Structured mode property '{DataBase64PropertyName}' must be a string, when present.");
|
||||
}
|
||||
cloudEvent.Data = Convert.FromBase64String((string?)dataBase64Token);
|
||||
var tokenString = (string?)dataBase64Token;
|
||||
if (tokenString != null)
|
||||
cloudEvent.Data = Convert.FromBase64String(tokenString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -524,7 +526,7 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
|
|||
}
|
||||
else
|
||||
{
|
||||
ContentType dataContentType = new ContentType(GetOrInferDataContentType(cloudEvent));
|
||||
ContentType dataContentType = new ContentType(GetOrInferDataContentType(cloudEvent)!);
|
||||
if (IsJsonMediaType(dataContentType.MediaType))
|
||||
{
|
||||
writer.WritePropertyName(DataPropertyName);
|
||||
|
@ -696,7 +698,7 @@ namespace CloudNative.CloudEvents.NewtonsoftJson
|
|||
/// <inheritdoc />
|
||||
protected override void EncodeStructuredModeData(CloudEvent cloudEvent, JsonWriter writer)
|
||||
{
|
||||
T data = (T)cloudEvent.Data;
|
||||
var data = (T?)cloudEvent.Data;
|
||||
writer.WritePropertyName(DataPropertyName);
|
||||
Serializer.Serialize(writer, data);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||
<Description>Support for the Protobuf event format in for CloudNative.CloudEvents</Description>
|
||||
<PackageTags>cncf;cloudnative;cloudevents;events;protobuf</PackageTags>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||
<Description>JSON support for the CNCF CloudEvents SDK, based on System.Text.Json.</Description>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<PackageTags>cncf;cloudnative;cloudevents;events;json;systemtextjson</PackageTags>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
using CloudNative.CloudEvents.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
|
@ -94,6 +96,13 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
/// </summary>
|
||||
protected const string DataPropertyName = "data";
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Json serialization context used to serialize and enable trimming and AOT.
|
||||
/// </summary>
|
||||
protected readonly JsonSerializerContext? JsonSerializerContext;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The options to use when serializing objects to JSON.
|
||||
/// </summary>
|
||||
|
@ -108,22 +117,54 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
/// Creates a JsonEventFormatter that uses the default <see cref="JsonSerializerOptions"/>
|
||||
/// and <see cref="JsonDocumentOptions"/> for serializing and parsing.
|
||||
/// </summary>
|
||||
#if NET7_0_OR_GREATER
|
||||
[RequiresUnreferencedCode("Use a constructor that takes a JsonSerializerContext.")]
|
||||
#endif
|
||||
public JsonEventFormatter() : this(null, default)
|
||||
{
|
||||
}
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Creates a JsonEventFormatter that uses the default <see cref="JsonSerializerOptions"/>
|
||||
/// and <see cref="JsonDocumentOptions"/> for serializing and parsing.
|
||||
/// </summary>
|
||||
/// <param name="jsonSerializerContext">The json context used for serializing objects to JSON.</param>
|
||||
public JsonEventFormatter(JsonSerializerContext jsonSerializerContext) : this(default, jsonSerializerContext)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a JsonEventFormatter that uses the specified <see cref="JsonSerializerOptions"/>
|
||||
/// and <see cref="JsonDocumentOptions"/> for serializing and parsing.
|
||||
/// </summary>
|
||||
/// <param name="serializerOptions">The options to use when serializing objects to JSON. May be null.</param>
|
||||
/// <param name="documentOptions">The options to use when parsing JSON documents.</param>
|
||||
#if NET7_0_OR_GREATER
|
||||
[RequiresUnreferencedCode("Use a constructor that takes a JsonSerializerContext.")]
|
||||
#endif
|
||||
public JsonEventFormatter(JsonSerializerOptions? serializerOptions, JsonDocumentOptions documentOptions)
|
||||
{
|
||||
SerializerOptions = serializerOptions;
|
||||
DocumentOptions = documentOptions;
|
||||
}
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Creates a JsonEventFormatter that uses the specified <see cref="JsonSerializerOptions"/>
|
||||
/// and <see cref="JsonDocumentOptions"/> for serializing and parsing.
|
||||
/// </summary>
|
||||
/// <param name="documentOptions">The options to use when parsing JSON documents.</param>
|
||||
/// <param name="jsonSerializerContext">The json context used for serializing objects to JSON.</param>
|
||||
public JsonEventFormatter(JsonDocumentOptions documentOptions, JsonSerializerContext jsonSerializerContext)
|
||||
{
|
||||
Validation.CheckNotNull(jsonSerializerContext, nameof(jsonSerializerContext));
|
||||
DocumentOptions = documentOptions;
|
||||
JsonSerializerContext = jsonSerializerContext;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<CloudEvent> DecodeStructuredModeMessageAsync(Stream body, ContentType? contentType, IEnumerable<CloudEventAttribute>? extensionAttributes) =>
|
||||
await DecodeStructuredModeMessageImpl(body, contentType, extensionAttributes, true).ConfigureAwait(false);
|
||||
|
@ -533,6 +574,10 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
/// <param name="cloudEvent">The CloudEvent being encoded, which will have a non-null value for
|
||||
/// its <see cref="CloudEvent.Data"/> property.
|
||||
/// <param name="writer"/>The writer to serialize the data to. Will not be null.</param>
|
||||
#if NET7_0_OR_GREATER
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Constructor already annotated.")]
|
||||
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Constructor already annotated.")]
|
||||
#endif
|
||||
protected virtual void EncodeStructuredModeData(CloudEvent cloudEvent, Utf8JsonWriter writer)
|
||||
{
|
||||
// Binary data is encoded using the data_base64 property, regardless of content type.
|
||||
|
@ -548,7 +593,12 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
if (IsJsonMediaType(dataContentType.MediaType))
|
||||
{
|
||||
writer.WritePropertyName(DataPropertyName);
|
||||
JsonSerializer.Serialize(writer, cloudEvent.Data, SerializerOptions);
|
||||
#if NET7_0_OR_GREATER
|
||||
if (JsonSerializerContext != null)
|
||||
JsonSerializer.Serialize(writer, cloudEvent.Data, cloudEvent.Data!.GetType(), JsonSerializerContext);
|
||||
else
|
||||
#endif
|
||||
JsonSerializer.Serialize(writer, cloudEvent.Data, SerializerOptions);
|
||||
}
|
||||
else if (cloudEvent.Data is string text && dataContentType.MediaType.StartsWith("text/"))
|
||||
{
|
||||
|
@ -564,6 +614,10 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#if NET7_0_OR_GREATER
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Constructor already annotated.")]
|
||||
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Constructor already annotated.")]
|
||||
#endif
|
||||
public override ReadOnlyMemory<byte> EncodeBinaryModeEventData(CloudEvent cloudEvent)
|
||||
{
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
|
@ -584,10 +638,18 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
var encoding = MimeUtilities.GetEncoding(contentType);
|
||||
if (encoding is UTF8Encoding)
|
||||
{
|
||||
#if NET7_0_OR_GREATER
|
||||
if (JsonSerializerContext != null)
|
||||
return JsonSerializer.SerializeToUtf8Bytes(cloudEvent.Data, cloudEvent.Data.GetType(), JsonSerializerContext);
|
||||
#endif
|
||||
return JsonSerializer.SerializeToUtf8Bytes(cloudEvent.Data, SerializerOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if NET7_0_OR_GREATER
|
||||
if (JsonSerializerContext != null)
|
||||
return MimeUtilities.GetEncoding(contentType).GetBytes(JsonSerializer.Serialize(cloudEvent.Data, cloudEvent.Data.GetType(), JsonSerializerContext));
|
||||
#endif
|
||||
return MimeUtilities.GetEncoding(contentType).GetBytes(JsonSerializer.Serialize(cloudEvent.Data, SerializerOptions));
|
||||
}
|
||||
}
|
||||
|
@ -654,22 +716,57 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
/// Creates a JsonEventFormatter that uses the default <see cref="JsonSerializerOptions"/>
|
||||
/// and <see cref="JsonDocumentOptions"/> for serializing and parsing.
|
||||
/// </summary>
|
||||
#if NET7_0_OR_GREATER
|
||||
[RequiresUnreferencedCode("Use a constructor that takes a JsonSerializerContext.")]
|
||||
#endif
|
||||
public JsonEventFormatter()
|
||||
{
|
||||
}
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Creates a JsonEventFormatter that uses the serializer <see cref="JsonSerializerOptions"/>
|
||||
/// and <see cref="JsonDocumentOptions"/> for serializing and parsing.
|
||||
/// </summary>
|
||||
/// <param name="jsonSerializerContext">The json context used for serializing objects to JSON.</param>
|
||||
public JsonEventFormatter(JsonSerializerContext jsonSerializerContext)
|
||||
: base(default, jsonSerializerContext)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a JsonEventFormatter that uses the serializer <see cref="JsonSerializerOptions"/>
|
||||
/// and <see cref="JsonDocumentOptions"/> for serializing and parsing.
|
||||
/// </summary>
|
||||
/// <param name="serializerOptions">The options to use when serializing and parsing. May be null.</param>
|
||||
/// <param name="documentOptions">The options to use when parsing JSON documents.</param>
|
||||
#if NET7_0_OR_GREATER
|
||||
[RequiresUnreferencedCode("Use a constructor that takes a JsonSerializerContext.")]
|
||||
#endif
|
||||
public JsonEventFormatter(JsonSerializerOptions serializerOptions, JsonDocumentOptions documentOptions)
|
||||
: base(serializerOptions, documentOptions)
|
||||
{
|
||||
}
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// Creates a JsonEventFormatter that uses the serializer <see cref="JsonSerializerOptions"/>
|
||||
/// and <see cref="JsonDocumentOptions"/> for serializing and parsing.
|
||||
/// </summary>
|
||||
/// <param name="documentOptions">The options to use when parsing JSON documents.</param>
|
||||
/// <param name="jsonSerializerContext">The json context used for serializing objects to JSON.</param>
|
||||
public JsonEventFormatter(JsonDocumentOptions documentOptions, JsonSerializerContext jsonSerializerContext)
|
||||
: base(documentOptions, jsonSerializerContext)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <inheritdoc />
|
||||
#if NET7_0_OR_GREATER
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Constructor already annotated.")]
|
||||
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Constructor already annotated.")]
|
||||
#endif
|
||||
public override ReadOnlyMemory<byte> EncodeBinaryModeEventData(CloudEvent cloudEvent)
|
||||
{
|
||||
Validation.CheckCloudEventArgument(cloudEvent, nameof(cloudEvent));
|
||||
|
@ -679,10 +776,18 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
return Array.Empty<byte>();
|
||||
}
|
||||
T data = (T)cloudEvent.Data;
|
||||
#if NET7_0_OR_GREATER
|
||||
if (JsonSerializerContext != null)
|
||||
return JsonSerializer.SerializeToUtf8Bytes(data, data.GetType(), JsonSerializerContext);
|
||||
#endif
|
||||
return JsonSerializer.SerializeToUtf8Bytes(data, SerializerOptions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#if NET7_0_OR_GREATER
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Constructor already annotated.")]
|
||||
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Constructor already annotated.")]
|
||||
#endif
|
||||
public override void DecodeBinaryModeEventData(ReadOnlyMemory<byte> body, CloudEvent cloudEvent)
|
||||
{
|
||||
Validation.CheckNotNull(cloudEvent, nameof(cloudEvent));
|
||||
|
@ -692,22 +797,45 @@ namespace CloudNative.CloudEvents.SystemTextJson
|
|||
cloudEvent.Data = null;
|
||||
return;
|
||||
}
|
||||
cloudEvent.Data = JsonSerializer.Deserialize<T>(body.Span, SerializerOptions);
|
||||
#if NET7_0_OR_GREATER
|
||||
if (JsonSerializerContext != null)
|
||||
cloudEvent.Data = JsonSerializer.Deserialize(body.Span, typeof(T), JsonSerializerContext);
|
||||
else
|
||||
#endif
|
||||
cloudEvent.Data = JsonSerializer.Deserialize<T>(body.Span, SerializerOptions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#if NET7_0_OR_GREATER
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Constructor already annotated.")]
|
||||
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Constructor already annotated.")]
|
||||
#endif
|
||||
protected override void EncodeStructuredModeData(CloudEvent cloudEvent, Utf8JsonWriter writer)
|
||||
{
|
||||
T data = (T)cloudEvent.Data;
|
||||
var data = (T?)cloudEvent.Data;
|
||||
writer.WritePropertyName(DataPropertyName);
|
||||
JsonSerializer.Serialize(writer, data, SerializerOptions);
|
||||
#if NET7_0_OR_GREATER
|
||||
if (JsonSerializerContext != null)
|
||||
JsonSerializer.Serialize(writer, data, data!.GetType(), JsonSerializerContext);
|
||||
else
|
||||
#endif
|
||||
JsonSerializer.Serialize(writer, data, SerializerOptions);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DecodeStructuredModeDataProperty(JsonElement dataElement, CloudEvent cloudEvent) =>
|
||||
// Note: this is an inefficient way of doing this.
|
||||
// See https://github.com/dotnet/runtime/issues/31274 - when that's implemented, we can use the new method here.
|
||||
cloudEvent.Data = JsonSerializer.Deserialize<T>(dataElement.GetRawText(), SerializerOptions);
|
||||
#if NET7_0_OR_GREATER
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Constructor already annotated.")]
|
||||
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "Constructor already annotated.")]
|
||||
#endif
|
||||
protected override void DecodeStructuredModeDataProperty(JsonElement dataElement, CloudEvent cloudEvent)
|
||||
{
|
||||
#if NET7_0_OR_GREATER
|
||||
if (JsonSerializerContext != null)
|
||||
cloudEvent.Data = JsonSerializer.Deserialize(dataElement, typeof(T), JsonSerializerContext);
|
||||
else
|
||||
#endif
|
||||
cloudEvent.Data = JsonSerializer.Deserialize<T>(dataElement.GetRawText(), SerializerOptions);
|
||||
}
|
||||
|
||||
// TODO: Consider decoding the base64 data as a byte array, then using DecodeBinaryModeData.
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
using CloudNative.CloudEvents.Core;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CloudNative.CloudEvents
|
||||
|
@ -21,6 +22,9 @@ namespace CloudNative.CloudEvents
|
|||
/// <summary>
|
||||
/// The type to use for CloudEvent formatting. Must not be null.
|
||||
/// </summary>
|
||||
#if NET7_0_OR_GREATER
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
|
||||
#endif
|
||||
public Type FormatterType { get; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -56,7 +60,7 @@ namespace CloudNative.CloudEvents
|
|||
throw new ArgumentException($"The {nameof(CloudEventFormatterAttribute)} on type {targetType} has no converter type specified.", nameof(targetType));
|
||||
}
|
||||
|
||||
object instance;
|
||||
object? instance;
|
||||
try
|
||||
{
|
||||
instance = Activator.CreateInstance(formatterType);
|
||||
|
@ -73,6 +77,6 @@ namespace CloudNative.CloudEvents
|
|||
}
|
||||
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;net7.0;net8.0</TargetFrameworks>
|
||||
<Description>CNCF CloudEvents SDK</Description>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageTags>cloudnative;cloudevents;events</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace CloudNative.CloudEvents.Core
|
|||
// It's safe to use memory.GetBuffer() and memory.Position here, as this is a stream
|
||||
// we've created using the parameterless constructor.
|
||||
var buffer = memory.GetBuffer();
|
||||
return new ReadOnlyMemory<byte>(buffer, 0, (int) memory.Position);
|
||||
return new ReadOnlyMemory<byte>(buffer, 0, (int)memory.Position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -65,7 +65,7 @@ namespace CloudNative.CloudEvents.Core
|
|||
public static MemoryStream AsStream(ReadOnlyMemory<byte> memory)
|
||||
{
|
||||
var segment = GetArraySegment(memory);
|
||||
return new MemoryStream(segment.Array, segment.Offset, segment.Count, false);
|
||||
return new MemoryStream(segment.Array!, segment.Offset, segment.Count, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -79,7 +79,7 @@ namespace CloudNative.CloudEvents.Core
|
|||
|
||||
// TODO: If we introduce an additional netstandard2.1 target, we can use encoding.GetString(memory.Span)
|
||||
var segment = GetArraySegment(memory);
|
||||
return encoding.GetString(segment.Array, segment.Offset, segment.Count);
|
||||
return encoding.GetString(segment.Array!, segment.Offset, segment.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -92,7 +92,7 @@ namespace CloudNative.CloudEvents.Core
|
|||
{
|
||||
Validation.CheckNotNull(destination, nameof(destination));
|
||||
var segment = GetArraySegment(source);
|
||||
await destination.WriteAsync(segment.Array, segment.Offset, segment.Count).ConfigureAwait(false);
|
||||
await destination.WriteAsync(segment.Array!, segment.Offset, segment.Count).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -108,7 +108,7 @@ namespace CloudNative.CloudEvents.Core
|
|||
var segment = GetArraySegment(memory);
|
||||
// We probably don't actually need to check the offset: if the count is the same as the length,
|
||||
// I can't see how the offset can be non-zero. But it doesn't *hurt* as a check.
|
||||
return segment.Offset == 0 && segment.Count == segment.Array.Length
|
||||
return segment.Array is not null && segment.Offset == 0 && segment.Count == segment.Array.Length
|
||||
? segment.Array
|
||||
: memory.ToArray();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
|
@ -57,7 +58,7 @@ namespace CloudNative.CloudEvents.Core
|
|||
var header = new MediaTypeHeaderValue(contentType.MediaType);
|
||||
foreach (string parameterName in contentType.Parameters.Keys)
|
||||
{
|
||||
header.Parameters.Add(new NameValueHeaderValue(parameterName, contentType.Parameters[parameterName].ToString()));
|
||||
header.Parameters.Add(new NameValueHeaderValue(parameterName, contentType.Parameters[parameterName]!.ToString()));
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
@ -76,7 +77,7 @@ namespace CloudNative.CloudEvents.Core
|
|||
/// </summary>
|
||||
/// <param name="contentType">The content type to check. May be null, in which case the result is false.</param>
|
||||
/// <returns>true if the given content type denotes a (non-batch) CloudEvent; false otherwise</returns>
|
||||
public static bool IsCloudEventsContentType(string? contentType) =>
|
||||
public static bool IsCloudEventsContentType([NotNullWhen(true)] string? contentType) =>
|
||||
contentType is string &&
|
||||
contentType.StartsWith(MediaType, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
!contentType.StartsWith(BatchMediaType, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
@ -86,7 +87,7 @@ namespace CloudNative.CloudEvents.Core
|
|||
/// </summary>
|
||||
/// <param name="contentType">The content type to check. May be null, in which case the result is false.</param>
|
||||
/// <returns>true if the given content type represents a CloudEvent batch; false otherwise</returns>
|
||||
public static bool IsCloudEventsBatchContentType(string? contentType) =>
|
||||
public static bool IsCloudEventsBatchContentType([NotNullWhen(true)] string? contentType) =>
|
||||
contentType is string && contentType.StartsWith(BatchMediaType, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
using CloudNative.CloudEvents.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
@ -60,6 +61,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
Validation.CheckNotNull(httpRequestMessage, nameof(httpRequestMessage));
|
||||
return HasCloudEventsBatchContentType(httpRequestMessage.Content);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this <see cref="HttpResponseMessage"/> holds a batch of CloudEvents.
|
||||
|
@ -83,7 +85,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
this HttpResponseMessage httpResponseMessage,
|
||||
CloudEventFormatter formatter,
|
||||
params CloudEventAttribute[]? extensionAttributes) =>
|
||||
ToCloudEventAsync(httpResponseMessage, formatter, (IEnumerable<CloudEventAttribute>?) extensionAttributes);
|
||||
ToCloudEventAsync(httpResponseMessage, formatter, (IEnumerable<CloudEventAttribute>?)extensionAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts this HTTP response message into a CloudEvent object
|
||||
|
@ -112,7 +114,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
this HttpRequestMessage httpRequestMessage,
|
||||
CloudEventFormatter formatter,
|
||||
params CloudEventAttribute[]? extensionAttributes) =>
|
||||
ToCloudEventAsync(httpRequestMessage, formatter, (IEnumerable<CloudEventAttribute>?) extensionAttributes);
|
||||
ToCloudEventAsync(httpRequestMessage, formatter, (IEnumerable<CloudEventAttribute>?)extensionAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts this HTTP request message into a CloudEvent object.
|
||||
|
@ -130,7 +132,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
return ToCloudEventInternalAsync(httpRequestMessage.Headers, httpRequestMessage.Content, formatter, extensionAttributes, nameof(httpRequestMessage));
|
||||
}
|
||||
|
||||
private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent content,
|
||||
private static async Task<CloudEvent> ToCloudEventInternalAsync(HttpHeaders headers, HttpContent? content,
|
||||
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
|
||||
{
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
@ -142,7 +144,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
}
|
||||
else
|
||||
{
|
||||
string? versionId = MaybeGetVersionId(headers) ?? MaybeGetVersionId(content.Headers);
|
||||
string? versionId = MaybeGetVersionId(headers) ?? MaybeGetVersionId(content?.Headers);
|
||||
if (versionId is null)
|
||||
{
|
||||
throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(paramName));
|
||||
|
@ -151,7 +153,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", paramName);
|
||||
|
||||
var cloudEvent = new CloudEvent(version, extensionAttributes);
|
||||
foreach (var header in headers.Concat(content.Headers))
|
||||
foreach (var header in headers.Concat(content!.Headers))
|
||||
{
|
||||
string? attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key);
|
||||
if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
|
||||
|
@ -231,7 +233,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
return ToCloudEventBatchInternalAsync(httpRequestMessage.Content, formatter, extensionAttributes, nameof(httpRequestMessage));
|
||||
}
|
||||
|
||||
private static async Task<IReadOnlyList<CloudEvent>> ToCloudEventBatchInternalAsync(HttpContent content,
|
||||
private static async Task<IReadOnlyList<CloudEvent>> ToCloudEventBatchInternalAsync(HttpContent? content,
|
||||
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute>? extensionAttributes, string paramName)
|
||||
{
|
||||
Validation.CheckNotNull(formatter, nameof(formatter));
|
||||
|
@ -332,15 +334,15 @@ namespace CloudNative.CloudEvents.Http
|
|||
|
||||
private static ByteArrayContent ToByteArrayContent(ReadOnlyMemory<byte> content) =>
|
||||
MemoryMarshal.TryGetArray(content, out var segment)
|
||||
? new ByteArrayContent(segment.Array, segment.Offset, segment.Count)
|
||||
? new ByteArrayContent(segment.Array!, segment.Offset, segment.Count)
|
||||
// TODO: Just throw?
|
||||
: new ByteArrayContent(content.ToArray());
|
||||
|
||||
// TODO: This would include "application/cloudeventsarerubbish" for example...
|
||||
private static bool HasCloudEventsContentType(HttpContent content) =>
|
||||
private static bool HasCloudEventsContentType([NotNullWhen(true)] HttpContent? content) =>
|
||||
MimeUtilities.IsCloudEventsContentType(content?.Headers?.ContentType?.MediaType);
|
||||
|
||||
private static bool HasCloudEventsBatchContentType(HttpContent content) =>
|
||||
private static bool HasCloudEventsBatchContentType([NotNullWhen(true)] HttpContent? content) =>
|
||||
MimeUtilities.IsCloudEventsBatchContentType(content?.Headers?.ContentType?.MediaType);
|
||||
|
||||
private static string? MaybeGetVersionId(HttpHeaders? headers) =>
|
||||
|
|
|
@ -151,7 +151,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
/// <returns>A reference to a validated CloudEvent instance.</returns>
|
||||
public static CloudEvent ToCloudEvent(this HttpListenerRequest httpListenerRequest,
|
||||
CloudEventFormatter formatter, params CloudEventAttribute[]? extensionAttributes) =>
|
||||
ToCloudEvent(httpListenerRequest, formatter, (IEnumerable<CloudEventAttribute>?) extensionAttributes);
|
||||
ToCloudEvent(httpListenerRequest, formatter, (IEnumerable<CloudEventAttribute>?)extensionAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts this listener request into a CloudEvent object, with the given extension attributes.
|
||||
|
@ -179,7 +179,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
}
|
||||
else
|
||||
{
|
||||
string versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader];
|
||||
string? versionId = httpListenerRequest.Headers[HttpUtilities.SpecVersionHttpHeader];
|
||||
if (versionId is null)
|
||||
{
|
||||
throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(httpListenerRequest));
|
||||
|
@ -191,12 +191,12 @@ namespace CloudNative.CloudEvents.Http
|
|||
var headers = httpListenerRequest.Headers;
|
||||
foreach (var key in headers.AllKeys)
|
||||
{
|
||||
string? attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key);
|
||||
string? attributeName = HttpUtilities.GetAttributeNameFromHeaderName(key!);
|
||||
if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]);
|
||||
string attributeValue = HttpUtilities.DecodeHeaderValue(headers[key]!);
|
||||
cloudEvent.SetAttributeFromString(attributeName, attributeValue);
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
this HttpListenerRequest httpListenerRequest,
|
||||
CloudEventFormatter formatter,
|
||||
params CloudEventAttribute[]? extensionAttributes) =>
|
||||
ToCloudEventBatchAsync(httpListenerRequest, formatter, (IEnumerable<CloudEventAttribute>?)extensionAttributes);
|
||||
ToCloudEventBatchAsync(httpListenerRequest, formatter, (IEnumerable<CloudEventAttribute>?) extensionAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts this HTTP request message into a CloudEvent batch.
|
||||
|
@ -263,6 +263,7 @@ namespace CloudNative.CloudEvents.Http
|
|||
CloudEventFormatter formatter,
|
||||
IEnumerable<CloudEventAttribute>? extensionAttributes) =>
|
||||
ToCloudEventBatchInternalAsync(httpListenerRequest, formatter, extensionAttributes, async: false).GetAwaiter().GetResult();
|
||||
|
||||
|
||||
private async static Task<IReadOnlyList<CloudEvent>> ToCloudEventBatchInternalAsync(HttpListenerRequest httpListenerRequest,
|
||||
CloudEventFormatter formatter, IEnumerable<CloudEventAttribute>? extensionAttributes, bool async)
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://cloudevents.io</PackageProjectUrl>
|
||||
<Copyright>Copyright Cloud Native Foundation</Copyright>
|
||||
|
||||
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">true</IsTrimmable>
|
||||
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Package the icon specified in the PackageIcon property -->
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
using CloudNative.CloudEvents.UnitTests;
|
||||
using CloudNative.CloudEvents.UnitTests.ConformanceTestData;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -21,7 +22,8 @@ public class ConformanceTest
|
|||
private static IEnumerable<object[]> SelectTestIds(ConformanceTestType type) =>
|
||||
allTests
|
||||
.Where(test => test.TestType == type)
|
||||
.Select(test => new object[] { test.Id });
|
||||
.Select(test => new object[][] { [test.Id, true], [test.Id, false] })
|
||||
.SelectMany(x => x);
|
||||
|
||||
public static IEnumerable<object[]> ValidEventTestIds => SelectTestIds(ConformanceTestType.ValidSingleEvent);
|
||||
public static IEnumerable<object[]> InvalidEventTestIds => SelectTestIds(ConformanceTestType.InvalidSingleEvent);
|
||||
|
@ -29,20 +31,21 @@ public class ConformanceTest
|
|||
public static IEnumerable<object[]> InvalidBatchTestIds => SelectTestIds(ConformanceTestType.InvalidBatch);
|
||||
|
||||
[Theory, MemberData(nameof(ValidEventTestIds))]
|
||||
public void ValidEvent(string testId)
|
||||
public void ValidEvent(string testId, bool useContext)
|
||||
{
|
||||
var test = GetTestById(testId);
|
||||
CloudEvent expected = SampleEvents.FromId(test.SampleId);
|
||||
var extensions = test.SampleExtensionAttributes ? SampleEvents.SampleExtensionAttributes : null;
|
||||
CloudEvent actual = new JsonEventFormatter().ConvertFromJsonElement(test.Event, extensions);
|
||||
CloudEvent actual = (useContext ? new JsonEventFormatter(GeneratedJsonContext.Default) : new JsonEventFormatter())
|
||||
.ConvertFromJsonElement(test.Event, extensions);
|
||||
TestHelpers.AssertCloudEventsEqual(expected, actual, TestHelpers.InstantOnlyTimestampComparer);
|
||||
}
|
||||
|
||||
[Theory, MemberData(nameof(InvalidEventTestIds))]
|
||||
public void InvalidEvent(string testId)
|
||||
public void InvalidEvent(string testId, bool useContext)
|
||||
{
|
||||
var test = GetTestById(testId);
|
||||
var formatter = new JsonEventFormatter();
|
||||
var formatter = useContext ? new JsonEventFormatter(GeneratedJsonContext.Default) : new JsonEventFormatter();
|
||||
var extensions = test.SampleExtensionAttributes ? SampleEvents.SampleExtensionAttributes : null;
|
||||
// Hmm... we throw FormatException in some cases, when ArgumentException would be better.
|
||||
// Changing that would be "somewhat breaking"... it's unclear how much we should worry.
|
||||
|
@ -50,7 +53,7 @@ public class ConformanceTest
|
|||
}
|
||||
|
||||
[Theory, MemberData(nameof(ValidBatchTestIds))]
|
||||
public void ValidBatch(string testId)
|
||||
public void ValidBatch(string testId, bool useContext)
|
||||
{
|
||||
var test = GetTestById(testId);
|
||||
IReadOnlyList<CloudEvent> expected = SampleBatches.FromId(test.SampleId);
|
||||
|
@ -58,15 +61,16 @@ public class ConformanceTest
|
|||
// We don't have a convenience method for batches, so serialize the array back to JSON.
|
||||
var json = test.Batch.ToString();
|
||||
var body = Encoding.UTF8.GetBytes(json);
|
||||
IReadOnlyList<CloudEvent> actual = new JsonEventFormatter().DecodeBatchModeMessage(body, contentType: null, extensions);
|
||||
IReadOnlyList<CloudEvent> actual = (useContext ? new JsonEventFormatter(GeneratedJsonContext.Default) : new JsonEventFormatter())
|
||||
.DecodeBatchModeMessage(body, contentType: null, extensions);
|
||||
TestHelpers.AssertBatchesEqual(expected, actual, TestHelpers.InstantOnlyTimestampComparer);
|
||||
}
|
||||
|
||||
[Theory, MemberData(nameof(InvalidBatchTestIds))]
|
||||
public void InvalidBatch(string testId)
|
||||
public void InvalidBatch(string testId, bool useContext)
|
||||
{
|
||||
var test = GetTestById(testId);
|
||||
var formatter = new JsonEventFormatter();
|
||||
var formatter = useContext ? new JsonEventFormatter(GeneratedJsonContext.Default) : new JsonEventFormatter();
|
||||
var extensions = test.SampleExtensionAttributes ? SampleEvents.SampleExtensionAttributes : null;
|
||||
// We don't have a convenience method for batches, so serialize the array back to JSON.
|
||||
var json = test.Batch.ToString();
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2023 Cloud Native Foundation.
|
||||
// Licensed under the Apache 2.0 license.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace CloudNative.CloudEvents.SystemTextJson.UnitTests;
|
||||
|
||||
[System.Text.Json.Serialization.JsonSerializable(typeof(JsonConformanceTest))]
|
||||
[System.Text.Json.Serialization.JsonSerializable(typeof(AttributedModel))]
|
||||
[System.Text.Json.Serialization.JsonSerializable(typeof(int))]
|
||||
internal partial class GeneratedJsonContext : System.Text.Json.Serialization.JsonSerializerContext
|
||||
{
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -14,5 +14,6 @@
|
|||
|
||||
<!-- Never pack any test projects -->
|
||||
<IsPackable>False</IsPackable>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
Loading…
Reference in New Issue