Skip to content
This repository was archived by the owner on Sep 3, 2024. It is now read-only.

Commit ca16da6

Browse files
Merge pull request #40 from thefringeninja/list-streams
Support List Streams API
2 parents 9461b2d + 5f424ed commit ca16da6

File tree

13 files changed

+196
-4
lines changed

13 files changed

+196
-4
lines changed

src/SqlStreamStore.HAL/AllStream/AllStreamResource.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public async Task<Response> Get(
5757
.FromOperation(operation)
5858
.Index()
5959
.Find()
60+
.Browse()
6061
.AllStreamNavigation(page, operation))
6162
.AddEmbeddedCollection(
6263
Constants.Relations.Message,

src/SqlStreamStore.HAL/AllStreamMessage/AllStreamMessageResource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public async Task<Response> Get(
2929
.FromOperation(operation)
3030
.Index()
3131
.Find()
32+
.Browse()
3233
.Add(
3334
Constants.Relations.Message,
3435
$"stream/{message.Position}",
@@ -62,7 +63,7 @@ public async Task<Response> Get(
6263
message.Type,
6364
payload,
6465
metadata = message.JsonMetadata
65-
}).AddLinks(links));
66+
}).AddLinks(links));
6667
}
6768
}
6869
}

src/SqlStreamStore.HAL/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public static class Relations
6060
public const string DeleteStream = StreamStorePrefix + ":delete-stream";
6161
public const string DeleteMessage = StreamStorePrefix + ":delete-message";
6262
public const string Find = StreamStorePrefix + ":find";
63+
public const string Browse = StreamStorePrefix + ":feed-browser";
6364
}
6465

6566
public static class Streams
@@ -71,6 +72,7 @@ public static class Streams
7172
public static PathString AllStreamPath = new PathString($"/{All}");
7273
public static PathString StreamsPath = new PathString($"/{Stream}");
7374
public static PathString IndexPath = new PathString("/");
75+
public static PathString StreamBrowserPath = StreamsPath;
7476
}
7577

7678
public static class ReadDirection

src/SqlStreamStore.HAL/Index/IndexResource.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ namespace SqlStreamStore.HAL.Index
44
using System.Reflection;
55
using Halcyon.HAL;
66
using Microsoft.AspNetCore.Http;
7-
using Newtonsoft.Json;
87
using Newtonsoft.Json.Linq;
98

109
internal class IndexResource : IResource
@@ -43,8 +42,7 @@ private static string GetVersion(Type type)
4342
.FromPath(PathString.Empty)
4443
.Index().Self()
4544
.Find()
45+
.Browse()
4646
.Add(Constants.Relations.Feed, Constants.Streams.All)));
47-
48-
public override string ToString() => _data.ToString(Formatting.None);
4947
}
5048
}

src/SqlStreamStore.HAL/LinksExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ public static Links Index(this Links links) =>
77

88
public static Links Find(this Links links)
99
=> links.Add(Constants.Relations.Find, "streams/{streamId}", "Find a Stream");
10+
11+
public static Links Browse(this Links links)
12+
=> links.Add(Constants.Relations.Browse, "streams{?p,t,m}", "Browse Streams");
1013
}
1114
}

