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

Commit ef2a1ed

Browse files
probably fixed hillariously broken relative path generation
1 parent 8323818 commit ef2a1ed

22 files changed

+84
-52
lines changed

src/SqlStreamStore.HAL.Tests/AllStreamMessageTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace SqlStreamStore.HAL.Tests
33
using System;
44
using System.Net;
55
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Http;
67
using Shouldly;
78
using Xunit;
89

@@ -31,7 +32,7 @@ public async Task read_single_message_all_stream()
3132

3233
resource.ShouldLink(
3334
Links
34-
.RootedAt("../")
35+
.FromRequestMessage(response.RequestMessage)
3536
.Find()
3637
.Index()
3738
.AddSelf(Constants.Relations.Message, "stream/0")
@@ -49,7 +50,7 @@ public async Task read_single_message_does_not_exist_all_stream()
4950
var resource = await response.AsHal();
5051

5152
resource.ShouldLink(Links
52-
.RootedAt("../")
53+
.FromPath(new PathString("/stream/0"))
5354
.AddSelf(Constants.Relations.Message, "stream/0")
5455
.Add(Constants.Relations.Feed, HeadOfAll));
5556
}

src/SqlStreamStore.HAL.Tests/StreamMessageTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public async Task read_single_message_stream()
3131
var resource = await response.AsHal();
3232
3333
resource.ShouldLink(Links
34-
.RootedAt("../../")
34+
.FromRequestMessage(response.RequestMessage)
3535
.Index()
3636
.Find()
3737
.Add(Constants.Relations.Self, "streams/a-stream/0")
@@ -54,7 +54,7 @@ public async Task read_single_message_does_not_exist_stream()
5454
var resource = await response.AsHal();
5555

5656
resource.ShouldLink(Links
57-
.RootedAt("../../")
57+
.FromRequestMessage(response.RequestMessage)
5858
.Index()
5959
.Find()
6060
.Add(Constants.Relations.Self, "streams/a-stream/0")

src/SqlStreamStore.HAL.Tests/StreamMetadataTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ await _fixture.HttpClient.SendAsync(
4242

4343
resource.ShouldLink(
4444
Links
45-
.RootedAt("../../../")
45+
.FromRequestMessage(response.RequestMessage)
4646
.Index()
4747
.Find()
4848
.Add(Constants.Relations.Metadata, $"streams/{StreamId}/metadata").Self()
@@ -93,7 +93,7 @@ await _fixture.HttpClient.SendAsync(
9393

9494
resource.ShouldLink(
9595
Links
96-
.RootedAt("../../../")
96+
.FromRequestMessage(response.RequestMessage)
9797
.Index()
9898
.Find()
9999
.Add(Constants.Relations.Metadata, $"streams/{StreamId}/metadata").Self()
@@ -139,7 +139,7 @@ public async Task set_metadata()
139139

140140
resource.ShouldLink(
141141
Links
142-
.RootedAt("../../../")
142+
.FromRequestMessage(response.RequestMessage)
143143
.Index()
144144
.Find()
145145
.Add(Constants.Relations.Metadata, $"streams/{StreamId}/metadata").Self()

src/SqlStreamStore.HAL.Tests/StreamNavigationTests.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public async Task read_head_link_no_messages(string path, string root, HttpStatu
4646
var resource = await response.AsHal();
4747

4848
var links = Links
49-
.RootedAt(root)
49+
.FromRequestMessage(response.RequestMessage)
5050
.Index()
5151
.Find()
5252
.Add(Constants.Relations.Self, $"{path}?{LastLinkQuery}")
@@ -85,7 +85,7 @@ public async Task read_head_link_when_multiple_pages(string path, string root)
8585
var resource = await response.AsHal();
8686

8787
var links = Links
88-
.RootedAt(root)
88+
.FromRequestMessage(response.RequestMessage)
8989
.Index()
9090
.Find()
9191
.Add(Constants.Relations.Self, $"{path}?{LastLinkQuery}")
@@ -118,7 +118,8 @@ public async Task read_first_link(string path, string root)
118118

119119
var resource = await response.AsHal();
120120

121-
var links = Links.RootedAt(root)
121+
var links = Links
122+
.FromRequestMessage(response.RequestMessage)
122123
.Index()
123124
.Find()
124125
.Add(Constants.Relations.Self, $"{path}?{FirstLinkQuery}")
@@ -146,7 +147,8 @@ public async Task read_first_link_when_multiple_pages(string path, string root)
146147

147148
var resource = await response.AsHal();
148149

149-
var links = Links.RootedAt(root)
150+
var links = Links
151+
.FromRequestMessage(response.RequestMessage)
150152
.Index()
151153
.Find()
152154
.Add(Constants.Relations.Self, $"{path}?{FirstLinkQuery}")

src/SqlStreamStore.HAL/AllStream/AllStreamResource.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public async Task<Response> Get(
5454
})
5555
.AddLinks(
5656
Links
57-
.RootedAt(string.Empty)
57+
.FromOperation(operation)
5858
.Index()
5959
.Find()
6060
.AllStreamNavigation(page, operation))
@@ -74,7 +74,8 @@ public async Task<Response> Get(
7474
metadata = message.JsonMetadata
7575
})
7676
.AddLinks(
77-
Links.RootedAt(string.Empty)
77+
Links
78+
.FromOperation(operation)
7879
.Add(
7980
Constants.Relations.Message,
8081
$"streams/{message.StreamId}/{message.StreamVersion}")

src/SqlStreamStore.HAL/AllStream/ReadAllStreamOperation.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ internal class ReadAllStreamOperation : IStreamStoreOperation<ReadAllPage>
1212

1313
public ReadAllStreamOperation(HttpRequest request)
1414
{
15+
Path = request.Path;
16+
1517
EmbedPayload = request.Query.TryGetValueCaseInsensitive('e', out var embedPayload)
1618
&& embedPayload == "1";
1719

@@ -62,6 +64,7 @@ public ReadAllStreamOperation(HttpRequest request)
6264
public int ReadDirection { get; }
6365
public string Self { get; }
6466
public bool IsUriCanonical { get; }
67+
public PathString Path { get; }
6568

6669
public Task<ReadAllPage> Invoke(IStreamStore streamStore, CancellationToken ct)
6770
=> ReadDirection == Constants.ReadDirection.Forwards

src/SqlStreamStore.HAL/AllStreamMessage/AllStreamMessageResource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public async Task<Response> Get(
2525
{
2626
var message = await operation.Invoke(_streamStore, cancellationToken);
2727

28-
var links = Links.RootedAt("../")
28+
var links = Links
29+
.FromOperation(operation)
2930
.Index()
3031
.Find()
3132
.Add(Constants.Relations.Message, $"stream/{message.Position}").Self()

src/SqlStreamStore.HAL/AllStreamMessage/ReadAllStreamMessageOperation.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ internal class ReadAllStreamMessageOperation : IStreamStoreOperation<StreamMessa
1010
{
1111
public ReadAllStreamMessageOperation(HttpRequest request)
1212
{
13+
Path = request.Path;
1314
Position = long.Parse(request.Path.Value.Remove(0, 2 + Constants.Streams.All.Length));
1415
}
1516

1617
public long Position { get; }
18+
public PathString Path { get; }
1719

1820
public async Task<StreamMessage> Invoke(IStreamStore streamStore, CancellationToken ct)
1921
{

src/SqlStreamStore.HAL/IStreamStoreOperation.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
{
33
using System.Threading;
44
using System.Threading.Tasks;
5+
using Microsoft.AspNetCore.Http;
56

67
internal interface IStreamStoreOperation<T>
78
{
9+
PathString Path { get; }
810
Task<T> Invoke(IStreamStore streamStore, CancellationToken cancellationToken);
911
}
1012
}

src/SqlStreamStore.HAL/Index/IndexResource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace SqlStreamStore.HAL.Index
33
using System;
44
using System.Reflection;
55
using Halcyon.HAL;
6+
using Microsoft.AspNetCore.Http;
67
using Newtonsoft.Json;
78
using Newtonsoft.Json.Linq;
89

@@ -39,7 +40,7 @@ private static string GetVersion(Type type)
3940
public Response Get() => new HalJsonResponse(new HALResponse(_data)
4041
.AddLinks(
4142
Links
42-
.RootedAt(string.Empty)
43+
.FromPath(PathString.Empty)
4344
.Index().Self()
4445
.Find()
4546
.Add(Constants.Relations.Feed, Constants.Streams.All)));

src/SqlStreamStore.HAL/Links.cs

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,36 @@ namespace SqlStreamStore.HAL
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Net.Http;
57
using Halcyon.HAL;
8+
using Microsoft.AspNetCore.Http;
9+
using SqlStreamStore.HAL.Logging;
610

711
internal class Links
812
{
13+
private static readonly ILog s_log = LogProvider.For<Links>();
14+
15+
private readonly PathString _path;
916
private readonly List<(string rel, string href, string title)> _links;
1017
private readonly string _relativePathToRoot;
1118

12-
public static Links RootedAt(string relativePathToRoot) => new Links(relativePathToRoot);
19+
public static Links FromOperation<T>(IStreamStoreOperation<T> operation) => FromPath(operation.Path);
20+
21+
public static Links FromPath(PathString path) => new Links(path);
1322

14-
private Links(string relativePathToRoot)
23+
public static Links FromRequestMessage(HttpRequestMessage requestMessage)
24+
=> FromPath(new PathString(requestMessage.RequestUri.AbsolutePath));
25+
26+
private Links(PathString path)
1527
{
16-
if(!String.IsNullOrEmpty(relativePathToRoot))
17-
{
18-
if(!relativePathToRoot.StartsWith(".."))
19-
{
20-
throw new ArgumentException("Non-empty relative path to root must start with '..'",
21-
nameof(relativePathToRoot));
22-
}
23-
24-
if(!relativePathToRoot.EndsWith("/"))
25-
{
26-
throw new ArgumentException("Non-empty relative path to root must end with '/'",
27-
nameof(relativePathToRoot));
28-
}
29-
}
28+
_path = path;
29+
var segmentCount = path.Value.Split('/').Length;
30+
_relativePathToRoot =
31+
segmentCount < 2
32+
? "./"
33+
: string.Join(string.Empty, Enumerable.Repeat("../", segmentCount - 2));
3034

31-
_relativePathToRoot = relativePathToRoot ?? String.Empty;
3235
_links = new List<(string rel, string href, string title)>();
3336
}
3437

@@ -38,16 +41,18 @@ public Links Add(string rel, string href, string title = null)
3841
throw new ArgumentNullException(nameof(rel));
3942
if(href == null)
4043
throw new ArgumentNullException(nameof(href));
41-
44+
4245
_links.Add((rel, href, title));
46+
47+
s_log.Debug($"Added link {_links[_links.Count - 1]} to response for request {_path}");
4348
return this;
4449
}
4550

4651
public Links Self()
4752
{
48-
var link = _links[_links.Count - 1];
53+
var (_, href, title) = _links[_links.Count - 1];
4954

50-
return Add(Constants.Relations.Self, link.href, link.title);
55+
return Add(Constants.Relations.Self, href, title);
5156
}
5257

5358
public Links AddSelf(string rel, string href, string title = null)
@@ -61,17 +66,16 @@ public Link[] ToHalLinks()
6166
for(var i = 0; i < _links.Count; i++)
6267
{
6368
var (rel, href, title) = _links[i];
64-
var resolvedHref = $"{_relativePathToRoot}{href}";
6569

66-
links[i] = new Link(rel, resolvedHref, title, replaceParameters: false)
70+
links[i] = new Link(rel, Resolve(href), title, replaceParameters: false)
6771
{
6872
Type = Constants.MediaTypes.HalJson
6973
};
7074
}
7175

7276
links[_links.Count] = new Link(
7377
Constants.Relations.Curies,
74-
$"{_relativePathToRoot}docs/{{rel}}",
78+
Resolve("docs/{rel}"),
7579
"Documentation",
7680
replaceParameters: false)
7781
{
@@ -83,6 +87,8 @@ public Link[] ToHalLinks()
8387
return links;
8488
}
8589

90+
private string Resolve(string relativeUrl) => $"{_relativePathToRoot}{relativeUrl}";
91+
8692
public static implicit operator Link[](Links links) => links.ToHalLinks();
8793

8894
private static string FormatLink(

src/SqlStreamStore.HAL/StreamMessage/DeleteStreamMessageOperation.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ internal class DeleteStreamMessageOperation : IStreamStoreOperation<Unit>
1010
{
1111
public DeleteStreamMessageOperation(HttpRequest request)
1212
{
13+
Path = request.Path;
1314
var pieces = request.Path.Value.Split('/').Reverse().Take(2).ToArray();
1415

1516
StreamId = pieces.LastOrDefault();
@@ -27,9 +28,7 @@ public DeleteStreamMessageOperation(HttpRequest request)
2728
public string StreamId { get; }
2829
public int? StreamVersion { get; }
2930
public Guid? MessageId { get; }
30-
31-
public Func<IStreamStore, CancellationToken, Task> GetDeleteOperation()
32-
=> async (streamStore, ct) => { await Invoke(streamStore, ct); };
31+
public PathString Path { get; }
3332

3433
public async Task<Unit> Invoke(IStreamStore streamStore, CancellationToken ct)
3534
{
@@ -44,7 +43,7 @@ public async Task<Unit> Invoke(IStreamStore streamStore, CancellationToken ct)
4443
|| message.StreamVersion == StreamVersion)
4544
.MessageId;
4645
await streamStore.DeleteMessage(StreamId, messageId, ct);
47-
46+
4847
return Unit.Instance;
4948
}
5049
}

src/SqlStreamStore.HAL/StreamMessage/ReadStreamMessageByStreamVersionOperation.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ internal class ReadStreamMessageByStreamVersionOperation : IStreamStoreOperation
1010
{
1111
public ReadStreamMessageByStreamVersionOperation(HttpRequest request)
1212
{
13+
Path = request.Path;
14+
1315
var pieces = request.Path.Value.Split('/').Reverse().Take(2).ToArray();
1416

1517
StreamId = pieces.LastOrDefault();
1618

1719
StreamVersion = int.Parse(pieces.First());
1820
}
1921

22+
public PathString Path { get; }
2023
public int StreamVersion { get; }
2124
public string StreamId { get; }
2225

src/SqlStreamStore.HAL/StreamMessage/StreamMessageResource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public async Task<Response> Get(
2828
var message = await operation.Invoke(_streamStore, cancellationToken);
2929

3030
var links = Links
31-
.RootedAt("../../")
31+
.FromPath(operation.Path)
3232
.Index()
3333
.Find()
3434
.StreamMessageNavigation(message, operation);

src/SqlStreamStore.HAL/StreamMetadata/GetStreamMetadataOperation.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ internal class GetStreamMetadataOperation : IStreamStoreOperation<StreamMetadata
99
{
1010
public GetStreamMetadataOperation(HttpRequest request)
1111
{
12+
Path = request.Path;
1213
StreamId = request.Path.Value.Split('/')[2];
1314
}
1415

1516
public string StreamId { get; }
17+
public PathString Path { get; }
1618

1719
public Task<StreamMetadataResult> Invoke(IStreamStore streamStore, CancellationToken ct)
1820
=> streamStore.GetStreamMetadata(StreamId, ct);

src/SqlStreamStore.HAL/StreamMetadata/SetStreamMetadataOperation.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ public static async Task<SetStreamMetadataOperation> Create(HttpRequest request,
2424

2525
private SetStreamMetadataOperation(HttpRequest request, JObject body)
2626
{
27+
Path = request.Path;
2728
StreamId = request.Path.Value.Split('/')[2];
2829
ExpectedVersion = request.GetExpectedVersion();
2930
MaxAge = body.Value<int?>("maxAge");
3031
MaxCount = body.Value<int?>("maxCount");
3132
MetadataJson = Normalize(body["metadataJson"]?.ToString(Formatting.Indented));
3233
}
33-
34+
35+
public PathString Path { get; }
3436
public string StreamId { get; }
3537
public int ExpectedVersion { get; }
3638
public string MetadataJson { get; }

0 commit comments

Comments
 (0)