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
16 changes: 16 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "slopwatch analyze -d $(git rev-parse --show-toplevel) --hook",
"timeout": 60000
}
]
}
]
}
}
11 changes: 11 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 1,
"isRoot": true,
"tools": {
"slopwatch.cmd": {
"version": "0.2.0",
"commands": ["slopwatch"],
"rollForward": false
}
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,5 @@ TurboHTTP/.obsidian/
.worktrees/
.maggus/logs/
.maggus/worktrees/

*.ps1
575 changes: 575 additions & 0 deletions .slopwatch/baseline.json

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions .slopwatch/slopwatch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"minSeverity": "warning",
"rules": {
"SW001": { "enabled": true, "severity": "error" },
"SW002": { "enabled": true, "severity": "error" },
"SW003": { "enabled": true, "severity": "error" },
"SW004": { "enabled": true, "severity": "error" },
"SW005": { "enabled": true, "severity": "error" },
"SW006": { "enabled": true, "severity": "error" }
},
"exclude": [
"**/Generated/**",
"**/obj/**",
"**/bin/**"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ namespace TurboHTTP
public static TurboHTTP.ITurboHttpClientBuilder UseResponse(this TurboHTTP.ITurboHttpClientBuilder builder, System.Func<System.Net.Http.HttpRequestMessage, System.Net.Http.HttpResponseMessage, System.Net.Http.HttpResponseMessage> transform) { }
public static TurboHTTP.ITurboHttpClientBuilder WithCache(this TurboHTTP.ITurboHttpClientBuilder builder, System.Action<TurboHTTP.CacheOptions>? configure = null) { }
public static TurboHTTP.ITurboHttpClientBuilder WithCache(this TurboHTTP.ITurboHttpClientBuilder builder, TurboHTTP.Protocol.Caching.ICacheStore store, System.Action<TurboHTTP.CacheOptions>? configure = null) { }
public static TurboHTTP.ITurboHttpClientBuilder WithCookies(this TurboHTTP.ITurboHttpClientBuilder builder, TurboHTTP.Protocol.Cookies.ICookieJar? jar = null) { }
public static TurboHTTP.ITurboHttpClientBuilder WithCookies(this TurboHTTP.ITurboHttpClientBuilder builder) { }
public static TurboHTTP.ITurboHttpClientBuilder WithCookies(this TurboHTTP.ITurboHttpClientBuilder builder, TurboHTTP.Protocol.Cookies.ICookieStore store) { }
public static TurboHTTP.ITurboHttpClientBuilder WithDecompression(this TurboHTTP.ITurboHttpClientBuilder builder, bool enabled = true) { }
public static TurboHTTP.ITurboHttpClientBuilder WithExpectContinue(this TurboHTTP.ITurboHttpClientBuilder builder, System.Action<TurboHTTP.Expect100Options>? configure = null) { }
public static TurboHTTP.ITurboHttpClientBuilder WithRedirect(this TurboHTTP.ITurboHttpClientBuilder builder, System.Action<TurboHTTP.RedirectOptions>? configure = null) { }
Expand Down Expand Up @@ -237,6 +238,14 @@ namespace TurboHTTP.Diagnostics
}
namespace TurboHTTP.Protocol.Caching
{
public sealed class CacheBody : System.IDisposable
{
public bool IsEmpty { get; }
public int Length { get; }
public System.ReadOnlyMemory<byte> Memory { get; }
public System.ReadOnlySpan<byte> Span { get; }
public void Dispose() { }
}
public sealed class CacheControl : System.IEquatable<TurboHTTP.Protocol.Caching.CacheControl>
{
public CacheControl() { }
Expand All @@ -257,6 +266,43 @@ namespace TurboHTTP.Protocol.Caching
public bool Public { get; init; }
public System.TimeSpan? SMaxAge { get; init; }
}
public sealed class CacheControlStoreEntry : System.IEquatable<TurboHTTP.Protocol.Caching.CacheControlStoreEntry>
{
public CacheControlStoreEntry() { }
public bool Immutable { get; init; }
public System.TimeSpan? MaxAge { get; init; }
public System.TimeSpan? MaxStale { get; init; }
public System.TimeSpan? MinFresh { get; init; }
public bool MustRevalidate { get; init; }
public bool MustUnderstand { get; init; }
public bool NoCache { get; init; }
public string[] NoCacheFields { get; init; }
public bool NoStore { get; init; }
public bool NoTransform { get; init; }
public bool OnlyIfCached { get; init; }
public bool Private { get; init; }
public string[] PrivateFields { get; init; }
public bool ProxyRevalidate { get; init; }
public bool Public { get; init; }
public System.TimeSpan? SMaxAge { get; init; }
}
public sealed class CacheStoreEntry : System.IDisposable
{
public CacheStoreEntry() { }
public int? AgeSeconds { get; init; }
public required TurboHTTP.Protocol.Caching.CacheBody Body { get; init; }
public TurboHTTP.Protocol.Caching.CacheControlStoreEntry? CacheControl { get; init; }
public System.DateTimeOffset? Date { get; init; }
public string? ETag { get; init; }
public System.DateTimeOffset? Expires { get; init; }
public System.DateTimeOffset? LastModified { get; init; }
public required System.DateTimeOffset RequestTime { get; init; }
public required System.Net.Http.HttpResponseMessage Response { get; init; }
public required System.DateTimeOffset ResponseTime { get; init; }
public string[] VaryHeaderNames { get; init; }
public System.Collections.Generic.Dictionary<string, string?> VaryRequestValues { get; init; }
public void Dispose() { }
}
public interface ICacheEntry : System.IDisposable
{
int? AgeSeconds { get; }
Expand All @@ -272,18 +318,43 @@ namespace TurboHTTP.Protocol.Caching
System.Collections.Generic.IReadOnlyList<string> VaryHeaderNames { get; }
System.Collections.Generic.IReadOnlyDictionary<string, string?> VaryRequestValues { get; }
}
public interface ICacheStore
public interface ICacheStore : System.IDisposable
{
TurboHTTP.Protocol.Caching.ICacheEntry? Get(System.Net.Http.HttpRequestMessage request);
void Invalidate(System.Uri uri);
void Put(System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpResponseMessage response, System.Buffers.IMemoryOwner<byte> bodyOwner, int bodyLength, System.DateTimeOffset requestTime, System.DateTimeOffset responseTime);
void Clear();
bool Remove(string key);
void Set(string key, TurboHTTP.Protocol.Caching.CacheStoreEntry entry);
bool TryGet(string key, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out TurboHTTP.Protocol.Caching.CacheStoreEntry? entry);
}
}
namespace TurboHTTP.Protocol.Cookies
{
public interface ICookieJar
public sealed class CookieStoreEntry : System.IEquatable<TurboHTTP.Protocol.Cookies.CookieStoreEntry>
{
public CookieStoreEntry(string Name, string Value, string Domain, string Path, System.DateTimeOffset? ExpiresAt, bool Secure, bool HttpOnly, TurboHTTP.Protocol.Cookies.SameSitePolicy SameSite, bool IsHostOnly, System.DateTimeOffset CreatedAt) { }
public System.DateTimeOffset CreatedAt { get; init; }
public string Domain { get; init; }
public System.DateTimeOffset? ExpiresAt { get; init; }
public bool HttpOnly { get; init; }
public bool IsHostOnly { get; init; }
public string Name { get; init; }
public string Path { get; init; }
public TurboHTTP.Protocol.Cookies.SameSitePolicy SameSite { get; init; }
public bool Secure { get; init; }
public string Value { get; init; }
}
public interface ICookieStore
{
int Count { get; }
void Add(TurboHTTP.Protocol.Cookies.CookieStoreEntry entry);
void Clear();
System.Collections.Generic.IReadOnlyList<TurboHTTP.Protocol.Cookies.CookieStoreEntry> GetAll();
void Remove(string name, string domain, string path);
}
public enum SameSitePolicy
{
void AddCookiesToRequest(System.Uri requestUri, ref System.Net.Http.HttpRequestMessage request);
void ProcessResponse(System.Uri requestUri, System.Net.Http.HttpResponseMessage response);
Unspecified = 0,
Strict = 1,
Lax = 2,
None = 3,
}
}
24 changes: 12 additions & 12 deletions src/TurboHTTP.AcceptanceTests/H10/CacheSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace TurboHTTP.AcceptanceTests.H10;
public sealed class CacheSpec : AcceptanceTestBase
{
private async Task<HttpResponseMessage> SendAsync(ResponseMap map, HttpRequestMessage request,
CacheStore store, CachePolicy? policy = null)
Cache store, CachePolicy? policy = null)
{
var cache = BidiFlow.FromGraph(new CacheBidiStage(store, policy));
var fake = ResponseMapFake.Create(map);
Expand Down Expand Up @@ -69,7 +69,7 @@ public async Task Cache_should_serve_max_age_response_from_cache()
return CacheableResponse($"max-age-body-{callCount}", "max-age=3600");
});

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/max-age/3600"), store);
Expand Down Expand Up @@ -97,7 +97,7 @@ public async Task Cache_should_force_revalidation_with_no_cache()
return CacheableResponse($"no-cache-body-{callCount}", "no-cache");
});

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/no-cache"), store);
Expand All @@ -119,7 +119,7 @@ public async Task Cache_should_never_cache_no_store_response()
var map = new ResponseMap()
.On("/cache/no-store", _ => CacheableResponse("no-store-resource", "no-store"));

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/no-store"), store);
Expand All @@ -143,7 +143,7 @@ public async Task Cache_should_send_if_none_match_for_etag_revalidation()
.On("/cache/etag/test1", _ => CacheableResponse("etag-resource-test1", "max-age=3600",
etag: "etag-test1"));

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/etag/test1"), store);
Expand All @@ -167,7 +167,7 @@ public async Task Cache_should_send_if_modified_since_for_last_modified_revalida
.On("/cache/last-modified/doc1", _ => CacheableResponse("last-modified-resource-doc1",
"max-age=3600", lastModified: DateTimeOffset.UtcNow.AddHours(-1)));

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/last-modified/doc1"), store);
Expand Down Expand Up @@ -195,7 +195,7 @@ public async Task Cache_should_produce_different_entries_for_vary_header_values(
vary: "Accept-Language");
});

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var request1 = new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/vary/Accept-Language");
request1.Headers.TryAddWithoutValidation("Accept-Language", "en");
Expand Down Expand Up @@ -249,7 +249,7 @@ public async Task Cache_should_force_revalidation_when_must_revalidate()
etag: "mr-etag-2");
});

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/must-revalidate"), store);
Expand Down Expand Up @@ -277,7 +277,7 @@ public async Task Cache_should_respect_s_maxage_by_shared_cache()
});

