Skip to content

Commit 67c6890

Browse files
C# HTTP handlers - Module Bindings (#5024)
# Description of Changes Adding C# HTTP handlers based on #4636 - adds the C# handler/router API `[SpacetimeDB.HttpHandler]`, `[SpacetimeDB.HttpRouter]` - wires C# HTTP handlers into module definition/build/runtime registration - mirrors the Rust/TypeScript HTTP smoketests and adds C# docs coverage - updates the HTTP handlers docs with C# examples - refactored ProcedureContext to allow for a central location for WithTx/TryWithTx to support HandlerContext - routes use a generated `Handlers.*` tokens to avoid raw strings # API and ABI breaking changes Adds new APIs for the HTTP handler and should not be breaking # Expected complexity level and risk 3 - this hit the binding, module registration, and had a decent refactor for ProcedureContext # Testing - [x] Expanded `crates/smoketests/tests/smoketests/http_routes.rs` with C# mirrors of the Rust HTTP route tests I also did some manual testing with a throw away project, and will be adding to the `module-test` after all languages are caught up on HTTP handlers.
1 parent cadc224 commit 67c6890

18 files changed

Lines changed: 2026 additions & 262 deletions

File tree

crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
}
2424
},
2525
{/*
26-
SpacetimeDB.Internal.Module.RegisterTable<global::TestUniqueNotEquatable, SpacetimeDB.Internal.TableHandles.TestUniqueNotEquatable>();
26+
2727
SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_FILTER);
2828
^^^^^^^^^
2929
SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_FOURTH_FILTER);

crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs

Lines changed: 73 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings-csharp/Codegen.Tests/fixtures/explicitnames/snapshots/Module#FFI.verified.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public readonly partial struct QueryBuilder
4747
new("DemoTable", new DemoTableCols("DemoTable"), new DemoTableIxCols("DemoTable"));
4848
}
4949

50+
public static class Handlers { }
51+
5052
public sealed record ReducerContext : DbContext<Local>, Internal.IReducerContext
5153
{
5254
public readonly Identity Sender;
@@ -210,6 +212,46 @@ public Uuid NewUuidV7()
210212
}
211213
}
212214

215+
public sealed partial class HandlerContext : global::SpacetimeDB.HandlerContextBase
216+
{
217+
private readonly Local _db = new();
218+
219+
internal HandlerContext(Random random, Timestamp time)
220+
: base(random, time) { }
221+
222+
protected override global::SpacetimeDB.LocalBase CreateLocal() => _db;
223+
224+
protected override global::SpacetimeDB.HandlerTxContextBase CreateTxContext(
225+
Internal.TxContext inner
226+
) => _cached ??= new HandlerTxContext(inner);
227+
228+
private HandlerTxContext? _cached;
229+
230+
[Experimental("STDB_UNSTABLE")]
231+
public TResult WithTx<TResult>(Func<HandlerTxContext, TResult> body) =>
232+
base.WithTx(tx => body((HandlerTxContext)tx));
233+
234+
[Experimental("STDB_UNSTABLE")]
235+
public TxOutcome<TResult> TryWithTx<TResult, TError>(
236+
Func<HandlerTxContext, Result<TResult, TError>> body
237+
)
238+
where TError : Exception => base.TryWithTx(tx => body((HandlerTxContext)tx));
239+
240+
public Uuid NewUuidV4()
241+
{
242+
var bytes = new byte[16];
243+
Rng.NextBytes(bytes);
244+
return Uuid.FromRandomBytesV4(bytes);
245+
}
246+
247+
public Uuid NewUuidV7()
248+
{
249+
var bytes = new byte[4];
250+
Rng.NextBytes(bytes);
251+
return Uuid.FromCounterV7(ref CounterUuid, Timestamp, bytes);
252+
}
253+
}
254+
213255
[Experimental("STDB_UNSTABLE")]
214256
public sealed class ProcedureTxContext : global::SpacetimeDB.ProcedureTxContextBase
215257
{
@@ -219,6 +261,15 @@ internal ProcedureTxContext(Internal.TxContext inner)
219261
public new Local Db => (Local)base.Db;
220262
}
221263

264+
[Experimental("STDB_UNSTABLE")]
265+
public sealed class HandlerTxContext : global::SpacetimeDB.HandlerTxContextBase
266+
{
267+
internal HandlerTxContext(Internal.TxContext inner)
268+
: base(inner) { }
269+
270+
public new Local Db => (Local)base.Db;
271+
}
272+
222273
public sealed class Local : global::SpacetimeDB.LocalBase
223274
{
224275
public global::SpacetimeDB.Internal.TableHandles.DemoTable DemoTable => new();
@@ -560,6 +611,9 @@ public static void Main()
560611
"canonical_index"
561612
);
562613

614+
SpacetimeDB.Internal.Module.SetHandlerContextConstructor(
615+
(random, time) => new SpacetimeDB.HandlerContext(random, time)
616+
);
563617
var __memoryStream = new MemoryStream();
564618
var __writer = new BinaryWriter(__memoryStream);
565619

@@ -635,6 +689,24 @@ SpacetimeDB.Internal.BytesSink result_sink
635689
result_sink
636690
);
637691

692+
[UnmanagedCallersOnly(EntryPoint = "__call_http_handler__")]
693+
public static SpacetimeDB.Internal.Errno __call_http_handler__(
694+
uint id,
695+
SpacetimeDB.Timestamp timestamp,
696+
SpacetimeDB.Internal.BytesSource request,
697+
SpacetimeDB.Internal.BytesSource request_body,
698+
SpacetimeDB.Internal.BytesSink response_sink,
699+
SpacetimeDB.Internal.BytesSink response_body_sink
700+
) =>
701+
SpacetimeDB.Internal.Module.__call_http_handler__(
702+
id,
703+
timestamp,
704+
request,
705+
request_body,
706+
response_sink,
707+
response_body_sink
708+
);
709+
638710
[UnmanagedCallersOnly(EntryPoint = "__call_view__")]
639711
public static SpacetimeDB.Internal.Errno __call_view__(
640712
uint id,

crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,8 @@ public readonly partial struct QueryBuilder
489489
);
490490
}
491491

492+
public static class Handlers { }
493+
492494
public sealed record ReducerContext : DbContext<Local>, Internal.IReducerContext
493495
{
494496
public readonly Identity Sender;
@@ -652,6 +654,46 @@ public Uuid NewUuidV7()
652654
}
653655
}
654656

657+
public sealed partial class HandlerContext : global::SpacetimeDB.HandlerContextBase
658+
{
659+
private readonly Local _db = new();
660+
661+
internal HandlerContext(Random random, Timestamp time)
662+
: base(random, time) { }
663+
664+
protected override global::SpacetimeDB.LocalBase CreateLocal() => _db;
665+
666+
protected override global::SpacetimeDB.HandlerTxContextBase CreateTxContext(
667+
Internal.TxContext inner
668+
) => _cached ??= new HandlerTxContext(inner);
669+
670+
private HandlerTxContext? _cached;
671+
672+
[Experimental("STDB_UNSTABLE")]
673+
public TResult WithTx<TResult>(Func<HandlerTxContext, TResult> body) =>
674+
base.WithTx(tx => body((HandlerTxContext)tx));
675+
676+
[Experimental("STDB_UNSTABLE")]
677+
public TxOutcome<TResult> TryWithTx<TResult, TError>(
678+
Func<HandlerTxContext, Result<TResult, TError>> body
679+
)
680+
where TError : Exception => base.TryWithTx(tx => body((HandlerTxContext)tx));
681+
682+
public Uuid NewUuidV4()
683+
{
684+
var bytes = new byte[16];
685+
Rng.NextBytes(bytes);
686+
return Uuid.FromRandomBytesV4(bytes);
687+
}
688+
689+
public Uuid NewUuidV7()
690+
{
691+
var bytes = new byte[4];
692+
Rng.NextBytes(bytes);
693+
return Uuid.FromCounterV7(ref CounterUuid, Timestamp, bytes);
694+
}
695+
}
696+
655697
[Experimental("STDB_UNSTABLE")]
656698
public sealed class ProcedureTxContext : global::SpacetimeDB.ProcedureTxContextBase
657699
{
@@ -661,6 +703,15 @@ internal ProcedureTxContext(Internal.TxContext inner)
661703
public new Local Db => (Local)base.Db;
662704
}
663705

706+
[Experimental("STDB_UNSTABLE")]
707+
public sealed class HandlerTxContext : global::SpacetimeDB.HandlerTxContextBase
708+
{
709+
internal HandlerTxContext(Internal.TxContext inner)
710+
: base(inner) { }
711+
712+
public new Local Db => (Local)base.Db;
713+
}
714+
664715
public sealed class Local : global::SpacetimeDB.LocalBase
665716
{
666717
internal global::SpacetimeDB.Internal.TableHandles.BTreeMultiColumn BTreeMultiColumn =>
@@ -2450,6 +2501,9 @@ public static void Main()
24502501
(identity, connectionId, random, time) =>
24512502
new SpacetimeDB.ProcedureContext(identity, connectionId, random, time)
24522503
);
2504+
SpacetimeDB.Internal.Module.SetHandlerContextConstructor(
2505+
(random, time) => new SpacetimeDB.HandlerContext(random, time)
2506+
);
24532507
var __memoryStream = new MemoryStream();
24542508
var __writer = new BinaryWriter(__memoryStream);
24552509

@@ -2499,6 +2553,7 @@ public static void Main()
24992553
global::Timers.SendMessageTimer,
25002554
SpacetimeDB.Internal.TableHandles.SendMessageTimer
25012555
>();
2556+
25022557
SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(
25032558
global::Module.ALL_PUBLIC_TABLES
25042559
);
@@ -2562,6 +2617,24 @@ SpacetimeDB.Internal.BytesSink result_sink
25622617
result_sink
25632618
);
25642619

2620+
[UnmanagedCallersOnly(EntryPoint = "__call_http_handler__")]
2621+
public static SpacetimeDB.Internal.Errno __call_http_handler__(
2622+
uint id,
2623+
SpacetimeDB.Timestamp timestamp,
2624+
SpacetimeDB.Internal.BytesSource request,
2625+
SpacetimeDB.Internal.BytesSource request_body,
2626+
SpacetimeDB.Internal.BytesSink response_sink,
2627+
SpacetimeDB.Internal.BytesSink response_body_sink
2628+
) =>
2629+
SpacetimeDB.Internal.Module.__call_http_handler__(
2630+
id,
2631+
timestamp,
2632+
request,
2633+
request_body,
2634+
response_sink,
2635+
response_body_sink
2636+
);
2637+
25652638
[UnmanagedCallersOnly(EntryPoint = "__call_view__")]
25662639
public static SpacetimeDB.Internal.Errno __call_view__(
25672640
uint id,

0 commit comments

Comments
 (0)