src/SqlStreamStore.HAL/SqlStreamStoreHalMiddleware.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using SqlStreamStore.HAL.Docs;
1212
using SqlStreamStore.HAL.Index;
1313
using SqlStreamStore.HAL.Logging;
14+
using SqlStreamStore.HAL.StreamBrowser;
1415
using SqlStreamStore.HAL.StreamMessage;
1516
using SqlStreamStore.HAL.StreamMetadata;
1617
using SqlStreamStore.HAL.Streams;
@@ -62,6 +63,7 @@ public static IApplicationBuilder UseSqlStreamStoreHal(
6263
var index = new IndexResource(streamStore);
6364
var allStream = new AllStreamResource(streamStore, options.UseCanonicalUrls);
6465
var allStreamMessages = new AllStreamMessageResource(streamStore);
66+
var streamBrowser = new StreamBrowserResource(streamStore);
6567
var streams = new StreamResource(streamStore);
6668
var streamMetadata = new StreamMetadataResource(streamStore);
6769
var streamMessages = new StreamMessageResource(streamStore);
@@ -83,6 +85,7 @@ public static IApplicationBuilder UseSqlStreamStoreHal(
8385
.UseIndex(index)
8486
.UseAllStream(allStream)
8587
.UseAllStreamMessage(allStreamMessages)
88+
.UseStreamBrowser(streamBrowser)
8689
.UseStreams(streams)
8790
.UseStreamMetadata(streamMetadata)
8891
.UseStreamMessages(streamMessages);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
namespace SqlStreamStore.HAL.StreamBrowser
2+
{
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Http;
6+
using SqlStreamStore.Streams;
7+
8+
internal class ListStreamsOperation : IStreamStoreOperation<ListStreamsPage>
9+
{
10+
public Pattern Pattern { get; }
11+
public string ContinuationToken { get; }
12+
public int MaxCount { get; }
13+
public string PatternType { get; }
14+
public PathString Path { get; }
15+
16+
public ListStreamsOperation(HttpRequest request)
17+
{
18+
Path = request.Path;
19+
if(request.Query.TryGetValueCaseInsensitive('t', out var patternType))
20+
{
21+
PatternType = patternType;
22+
}
23+
24+
if(!request.Query.TryGetValueCaseInsensitive('p', out var pattern))
25+
{
26+
Pattern = Pattern.Anything();
27+
}
28+
else
29+
{
30+
switch(PatternType)
31+
{
32+
case "s":
33+
Pattern = Pattern.StartsWith(pattern);
34+
break;
35+
case "e":
36+
Pattern = Pattern.EndsWith(pattern);
37+
break;
38+
default:
39+
Pattern = Pattern.Anything();
40+
break;
41+
}
42+
}
43+
44+
if(request.Query.TryGetValueCaseInsensitive('c', out var continuationToken))
45+
{
46+
ContinuationToken = continuationToken;
47+
}
48+
49+
MaxCount = request.Query.TryGetValueCaseInsensitive('m', out var m)
50+
&& int.TryParse(m, out var maxCount)
51+
? maxCount
52+
: 100;
53+
}
54+
55+
public Task<ListStreamsPage> Invoke(IStreamStore streamStore, CancellationToken cancellationToken)
56+
=> streamStore.ListStreams(Pattern, MaxCount, ContinuationToken, cancellationToken);
57+
}
58+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace SqlStreamStore.HAL.StreamBrowser
2+
{
3+
using SqlStreamStore.Streams;
4+
5+
internal static class StreamBrowserLinkExtensions
6+
{
7+
public static Links StreamBrowserNavigation(
8+
this Links links,
9+
ListStreamsPage listStreamsPage,
10+
ListStreamsOperation operation)
11+
{
12+
if(operation.ContinuationToken != null)
13+
{
14+
links.Add(
15+
Constants.Relations.Next,
16+
FormatLink(operation, listStreamsPage.ContinuationToken));
17+
}
18+
19+
if(listStreamsPage.ContinuationToken != null)
20+
{
21+
links.Add(
22+
Constants.Relations.Next,
23+
FormatLink(operation, listStreamsPage.ContinuationToken));
24+
}
25+
26+
27+
return links
28+
.Add(
29+
Constants.Relations.Browse,
30+
FormatLink(operation, listStreamsPage.ContinuationToken))
31+
.Self();
32+
}
33+
34+
private static string FormatLink(ListStreamsOperation operation, string continuationToken) =>
35+
$"streams?p={operation.Pattern.Value}&t={operation.PatternType}&c={continuationToken}&m={operation.MaxCount}";
36+
}
37+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace SqlStreamStore.HAL.StreamBrowser
2+
{
3+
using System;
4+
using System.Net.Http;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Http;
7+
using MidFunc = System.Func<
8+
Microsoft.AspNetCore.Http.HttpContext,
9+
System.Func<System.Threading.Tasks.Task>,
10+
System.Threading.Tasks.Task
11+
>;
12+
13+
internal static class StreamBrowserMiddleware
14+
{
15+
public static IApplicationBuilder UseStreamBrowser(
16+
this IApplicationBuilder builder,
17+
StreamBrowserResource streamBrowser)
18+
=> builder.MapWhen(IsMatch, Configure(streamBrowser));
19+
20+
private static bool IsMatch(HttpContext context)
21+
=> context.Request.Path.IsStreamBrowser();
22+
23+
public static bool IsStreamBrowser(this PathString requestPath)
24+
=> requestPath == Constants.Streams.StreamBrowserPath;
25+
26+
private static Action<IApplicationBuilder> Configure(StreamBrowserResource streamBrowser)
27+
=> builder => builder
28+
.UseMiddlewareLogging(typeof(StreamBrowserMiddleware))
29+
.MapWhen(HttpMethod.Get, inner => inner.Use(BrowseStreams(streamBrowser)));
30+
31+
private static MidFunc BrowseStreams(StreamBrowserResource streamBrowser)
32+
=> async (context, next) =>
33+
{
34+
var response = await streamBrowser.Get(
35+
new ListStreamsOperation(context.Request),
36+
context.RequestAborted);
37+
38+
await context.WriteResponse(response);
39+
};
40+
}
41+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
namespace SqlStreamStore.HAL.StreamBrowser
2+
{
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Halcyon.HAL;
7+
using SqlStreamStore.HAL.StreamBrowser;
8+
9+
internal class StreamBrowserResource
10+
{
11+
private readonly IStreamStore _streamStore;
12+
13+
public StreamBrowserResource(IStreamStore streamStore)
14+
{
15+
_streamStore = streamStore;
16+
}
17+
18+
public async Task<Response> Get(ListStreamsOperation operation, CancellationToken cancellationToken)
19+
{
20+
var listStreamsPage = await operation.Invoke(_streamStore, cancellationToken);
21+
22+
return new HalJsonResponse(new HALResponse(new
23+
{
24+
listStreamsPage.ContinuationToken
25+
})
26+
.AddLinks(
27+
Links
28+
.FromOperation(operation)
29+
.Index()
30+
.Find()
31+
.StreamBrowserNavigation(listStreamsPage, operation))
32+
.AddEmbeddedCollection(
33+
Constants.Relations.Feed,
34+
Array.ConvertAll(
35+
listStreamsPage.StreamIds,
36+
streamId => new HALResponse(null)
37+
.AddLinks(
38+
Links
39+
.FromOperation(operation)
40+
.Add(Constants.Relations.Feed, $"{Constants.Streams.Stream}/{streamId}", streamId)
41+
.Self()))));
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)