@@ -553,20 +553,160 @@ public class ProviderConfig
5535533 . Migrate providers one by one
5545544 . Remove old interfaces
555555
556- ### Open Questions
556+ ### Even Simpler: Routing Does All Path Work
557557
558- 1 . ** Caching integration** - Current ` ICacheableBlobPromise ` has cache key computation. Where does this go?
559- - Option A: ` BlobFetchResult ` includes cache metadata
560- - Option B: Separate ` ICacheKeyProvider ` interface
561- - Option C: Provider returns ` IBlobWrapper ` which has cache info
558+ If routing handles path parsing completely, provider just gets final params:
562559
563- 2 . ** Pre-signed URLs** - Current ` ISupportsPreSignedUrls ` . Keep as optional interface?
560+ ``` csharp
561+ // Minimal provider interface
562+ public interface IBlobSource
563+ {
564+ string Name { get ; }
565+
566+ ValueTask <BlobResult > FetchAsync (
567+ IReadOnlyDictionary <string , string > params ,
568+ CancellationToken ct = default );
569+ }
570+
571+ // Result is simple
572+ public readonly struct BlobResult
573+ {
574+ public bool Found { get ; init ; }
575+ public IBlobWrapper ? Blob { get ; init ; }
576+ public int ? ErrorCode { get ; init ; }
577+ public string ? ErrorMessage { get ; init ; }
578+
579+ public static BlobResult Ok (IBlobWrapper blob ) => new () { Found = true , Blob = blob };
580+ public static BlobResult NotFound () => new () { Found = false };
581+ public static BlobResult Error (int code , string msg ) => new () { ErrorCode = code , ErrorMessage = msg };
582+ }
583+ ```
584+
585+ ** What each provider receives:**
586+
587+ | Provider | Params |
588+ | ----------| --------|
589+ | S3 | ` bucket ` , ` key ` |
590+ | Azure | ` container ` , ` blob ` |
591+ | Filesystem | ` path ` |
592+ | HTTP | ` url ` or ` path ` |
593+
594+ ** What provider does NOT need to know:**
595+ - Original request path ❌
596+ - Route that matched ❌
597+ - Query string parsing ❌
598+ - URI construction ❌
599+ - Template evaluation ❌
600+
601+ ** Example S3 implementation:**
602+ ``` csharp
603+ public class S3BlobSource : IBlobSource
604+ {
605+ private readonly IAmazonS3 _client ;
606+ private readonly S3Options _options ;
607+
608+ public string Name { get ; }
609+
610+ public async ValueTask <BlobResult > FetchAsync (
611+ IReadOnlyDictionary <string , string > params ,
612+ CancellationToken ct )
613+ {
614+ var bucket = params [" bucket" ];
615+ var key = params [" key" ];
616+
617+ try
618+ {
619+ var response = await _client .GetObjectAsync (bucket , key , ct );
620+ return BlobResult .Ok (new S3BlobWrapper (response ));
621+ }
622+ catch (AmazonS3Exception ex ) when (ex .StatusCode == HttpStatusCode .NotFound )
623+ {
624+ return BlobResult .NotFound ();
625+ }
626+ }
627+ }
628+ ```
629+
630+ ** Routing layer does the heavy lifting:**
631+ ``` csharp
632+ public class RoutingEngine
633+ {
634+ public async ValueTask <BlobResult ?> RouteAsync (IRequestSnapshot request , CancellationToken ct )
635+ {
636+ foreach (var route in _routes )
637+ {
638+ // 1. Match request path
639+ if (! route .Matcher .TryMatch (request .Path , out var captures ))
640+ continue ;
641+
642+ // 2. Check header conditions
643+ if (! route .HeaderConditions .All (c => c .Matches (request )))
644+ continue ;
645+
646+ // 3. Evaluate template to get provider path
647+ var templateOutput = route .Template .Evaluate (captures );
648+
649+ // 4. Run provider's path parsers to extract params
650+ var params = route .Provider .PathParsers
651+ .Select (p => p .TryParse (templateOutput ))
652+ .FirstOrDefault (r => r != null );
653+
654+ if (params == null )
655+ continue ; // No parser matched
656+
657+ // 5. Merge with any static flags [bucket=default]
658+ params = MergeWithFlags (params , route .Flags );
659+
660+ // 6. Call provider with just the params
661+ var provider = _providers [route .ProviderName ];
662+ return await provider .FetchAsync (params , ct );
663+ }
664+ return null ; // No route matched
665+ }
666+ }
667+ ```
668+
669+ ### Remaining Questions
670+
671+ 1 . ** Caching** - Cache key needs to include provider name + params. Routing layer can compute this.
672+
673+ 2 . ** Pre-signed URLs** - Optional interface on provider:
674+ ``` csharp
675+ public interface ISupportsPreSignedUrls : IBlobSource
676+ {
677+ string ? GeneratePreSignedUrl (IReadOnlyDictionary <string , string > params , TimeSpan expiry );
678+ }
679+ ```
680+
681+ 3 . ** Latency zones** - Provider declares its zone, routing layer tracks:
682+ ``` csharp
683+ public interface IBlobSource
684+ {
685+ string Name { get ; }
686+ LatencyTrackingZone LatencyZone { get ; } // e.g., "s3:us-east-1:my-bucket"
687+ ValueTask <BlobResult > FetchAsync (...);
688+ }
689+ ```
690+
691+ 4 . ** Request context** - If provider needs headers (conditional GET):
692+ ``` csharp
693+ // Option A: Pass minimal context
694+ ValueTask < BlobResult > FetchAsync (
695+ IReadOnlyDictionary < string , string > params ,
696+ BlobFetchHints ? hints , // If-None-Match, Accept-Encoding, etc.
697+ CancellationToken ct );
698+
699+ // Option B: Let routing layer handle conditional logic
700+ // Provider always fetches, routing layer returns 304 if appropriate
701+ ```
564702
565- 3 . ** Latency zones ** - Current ` LatencyTrackingZone ` . Include in result or separate?
703+ ### Interface Evolution Summary
566704
567- 4 . ** Hot-reload** - Provider instances are long-lived. How to update config?
568- - Option A: Recreate provider on config change
569- - Option B: Provider observes ` IOptionsMonitor ` internally
705+ | Version | Interface | Params | Responsibility |
706+ | ---------| -----------| --------| ----------------|
707+ | Legacy | ` IBlobProvider ` | ` virtualPath ` | Provider parses path |
708+ | Current | ` IRoutedBlobProvider ` | 6 params | Provider + routing mixed |
709+ | Proposed | ` IBlobSource ` | ` params ` dict | Routing parses, provider fetches |
570710
571711## Existing API Alignment
572712
0 commit comments