Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/JSCalendar.Net/Alert.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using JSCalendar.Net.Enums;

namespace JSCalendar.Net;

Expand Down Expand Up @@ -35,10 +36,10 @@ public sealed class Alert

/// <summary>
/// How to alert the user (Section 4.5.2).
/// Default: "display"
/// Default: Display
/// </summary>
[JsonPropertyName("action")]
public string Action { get; init; } = "display";
public AlertAction Action { get; init; } = AlertAction.Display;
}

/// <summary>
Expand All @@ -61,10 +62,10 @@ public sealed class OffsetTrigger

/// <summary>
/// Relation to the event time (start or end).
/// Default: "start"
/// Default: Start
/// </summary>
[JsonPropertyName("relativeTo")]
public string RelativeTo { get; init; } = "start";
public TriggerRelation RelativeTo { get; init; } = TriggerRelation.Start;
}

/// <summary>
Expand Down
112 changes: 112 additions & 0 deletions src/JSCalendar.Net/Converters/EnumMemberJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace JSCalendar.Net.Converters;

/// <summary>
/// Generic JSON converter for enums that uses EnumMember attribute values for serialization.
/// This allows enums to be serialized with custom string values like "needs-action" instead of integer values.
/// </summary>
/// <typeparam name="TEnum">The enum type to convert.</typeparam>
public class EnumMemberJsonConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
{
private readonly Dictionary<TEnum, string> _enumToString = new();
private readonly Dictionary<string, TEnum> _stringToEnum = new();

public EnumMemberJsonConverter()
{
var enumType = typeof(TEnum);
var enumValues = Enum.GetValues<TEnum>();

foreach (var value in enumValues)
{
var memberInfo = enumType.GetMember(value.ToString())[0];
var enumMemberAttribute = memberInfo.GetCustomAttribute<EnumMemberAttribute>();

var stringValue = enumMemberAttribute?.Value ?? value.ToString().ToLowerInvariant();

_enumToString[value] = stringValue;
_stringToEnum[stringValue] = value;
}
}

public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
{
throw new JsonException($"Expected string value for enum {typeof(TEnum).Name}");
}

var stringValue = reader.GetString();
if (string.IsNullOrEmpty(stringValue))
{
throw new JsonException($"Null or empty string for enum {typeof(TEnum).Name}");
}

if (_stringToEnum.TryGetValue(stringValue, out var enumValue))
{
return enumValue;
}

throw new JsonException($"Unknown enum value '{stringValue}' for enum {typeof(TEnum).Name}");
}

public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
if (_enumToString.TryGetValue(value, out var stringValue))
{
writer.WriteStringValue(stringValue);
}
else
{
writer.WriteStringValue(value.ToString().ToLowerInvariant());
}
}

public override TEnum ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var stringValue = reader.GetString();
if (string.IsNullOrEmpty(stringValue))
{
throw new JsonException($"Null or empty string for enum property name {typeof(TEnum).Name}");
}

if (_stringToEnum.TryGetValue(stringValue, out var enumValue))
{
return enumValue;
}

throw new JsonException($"Unknown enum value '{stringValue}' for enum property name {typeof(TEnum).Name}");
}

public override void WriteAsPropertyName(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
if (_enumToString.TryGetValue(value, out var stringValue))
{
writer.WritePropertyName(stringValue);
}
else
{
writer.WritePropertyName(value.ToString().ToLowerInvariant());
}
}
}

/// <summary>
/// Factory for creating EnumMemberJsonConverter instances.
/// </summary>
public class EnumMemberJsonConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert.IsEnum;
}

public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var converterType = typeof(EnumMemberJsonConverter<>).MakeGenericType(typeToConvert);
return (JsonConverter?)Activator.CreateInstance(converterType);
}
}
24 changes: 24 additions & 0 deletions src/JSCalendar.Net/Enums/AlertAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using JSCalendar.Net.Converters;

namespace JSCalendar.Net.Enums;

/// <summary>
/// Alert action types (RFC 8984 Section 8.4.3).
/// </summary>
[JsonConverter(typeof(EnumMemberJsonConverterFactory))]
public enum AlertAction
{
/// <summary>
/// Display the alert.
/// </summary>
[EnumMember(Value = "display")]
Display,

/// <summary>
/// Send an email alert.
/// </summary>
[EnumMember(Value = "email")]
Email
}
54 changes: 54 additions & 0 deletions src/JSCalendar.Net/Enums/DayOfWeek.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using JSCalendar.Net.Converters;

namespace JSCalendar.Net.Enums;

