URI Resolution
==============
URI Resolution is the process a WRAP client goes through to resolve a URI to a wrapper.
## Prior Knowledge
Readers are expected to have a prior understanding of the [WRAP URI standard](), and the individual components of a `wrap://` URI string.
## Overview
The resolution process inside of the client happens in the `tryResolveUri` method. The goal of the `tryResolveToWrapper` method is to resolve an origin WRAP URI (as defined in [WRAP URI standard](TODO)) and return a wrapper and its WRAP URI. If the origin WRAP URI cannot be fully resolved the `tryResolveUri` method will return the last redirected URI found. As a byproduct of the resolution process, the URI history is also returned. It contains the history of the resolution process. If an error is found during the resolution process, then it is returned alongside the history.
traceUriResolution
### tryResolveUri method signature:
```typescript=
tryResolveToWrapper(
options: ResolveUriOptions // resolution options
): UriResolutionResult
```
#### ResolveUriOptions
```typescript=
interface ResolveUriOptions {
uri: string; // wrapper's URI
noCacheRead?: boolean; // If true then do not use the cache for resolution
noCacheWrite?: boolean; // If true then do not save resolution to cache
history?: HistoryType
}
```
#### HistoryType
```typescript=
enum HistoryType {
Full,
Path,
None
}
```
#### UriResolutionResult
```typescript=
interface UriResolutionResult extends ResolveUriResult {
history: UriResolutionStep[]; // The resolution history
}
interface ResolveUriResult {
uri: Uri;
wrapper?: Wrapper;
}
```
#### Types of possible errors
```typescript=
interface UriResolutionError {
type: UriResolutionErrorType;
history: UriResolutionStep[];
}
enum ResolveUriErrorType {
InfiniteLoop,
UriResolver,
}
interface InfiniteLoopError extends UriResolutionError {
type: UriResolutionErrorType.InfiniteLoop;
}
interface UriResolverError extends UriResolutionError {
type: UriResolutionErrorType.UriResolver;
resolverName: string;
resolverError: Error;
}
```
#### URI Resolution History
```typescript=
UriResolutionStep[];
```
The URI history is an ordered list of steps which describe the resolution process per resolver. The list is ordered chronologically based on resolvers used.
#### UriResolutionStep
```typescript=
interface UriResolutionStep {
resolver: UriResolver; // The resolver used in this step
sourceUri: string; // The starting WRAP URI of this step
result: TryResolveUriResult; // The result of URI resolver resolution
}
```
## URI Resolver
The responsibility of a URI Resolver is to advance the resolution process.
Based on the WRAP URI provided it can choose to:
- Ignore: The URI resolution process skips the resolver
- Redirect: Redirects the provided URI to another WRAP URI based on internal criteria
- Return a wrapper: If the resolver can find a wrapper that matches the provided URI then it returns that wrapper
```typescript=
interface UriResolver {
name: string;
tryResolveToWrapper(
uri: Uri, // Current WRAP URI to resolve
client: Client, // Instance of the Polywrap Client
cache: WrapperCache, // Wrapper Cache used in the Client
resolutionPath: string[] // The resolution path until this point
): Promise<ResolveUriResult>;
}
```
## Resolution process
Each WRAP client can be configured with a list of URI Resolvers.
Given an origin URI the client iterates through the list of resolvers, giving each resolver full responsibility of advancing the resolution process. The result the URI resolver resolution process is an instance of `UriResolutionResult` as defined here:
```typescript=
interface UriResolutionResult ResolutionResult {
uri: Uri;
wrapper?: Wrapper;
error?: Error;
}
```
Depending on the objects inside of the `UriResolutionResult`, the process can continue in different ways.
1. If the result contains a wrapper, then the process ends and the client returns the wrapper in the `wrapper` property of the `ResolveUriResult` object.
2. If the result URI is the same as the origin URI then the process continues with another resolver from the list.
3. If the result is a URI that is different from the origin URI (redirect) then the process restarts with a new origin URI.
4. If the result is an error, then the process short circuits and the client returns the resolver error in the `error` property of the `ResolveUriResult`.
During the resolution process, the client keeps track of all URI redirects and if at any point a URI is revisited, the process stops and the client return an `InfiniteLoop` error in the `error` property of the `ResolveUriResult`.
## Pseudocode
```typescript=
runAgain = true
while (runAgain) {
runAgain = false
const { infiniteLoopDetected } = trackVisitedUri(currentUri)
for (resolver of uriResolvers) {
if (infiniteLoopDetected) {
return {
uri: currentUri,
wrapper,
uriHistory: options.fullHistory
? uriHistory
: getResolutionPath(uriHistory),
error: {
type: ResolveUriErrorType.InfiniteLoop,
},
};
}
result = resolver.resolveUri(
currentUri,
client,
cache,
getResolutionPath(uriHistory)
)
trackUriHistory(currentUri, resolver, result, uriHistory);
if(result.wrapper) {
wrapper = result.wrapper
break
}
else if(result.uri != currentUri) {
currentUri = result.uri
runAgain = true
break
}
else if(result.error) {
return {
uri: currentUri,
error: new InternalResolverError(
resolver.name, result.error
),
uriHistory: options.fullHistory
? uriHistory
: getResolutionPath(uriHistory)
}
}
}
}
return {
uri: currentUri,
wrapper: wrapper,
uriHistory: options.fullHistory
? uriHistory
: getResolutionPath(uriHistory)
}
```
- getResolutionPath - Gets the list of `UriResolutionStep` that were taken to arrive to the final redirected URI
- trackVisitedUri - Tracks the current URI and checks if it has been visited before
- trackUriHistory - Add the current URI to the URI history
#### Example URI resolver:
If a URI is pointing to an old domain (old-domain.com), this example resolver will redirect it to the new domain (new-domain.com).
```typescript=
class ExampleResolver implements UriResolver {
name: "ExampleResolver"
async resolveUri(uri: string): Promise<UriResolutionResult> {
if (uri.startsWith("wrap://http/old-domain.com")) {
const redirectedUri = "wrap://http/new-domain.com" + getPath(uri)
return Promise.resolve({
uri: redirectedUri,
})
}
else {
return Promise.resolve({
uri,
})
}
}
}
```
### Default URI resolvers
The Polywrap client is by default configured with a set a URI resolvers:
- Redirects resolver
- Cache resolver
- Plugin resolver
- Extendable URI resolver
#### Redirects resolver
This resolver is responsible for handling the redirects defined in the Polywrap client configuration.
#### Cache resolver
This resolver uses the wrapper cache of the Polywrap client to retrieve cached wrappers.
#### Plugin resolver
This resolver handles plugins registered in the Polywrap client configuration.
#### Extendable URI resolver
Extendable URI resolver is used to dynamically extend the Polywrap client resolution process. It fetches all wrapper implementations, defined in the client configuration, of the core URI Resolver Extension WRAP interface and uses them to try to resolve the provided URI.
---
# Standard Process
client = new PolywrapClient({
resolvers: [
new CacheResolver()
new PluginResolver()
new ExtendableResolver()
]
})
------------------
interface ExtendableExtensionsResolver extends BaseExtendableResolver
override getResolvers(client) {
return client.getImplementations().map(x => new UriResolverWrapper(x));
}
client = new PolywrapClient({
resolver: new SimpleExtendableResolver([
new CacheResolver()
new PluginResolver()
new ExtendableExtensionsResolver()
])
})
abstract BaseExtendableResolver implements IExtendableResolver
abstract getResolvers(): UriResolver[];
tryResolveToWrapper() {
...
}
SimpleExtendableResolver extends BaseExtendableResolver
contructror(resolvers: UriResolver[])
override getResolvers(): UriResolver[] {
return this.resolvers;
}
interface IExtendableResolver
getResolvers(): UriResolver[];
tryResolveToWrapper()
client.tryResolveToWrapper
const resolver = this.getResolver();
const result = await resolver.tryResolveToWrapper()
- redirect
- cache
- plugin
- extensions
- ens
- ipfs
- fs
:::info
## Initial Feedback (2022-5-20)
- [x] what if we use psuedo code instead of some concrete code here to not focus on one language but the interface and procedure of it.
- [x] We can find different styles of psuedo-code from the wikipedia here: https://en.wikipedia.org/wiki/Pseudocode
- [x] add resolver URI options
- [x] Can we also have code snippet about how history looks like?
:::
:::warning
1. Implement code changes coming out of Standard Workshop discussion (`@jure`)
2. Schedule a Standard Workshop on July 22, 2022 (scheduled) -- estimated to be a final workshop
3. Schedule a Standard Workshop on August 5, 2022 (scheduled) -- need additional time for development
### Next steps
- [x] method renaming
- implement new architecture
- describe how tryResolveToWrapper uses traceUriResolution
:::