diff options
author | Emma [it/its]@Rory& <root@rory.gay> | 2024-06-01 19:02:28 +0200 |
---|---|---|
committer | Emma [it/its]@Rory& <root@rory.gay> | 2024-06-01 19:03:05 +0200 |
commit | a129b321998614b20e4ebb8a7c1632553ebee981 (patch) | |
tree | f4e16f7b1bbeffc21e81f8749e4980994242ff19 | |
parent | Event serialisation fix (diff) | |
download | LibMatrix-a129b321998614b20e4ebb8a7c1632553ebee981.tar.xz |
Split event abstractions
18 files changed, 324 insertions, 116 deletions
diff --git a/ArcaneLibs b/ArcaneLibs -Subproject 68eca20fbf4d5c08b6960fb2362ba3733d2df9e +Subproject 65d6fa98cc05ed3c37e71797145e8eb7f2c072a diff --git a/LibMatrix.EventTypes/MatrixEventContent.cs b/LibMatrix.EventTypes.Abstractions/BaseMatrixEventContent.cs index 81b8c52..eba50a5 100644 --- a/LibMatrix.EventTypes/MatrixEventContent.cs +++ b/LibMatrix.EventTypes.Abstractions/BaseMatrixEventContent.cs @@ -12,21 +12,18 @@ namespace LibMatrix.EventTypes; /// <seealso cref="System.Text.Json.Nodes.JsonNode"/> /// <seealso cref="System.Text.Json.Nodes.JsonObject"/> /// </summary> -[JsonConverter(typeof(MatrixEventContentConverter<MatrixEventContent>))] -public class MatrixEventContent { - - [JsonExtensionData, JsonInclude] +[JsonConverter(typeof(MatrixEventContentConverter<BaseMatrixEventContent>))] +// [JsonSerializable(typeof(MatrixEventContent))] +public class BaseMatrixEventContent { public JsonObject InternalJson { get; set; } = new(); - - - public MatrixEventContent() { } + public BaseMatrixEventContent() { } - public MatrixEventContent(JsonNode json) { + public BaseMatrixEventContent(JsonNode json) { InternalJson = json.AsObject(); } - public static implicit operator MatrixEventContent(JsonNode json) => new(json); + public static implicit operator BaseMatrixEventContent(JsonNode json) => new(json); // public static implicit operator JsonNode(MatrixEventContent content) => content.InternalJson; @@ -41,20 +38,17 @@ public class MatrixEventContent { public string ToJson() => InternalJson.ToJson(); - public class MatrixEventContentConverter<T> : JsonConverter<T> where T : MatrixEventContent, new() { + public class MatrixEventContentConverter<T> : JsonConverter<T> where T : BaseMatrixEventContent, new() { public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // read entire object into a JsonObject + // Console.WriteLine($"MatrixEventContentConverter<T>: Reading {typeToConvert}"); var json = JsonNode.Parse(ref reader); return new T { InternalJson = json.AsObject() }; } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { + // Console.WriteLine($"MatrixEventContentConverter<T>: Writing {value.GetType()}"); value.InternalJson.WriteTo(writer); } } -} - -public class MatrixEventAttribute(string eventType, bool deprecated = false) : Attribute { - public string EventType { get; } = eventType; - public bool Deprecated { get; } = deprecated; } \ No newline at end of file diff --git a/LibMatrix.EventTypes.Abstractions/Converters/EventConverterFactory.cs b/LibMatrix.EventTypes.Abstractions/Converters/EventConverterFactory.cs new file mode 100644 index 0000000..3b4c493 --- /dev/null +++ b/LibMatrix.EventTypes.Abstractions/Converters/EventConverterFactory.cs @@ -0,0 +1,112 @@ +/*namespace LibMatrix.EventTypes.Converters; + +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +public class MatrixEventConverter : JsonConverterFactory { + public override bool CanConvert(Type typeToConvert) { + Console.WriteLine(typeToConvert); + if (!typeToConvert.IsGenericType) { + return false; + } + + if (typeToConvert.GetGenericTypeDefinition() != typeof(MatrixEvent<>)) { + return false; + } + + return typeToConvert.GetGenericArguments()[0].IsAssignableTo(typeof(MatrixEventContent)); + } + + public override JsonConverter CreateConverter( + Type type, + JsonSerializerOptions options) { + Type[] typeArguments = type.GetGenericArguments(); + Type keyType = typeArguments[0]; + Type valueType = typeArguments[1]; + + JsonConverter converter = (JsonConverter)Activator.CreateInstance( + typeof(DictionaryEnumConverterInner<,>).MakeGenericType( + [keyType, valueType]), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + args: [options], + culture: null)!; + + return converter; + } + + private class DictionaryEnumConverterInner<TKey, TValue> : + JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum { + private readonly JsonConverter<TValue> _valueConverter; + private readonly Type _keyType; + private readonly Type _valueType; + + public DictionaryEnumConverterInner(JsonSerializerOptions options) { + // For performance, use the existing converter. + _valueConverter = (JsonConverter<TValue>)options + .GetConverter(typeof(TValue)); + + // Cache the key and value types. + _keyType = typeof(TKey); + _valueType = typeof(TValue); + } + + public override Dictionary<TKey, TValue> Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { + if (reader.TokenType != JsonTokenType.StartObject) { + throw new JsonException(); + } + + var dictionary = new Dictionary<TKey, TValue>(); + + while (reader.Read()) { + if (reader.TokenType == JsonTokenType.EndObject) { + return dictionary; + } + + // Get the key. + if (reader.TokenType != JsonTokenType.PropertyName) { + throw new JsonException(); + } + + string? propertyName = reader.GetString(); + + // For performance, parse with ignoreCase:false first. + if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) && + !Enum.TryParse(propertyName, ignoreCase: true, out key)) { + throw new JsonException( + $"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\"."); + } + + // Get the value. + reader.Read(); + TValue value = _valueConverter.Read(ref reader, _valueType, options)!; + + // Add to dictionary. + dictionary.Add(key, value); + } + + throw new JsonException(); + } + + public override void Write( + Utf8JsonWriter writer, + Dictionary<TKey, TValue> dictionary, + JsonSerializerOptions options) { + writer.WriteStartObject(); + + foreach ((TKey key, TValue value) in dictionary) { + string propertyName = key.ToString(); + writer.WritePropertyName + (options.PropertyNamingPolicy?.ConvertName(propertyName) ?? propertyName); + + _valueConverter.Write(writer, value, options); + } + + writer.WriteEndObject(); + } + } +}*/ \ No newline at end of file diff --git a/LibMatrix.EventTypes.Abstractions/Converters/EventJsonSerializerContext.cs b/LibMatrix.EventTypes.Abstractions/Converters/EventJsonSerializerContext.cs new file mode 100644 index 0000000..5b8e2dc --- /dev/null +++ b/LibMatrix.EventTypes.Abstractions/Converters/EventJsonSerializerContext.cs @@ -0,0 +1,10 @@ +// using System.Text.Json; +// using System.Text.Json.Serialization; +// using System.Text.Json.Serialization.Metadata; +// +// namespace LibMatrix.EventTypes.Converters; +// +// [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] +// [JsonSerializable(typeof(MatrixEvent<>))] +// internal partial class EventJsonSerializerContext : JsonSerializerContext { +// } \ No newline at end of file diff --git a/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj b/LibMatrix.EventTypes.Abstractions/LibMatrix.EventTypes.Abstractions.csproj index bd33993..dce51ea 100644 --- a/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj +++ b/LibMatrix.EventTypes.Abstractions/LibMatrix.EventTypes.Abstractions.csproj @@ -4,7 +4,7 @@ <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> - <OutputType>Exe</OutputType> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> diff --git a/LibMatrix.EventTypes.Abstractions/MatrixEvent.cs b/LibMatrix.EventTypes.Abstractions/MatrixEvent.cs new file mode 100644 index 0000000..0e548c6 --- /dev/null +++ b/LibMatrix.EventTypes.Abstractions/MatrixEvent.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.EventTypes; + +public interface IMatrixEvent<out T> where T : BaseMatrixEventContent; +public class MatrixEvent<T> : IMatrixEvent<T> where T : BaseMatrixEventContent { + [JsonPropertyName("content")] + public T? Content { get; set; } +} \ No newline at end of file diff --git a/LibMatrix.EventTypes.Abstractions/MatrixEventAttribute.cs b/LibMatrix.EventTypes.Abstractions/MatrixEventAttribute.cs new file mode 100644 index 0000000..aface6d --- /dev/null +++ b/LibMatrix.EventTypes.Abstractions/MatrixEventAttribute.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.EventTypes; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class MatrixEventAttribute(string eventType, bool deprecated = false) : Attribute { + public string EventType { get; } = eventType; + public bool Deprecated { get; } = deprecated; +} \ No newline at end of file diff --git a/LibMatrix.EventTypes/MatrixEventCollection.cs b/LibMatrix.EventTypes.Abstractions/MatrixEventCollection.cs index 78886d9..78886d9 100644 --- a/LibMatrix.EventTypes/MatrixEventCollection.cs +++ b/LibMatrix.EventTypes.Abstractions/MatrixEventCollection.cs diff --git a/LibMatrix.EventTypes.Spec/LibMatrix.EventTypes.Spec.csproj b/LibMatrix.EventTypes.Spec/LibMatrix.EventTypes.Spec.csproj new file mode 100644 index 0000000..5bfaedf --- /dev/null +++ b/LibMatrix.EventTypes.Spec/LibMatrix.EventTypes.Spec.csproj @@ -0,0 +1,26 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net8.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <TrimEnabled>true</TrimEnabled> + <PublishTrimmed>true</PublishTrimmed> + <TrimMode>full</TrimMode> + + + <Optimize>true</Optimize> + <RunAOTCompilation>true</RunAOTCompilation> + <PublishTrimmed>true</PublishTrimmed> + <PublishReadyToRun>true</PublishReadyToRun> + <PublishSingleFile>true</PublishSingleFile> + <PublishReadyToRunShowWarnings>true</PublishReadyToRunShowWarnings> + <PublishTrimmedShowLinkerSizeComparison>true</PublishTrimmedShowLinkerSizeComparison> + <PublishTrimmedShowLinkerSizeComparisonWarnings>true</PublishTrimmedShowLinkerSizeComparisonWarnings> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\LibMatrix.EventTypes.Abstractions\LibMatrix.EventTypes.Abstractions.csproj" /> + </ItemGroup> + +</Project> diff --git a/LibMatrix.EventTypes/Events/RoomMembershipEventContent.cs b/LibMatrix.EventTypes.Spec/RoomMembershipEventContent.cs index fe50a2e..370a192 100644 --- a/LibMatrix.EventTypes/Events/RoomMembershipEventContent.cs +++ b/LibMatrix.EventTypes.Spec/RoomMembershipEventContent.cs @@ -4,9 +4,11 @@ namespace LibMatrix.EventTypes.Events; [MatrixEvent("m.room.member")] [JsonConverter(typeof(MatrixEventContentConverter<RoomMembershipEventContent>))] -public class RoomMembershipEventContent : MatrixEventContent { +public class RoomMembershipEventContent : BaseMatrixEventContent { public string Membership { get => InternalJson["membership"]!.GetValue<string>(); set => InternalJson["membership"] = value; } + + public string? Something { get; set; } } \ No newline at end of file diff --git a/LibMatrix.EventTypes.Spec/RoomMessageEventContent.cs b/LibMatrix.EventTypes.Spec/RoomMessageEventContent.cs new file mode 100644 index 0000000..f2a5483 --- /dev/null +++ b/LibMatrix.EventTypes.Spec/RoomMessageEventContent.cs @@ -0,0 +1,57 @@ +// using System.Text.Json.Serialization; +// using LibMatrix.EventTypes; +// +// namespace LibMatrix.LegacyEvents.EventTypes.Spec; +// +// [MatrixEvent(EventId)] +// public class RoomMessageEventContent : MatrixEventContent { +// public const string EventId = "m.room.message"; +// +// public RoomMessageEventContent(string messageType = "m.notice", string? body = null) { +// MessageType = messageType; +// Body = body ?? ""; +// } +// +// [JsonPropertyName("body")] +// public string Body { get; set; } +// +// [JsonPropertyName("msgtype")] +// public string MessageType { get; set; } = "m.notice"; +// +// [JsonPropertyName("formatted_body")] +// public string? FormattedBody { get; set; } +// +// [JsonPropertyName("format")] +// public string? Format { get; set; } +// +// /// <summary> +// /// Media URI for this message, if any +// /// </summary> +// [JsonPropertyName("url")] +// public string? Url { get; set; } +// +// public string? FileName { get; set; } +// +// [JsonPropertyName("info")] +// public FileInfoStruct? FileInfo { get; set; } +// +// [JsonIgnore] +// public string BodyWithoutReplyFallback => Body.Split('\n').SkipWhile(x => x.StartsWith(">")).SkipWhile(x=>x.Trim().Length == 0).Aggregate((x, y) => $"{x}\n{y}"); +// +// public class FileInfoStruct { +// [JsonPropertyName("mimetype")] +// public string? MimeType { get; set; } +// +// [JsonPropertyName("size")] +// public long Size { get; set; } +// +// [JsonPropertyName("thumbnail_url")] +// public string? ThumbnailUrl { get; set; } +// +// [JsonPropertyName("w")] +// public int? Width { get; set; } +// +// [JsonPropertyName("h")] +// public int? Height { get; set; } +// } +// } \ No newline at end of file diff --git a/LibMatrix.EventTypes/Events/RoomMessageEventContent.cs b/LibMatrix.EventTypes/Events/RoomMessageEventContent.cs deleted file mode 100644 index 55c2b6c..0000000 --- a/LibMatrix.EventTypes/Events/RoomMessageEventContent.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.EventTypes; - -namespace LibMatrix.LegacyEvents.EventTypes.Spec; - -[MatrixEvent(EventId)] -public class RoomMessageEventContent : MatrixEventContent { - public const string EventId = "m.room.message"; - - public RoomMessageEventContent(string messageType = "m.notice", string? body = null) { - MessageType = messageType; - Body = body ?? ""; - } - - [JsonPropertyName("body")] - public string Body { get; set; } - - [JsonPropertyName("msgtype")] - public string MessageType { get; set; } = "m.notice"; - - [JsonPropertyName("formatted_body")] - public string? FormattedBody { get; set; } - - [JsonPropertyName("format")] - public string? Format { get; set; } - - /// <summary> - /// Media URI for this message, if any - /// </summary> - [JsonPropertyName("url")] - public string? Url { get; set; } - - public string? FileName { get; set; } - - [JsonPropertyName("info")] - public FileInfoStruct? FileInfo { get; set; } - - [JsonIgnore] - public string BodyWithoutReplyFallback => Body.Split('\n').SkipWhile(x => x.StartsWith(">")).SkipWhile(x=>x.Trim().Length == 0).Aggregate((x, y) => $"{x}\n{y}"); - - public class FileInfoStruct { - [JsonPropertyName("mimetype")] - public string? MimeType { get; set; } - - [JsonPropertyName("size")] - public long Size { get; set; } - - [JsonPropertyName("thumbnail_url")] - public string? ThumbnailUrl { get; set; } - - [JsonPropertyName("w")] - public int? Width { get; set; } - - [JsonPropertyName("h")] - public int? Height { get; set; } - } -} \ No newline at end of file diff --git a/LibMatrix.EventTypes/MatrixEvent.cs b/LibMatrix.EventTypes/MatrixEvent.cs deleted file mode 100644 index 63f1e75..0000000 --- a/LibMatrix.EventTypes/MatrixEvent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace LibMatrix.EventTypes; - -public interface IMatrixEvent<out T> where T : MatrixEventContent; -public class MatrixEvent<T> : IMatrixEvent<T> where T : MatrixEventContent { - [JsonPropertyName("content")] - public T? Content { get; set; } -} \ No newline at end of file diff --git a/LibMatrix.EventTypes/temp/Program.cs b/LibMatrix.EventTypes/temp/Program.cs deleted file mode 100644 index 22a65d4..0000000 --- a/LibMatrix.EventTypes/temp/Program.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Events; - -namespace LibMatrix.EventTypes.temp; - -public class Program { - // public MatrixEventCollection<MatrixEventContent> Members = [ - // new MatrixEvent<RoomMembershipEventContent>() { - // Content = new() { - // Membership = "join" - // } - // } - // ]; - - public static void Main(string[] args) { - var evt = new RoomMembershipEventContent() { - Membership = "join" - }; - Console.WriteLine(evt.ToJson()); - - var eventJson = File.ReadAllText("test-event.json"); - var evt2 = JsonSerializer.Deserialize<MatrixEvent<RoomMembershipEventContent>>(eventJson); - evt2.Content.Membership = "meow"; - Console.WriteLine(evt2.Content.ToJson()); - Console.WriteLine(ObjectExtensions.ToJson(evt2)); - - } -} \ No newline at end of file diff --git a/LibMatrix/LegacyMatrixEvent.cs b/LibMatrix.LegacyEvents.EventTypes/LegacyMatrixEvent.cs index a1ac5db..3cb9ecb 100644 --- a/LibMatrix/LegacyMatrixEvent.cs +++ b/LibMatrix.LegacyEvents.EventTypes/LegacyMatrixEvent.cs @@ -1,6 +1,7 @@ #if !DISABLE_LEGACY_EVENTS using System.Collections.Frozen; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; @@ -9,7 +10,6 @@ using ArcaneLibs; using ArcaneLibs.Attributes; using ArcaneLibs.Extensions; using LibMatrix.LegacyEvents.EventTypes; -using LibMatrix.Extensions; namespace LibMatrix; @@ -224,4 +224,28 @@ public class StateEventContentPolymorphicTypeInfoResolver : DefaultJsonTypeInfoR */ #endregion -#endif \ No newline at end of file +#endif + +public class JsonFloatStringConverter : JsonConverter<float> { + public override float Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => float.Parse(reader.GetString()!); + + public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); +} + +public class JsonDoubleStringConverter : JsonConverter<double> { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => double.Parse(reader.GetString()!); + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); +} + +public class JsonDecimalStringConverter : JsonConverter<decimal> { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => decimal.Parse(reader.GetString()!); + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture)); +} \ No newline at end of file diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj index 69f2bf4..d6c9155 100644 --- a/LibMatrix/LibMatrix.csproj +++ b/LibMatrix/LibMatrix.csproj @@ -10,7 +10,7 @@ <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <!-- event rewrite dev... --> - <DefineConstants Condition="!$(ExtraDefineConstants.Contains('WITH_LEGACY_EVENTS'))">$(DefineConstants);DISABLE_LEGACY_EVENTS</DefineConstants> +<!-- <DefineConstants Condition="!$(ExtraDefineConstants.Contains('WITH_LEGACY_EVENTS'))">$(DefineConstants);DISABLE_LEGACY_EVENTS</DefineConstants>--> <DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants> </PropertyGroup> diff --git a/Utilities/LibMatrix.EventTypes.BasicTests/LibMatrix.EventTypes.BasicTests.csproj b/Utilities/LibMatrix.EventTypes.BasicTests/LibMatrix.EventTypes.BasicTests.csproj new file mode 100644 index 0000000..b769cb7 --- /dev/null +++ b/Utilities/LibMatrix.EventTypes.BasicTests/LibMatrix.EventTypes.BasicTests.csproj @@ -0,0 +1,24 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net8.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\..\LibMatrix.EventTypes.Abstractions\LibMatrix.EventTypes.Abstractions.csproj" /> + <ProjectReference Include="..\..\LibMatrix.EventTypes.Spec\LibMatrix.EventTypes.Spec.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="BenchmarkDotNet" Version="0.13.12" /> + </ItemGroup> + + <ItemGroup> + <Content Include="*.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + </ItemGroup> +</Project> diff --git a/Utilities/LibMatrix.EventTypes.BasicTests/Program.cs b/Utilities/LibMatrix.EventTypes.BasicTests/Program.cs new file mode 100644 index 0000000..8c1e15a --- /dev/null +++ b/Utilities/LibMatrix.EventTypes.BasicTests/Program.cs @@ -0,0 +1,37 @@ +using System.Text.Json; +using ArcaneLibs.Extensions; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Running; +using LibMatrix.EventTypes; +using LibMatrix.EventTypes.Events; + +BenchmarkRunner.Run<Tests>(); + +[ShortRunJob] +[MemoryDiagnoser] +public class Tests { + // public MatrixEventCollection<MatrixEventContent> Members = [ + // new MatrixEvent<RoomMembershipEventContent>() { + // Content = new() { + // Membership = "join" + // } + // } + // ]; + + private static string eventJson = File.ReadAllText("test-event.json"); + private static MatrixEvent<RoomMembershipEventContent> evt2 = JsonSerializer.Deserialize<MatrixEvent<RoomMembershipEventContent>>(eventJson); + [Benchmark] + public void Deserialise() { + JsonSerializer.Deserialize<MatrixEvent<RoomMembershipEventContent>>(eventJson); + } + [Benchmark] + public void Serialise() { + evt2.ToJson(); + } + + [Benchmark] + public void Modify() { + evt2.Content.Membership = "meow"; + } +} \ No newline at end of file |