/// <summary>
/// Day of week values (RFC 8984 Section 4.3.3).
/// </summary>
[JsonConverter(typeof(EnumMemberJsonConverterFactory))]
public enum DayOfWeek
{
/// <summary>
/// Monday.
/// </summary>
[EnumMember(Value = "mo")]
Monday,

/// <summary>
/// Tuesday.
/// </summary>
[EnumMember(Value = "tu")]
Tuesday,

/// <summary>
/// Wednesday.
/// </summary>
[EnumMember(Value = "we")]
Wednesday,

/// <summary>
/// Thursday.
/// </summary>
[EnumMember(Value = "th")]
Thursday,

/// <summary>
/// Friday.
/// </summary>
[EnumMember(Value = "fr")]
Friday,

/// <summary>
/// Saturday.
/// </summary>
[EnumMember(Value = "sa")]
Saturday,

/// <summary>
/// Sunday.
/// </summary>
[EnumMember(Value = "su")]
Sunday
}
30 changes: 30 additions & 0 deletions src/JSCalendar.Net/Enums/EventStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using JSCalendar.Net.Converters;

namespace JSCalendar.Net.Enums;

/// <summary>
/// Event status types (RFC 8984 Section 8.4.3).
/// </summary>
[JsonConverter(typeof(EnumMemberJsonConverterFactory))]
public enum EventStatus
{
/// <summary>
/// Indicates the event is definitely happening.
/// </summary>
[EnumMember(Value = "confirmed")]
Confirmed,

/// <summary>
/// Indicates the event has been cancelled.
/// </summary>
[EnumMember(Value = "cancelled")]
Cancelled,

/// <summary>
/// Indicates the event may happen.
/// </summary>
[EnumMember(Value = "tentative")]
Tentative
}
24 changes: 24 additions & 0 deletions src/JSCalendar.Net/Enums/FreeBusyStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using JSCalendar.Net.Converters;

namespace JSCalendar.Net.Enums;

/// <summary>
/// Free/busy status (RFC 8984 Section 8.4.3).
/// </summary>
[JsonConverter(typeof(EnumMemberJsonConverterFactory))]
public enum FreeBusyStatus
{
/// <summary>
/// The object should be ignored when calculating whether the user is busy.
/// </summary>
[EnumMember(Value = "free")]
Free,

/// <summary>
/// The object should be included when calculating whether the user is busy.
/// </summary>
[EnumMember(Value = "busy")]
Busy
}
36 changes: 36 additions & 0 deletions src/JSCalendar.Net/Enums/LinkDisplayType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using JSCalendar.Net.Converters;

namespace JSCalendar.Net.Enums;

/// <summary>
/// Link display types for icons (RFC 8984 Section 8.4.3).
/// </summary>
[JsonConverter(typeof(EnumMemberJsonConverterFactory))]
public enum LinkDisplayType
{
/// <summary>
/// An image meant to be displayed alongside the title.
/// </summary>
[EnumMember(Value = "badge")]
Badge,

/// <summary>
/// A full image replacement for the object itself.
/// </summary>
[EnumMember(Value = "graphic")]
Graphic,

/// <summary>
/// An image that is used to enhance the object.
/// </summary>
[EnumMember(Value = "fullsize")]
Fullsize,

/// <summary>
/// A smaller variant of fullsize to be used when space for the image is constrained.
/// </summary>
[EnumMember(Value = "thumbnail")]
Thumbnail
}
24 changes: 24 additions & 0 deletions src/JSCalendar.Net/Enums/LocationRelation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using JSCalendar.Net.Converters;

namespace JSCalendar.Net.Enums;

/// <summary>
/// Location relative-to values (RFC 8984 Section 8.4.3).
/// </summary>
[JsonConverter(typeof(EnumMemberJsonConverterFactory))]
public enum LocationRelation
{
/// <summary>
/// The event/task occurs at this location at the start time.
/// </summary>
[EnumMember(Value = "start")]
Start,

/// <summary>
/// The event/task occurs at this location at the end time.
/// </summary>
[EnumMember(Value = "end")]
End
}
36 changes: 36 additions & 0 deletions src/JSCalendar.Net/Enums/ParticipantKind.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using JSCalendar.Net.Converters;

namespace JSCalendar.Net.Enums;

/// <summary>
/// Participant kind types (RFC 8984 Section 8.4.3).
/// </summary>
[JsonConverter(typeof(EnumMemberJsonConverterFactory))]
public enum ParticipantKind
{
/// <summary>
/// A single person.
/// </summary>
[EnumMember(Value = "individual")]
Individual,

/// <summary>
/// A collection of people invited as a whole.
/// </summary>
[EnumMember(Value = "group")]
Group,

/// <summary>
/// A non-human resource other than a location.
/// </summary>
[EnumMember(Value = "resource")]
Resource,

/// <summary>
/// A physical location that needs to be scheduled.
/// </summary>
[EnumMember(Value = "location")]
Location
}
Loading
Loading