var policy = new CachePolicy { SharedCache = true };
var store = new CacheStore(policy);
var store = new Cache(policy);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/s-maxage/3600"), store, policy);
Expand Down Expand Up @@ -305,7 +305,7 @@ public async Task Cache_should_enable_caching_with_expires_header()
expires: DateTimeOffset.UtcNow.AddHours(1));
});

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/expires"), store);
Expand All @@ -332,7 +332,7 @@ public async Task Cache_should_cache_private_response_by_private_cache()
return CacheableResponse($"private-body-{callCount}", "private, max-age=3600");
});

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/private"), store);
Expand Down Expand Up @@ -360,7 +360,7 @@ public async Task Cache_must_not_cache_private_response_by_shared_cache()
});

var policy = new CachePolicy { SharedCache = true };
var store = new CacheStore(policy);
var store = new Cache(policy);

var response1 = await SendAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/private"), store, policy);
Expand Down
12 changes: 6 additions & 6 deletions src/TurboHTTP.AcceptanceTests/H10/FeatureInteractionSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private async Task<HttpResponseMessage> SendCookieRedirectAsync(ResponseMap map,
}

private async Task<HttpResponseMessage> SendCacheAsync(ResponseMap map, HttpRequestMessage request,
CacheStore store, CachePolicy? policy = null)
Cache store, CachePolicy? policy = null)
{
var cache = BidiFlow.FromGraph(new CacheBidiStage(store, policy));
var fake = ResponseMapFake.Create(map);
Expand Down Expand Up @@ -103,7 +103,7 @@ private async Task<HttpResponseMessage> SendCookieRetryAsync(ResponseMap map, Ht
}

private async Task<HttpResponseMessage> SendCacheCookieAsync(ResponseMap map, HttpRequestMessage request,
CacheStore store, CookieJar jar)
Cache store, CookieJar jar)
{
var cache = BidiFlow.FromGraph(new CacheBidiStage(store));
var cookie = BidiFlow.FromGraph(new CookieBidiStage(jar));
Expand All @@ -120,7 +120,7 @@ private async Task<HttpResponseMessage> SendCacheCookieAsync(ResponseMap map, Ht
}

private async Task<HttpResponseMessage> SendCacheRetryAsync(ResponseMap map, HttpRequestMessage request,
CacheStore store, RetryPolicy retryPolicy)
Cache store, RetryPolicy retryPolicy)
{
var cache = BidiFlow.FromGraph(new CacheBidiStage(store));
var retry = BidiFlow.FromGraph(new RetryBidiStage(retryPolicy));
Expand Down Expand Up @@ -178,7 +178,7 @@ public async Task FeatureInteraction_should_serve_compressed_response_from_cache
return r;
});

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var res1 = await SendCacheAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/interaction/cache-gzip"), store);
Expand Down Expand Up @@ -294,7 +294,7 @@ public async Task FeatureInteraction_should_separate_cache_entries_with_vary_hea
return r;
});

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var req1 = new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/vary/Accept-Language");
req1.Headers.Add("Accept-Language", "en");
Expand Down Expand Up @@ -364,7 +364,7 @@ public async Task FeatureInteraction_should_bypass_retry_logic_on_cache_hit()
return r;
});

var store = new CacheStore(CachePolicy.Default);
var store = new Cache(CachePolicy.Default);

var res1 = await SendCacheRetryAsync(map,
new HttpRequestMessage(HttpMethod.Get, "http://localhost/cache/max-age/3600"),
Expand Down
Loading
Loading