Skip to content

Commit

Permalink
Added JsonToken types.
Browse files Browse the repository at this point in the history
  • Loading branch information
kekyo committed Apr 17, 2024
1 parent 2557811 commit b8486e2
Show file tree
Hide file tree
Showing 7 changed files with 772 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
////////////////////////////////////////////////////////////////////////////

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;

namespace DupeNukem.Internal;
namespace DupeNukem;

internal static class ConverterContext
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static class ConverterContext
{
// JsonSerializer cannot pass application-specific context information
// to the Converter during serialization runs.
Expand Down Expand Up @@ -68,27 +70,37 @@ public void Exit(IMessenger messenger)
private static readonly ThreadLocal<MessengerContext> messengers =
new(() => new MessengerContext());

public static Messenger Current
internal static Messenger Current
{
get
{
AssertValidState();
return (Messenger)messengers.Value!.Current;

// If the cast fails, you need real `Messenger` that actually works.
// Perhaps you are using mocks in your unit tests.
// Messenger is necessary for successful DupeNukem serialization.
if (messengers.Value!.Current is not Messenger m)
{
throw new InvalidOperationException(
"DupeNukem: You need real `Messenger` instance.");
}
return m;
}
}

[Conditional("DEBUG")]
public static void AssertValidState() =>
internal static void AssertValidState() =>
Debug.Assert(
messengers.Value!.Current is Messenger,
"Invalid state: Not called correctly.");

public static void Enter(IMessenger messenger) =>
internal static void Enter(IMessenger messenger) =>
messengers.Value!.Enter(messenger);

public static void Exit(IMessenger messenger) =>
internal static void Exit(IMessenger messenger) =>
messengers.Value!.Exit(messenger);

[EditorBrowsable(EditorBrowsableState.Advanced)]
public static void Run(IMessenger messenger, Action action)
{
messengers.Value!.Enter(messenger);
Expand All @@ -102,6 +114,7 @@ public static void Run(IMessenger messenger, Action action)
}
}

[EditorBrowsable(EditorBrowsableState.Advanced)]
public static T Run<T>(IMessenger messenger, Func<T> action)
{
messengers.Value!.Enter(messenger);
Expand All @@ -114,4 +127,28 @@ public static T Run<T>(IMessenger messenger, Func<T> action)
messengers.Value!.Exit(messenger);
}
}

[EditorBrowsable(EditorBrowsableState.Advanced)]
public static IDisposable Begin(IMessenger messenger)
{
messengers.Value!.Enter(messenger);
return new Disposer(messenger);
}

private sealed class Disposer : IDisposable
{
private IMessenger? messenger;

public Disposer(IMessenger messenger) =>
this.messenger = messenger;

public void Dispose()
{
if (this.messenger is { } messenger)
{
this.messenger = null!;
messengers.Value!.Exit(messenger);
}
}
}
}
49 changes: 49 additions & 0 deletions DupeNukem.Core/Internal/JsonTokenConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
////////////////////////////////////////////////////////////////////////////
//
// DupeNukem - WebView attachable full-duplex asynchronous interoperable
// independent messaging library between .NET and JavaScript.
//
// Copyright (c) Kouji Matsui (@kozy_kekyo, @kekyo@mastodon.cloud)
//
// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
//
////////////////////////////////////////////////////////////////////////////

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace DupeNukem.Internal;

internal sealed class JsonTokenConverter : JsonConverter
{
public override bool CanConvert(Type objectType) =>
typeof(JsonToken).IsAssignableFrom(objectType);

public override object? ReadJson(
JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
ConverterContext.AssertValidState();

if (serializer.Deserialize<JToken>(reader) is { } token)
{
return JsonToken.FromJToken(ConverterContext.Current, token);
}
return null;
}

public override void WriteJson(
JsonWriter writer, object? value, JsonSerializer serializer)
{
ConverterContext.AssertValidState();

if (value is JsonToken t)
{
serializer.Serialize(writer, t.token);
}
else
{
writer.WriteNull();
}
}
}
24 changes: 2 additions & 22 deletions DupeNukem.Core/Internal/MethodDescriptors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,8 @@ arg is { } a ?
Activator.CreateInstance(type) :
null;

private protected IDisposable BeginConverterContext()
{
ConverterContext.Enter(this.messenger);
return new Disposer(this.messenger);
}

private sealed class Disposer : IDisposable
{
private IMessenger? messenger;

public Disposer(IMessenger messenger) =>
this.messenger = messenger;

public void Dispose()
{
if (this.messenger is { } messenger)
{
this.messenger = null;
ConverterContext.Exit(messenger);
}
}
}
private protected IDisposable BeginConverterContext() =>
ConverterContext.Begin(this.messenger);
}

///////////////////////////////////////////////////////////////////////////////
Expand Down
20 changes: 17 additions & 3 deletions DupeNukem.Core/Internal/SuspendingDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,20 @@ public override void Cancel() =>
// SuspendingDescriptor with a return value type.
internal sealed class SuspendingDescriptor<TR> : SuspendingDescriptor
{
private readonly Messenger messenger;
private readonly TaskCompletionSource<TR> tcs = new();

public SuspendingDescriptor(Messenger messenger) =>
this.messenger = messenger;

public Task<TR> Task =>
this.tcs.Task;

public override void Resolve(JToken? result) =>
this.tcs.TrySetResult(result is { } ? result.ToObject<TR>()! : default!);
this.tcs.TrySetResult(result is { } ?
ConverterContext.Run(this.messenger, () =>
result.ToObject<TR>(this.messenger.Serializer)!) :
default!);
public override void Reject(Exception ex) =>
this.tcs.TrySetException(ex);
public override void Cancel() =>
Expand All @@ -64,17 +71,24 @@ public override void Cancel() =>
// SuspendingDescriptor with a return value type at runtime.
internal sealed class DynamicSuspendingDescriptor : SuspendingDescriptor
{
private readonly Messenger messenger;
private readonly Type returnType;
private readonly TaskCompletionSource<object?> tcs = new();

public DynamicSuspendingDescriptor(Type returnType) =>
public DynamicSuspendingDescriptor(Messenger messenger, Type returnType)
{
this.messenger = messenger;
this.returnType = returnType;
}

public Task<object?> Task =>
this.tcs.Task;

public override void Resolve(JToken? result) =>
this.tcs.TrySetResult(result is { } ? result.ToObject(this.returnType)! : default!);
this.tcs.TrySetResult(result is { } ?
ConverterContext.Run(this.messenger, () =>
result.ToObject(this.returnType, this.messenger.Serializer)!) :
default!);
public override void Reject(Exception ex) =>
this.tcs.TrySetException(ex);
public override void Cancel() =>
Expand Down
Loading

0 comments on commit b8486e2

Please sign in to comment.