chore: add joinPath + with methods on Uri extension point (#5360)

* chore: add `joinPath` and `with` on Uri extension point

fixes https://github.com/containers/podman-desktop/issues/5359
Signed-off-by: Florent Benoit <fbenoit@redhat.com>
This commit is contained in:
Florent BENOIT 2023-12-22 14:54:48 +01:00 committed by GitHub
parent 91db07a179
commit 409966dccf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 154 additions and 0 deletions

View file

@ -1220,6 +1220,16 @@ declare module '@podman-desktop/api' {
*/
static file(path: string): Uri;
/**
* Create a new uri which path is the result of joining
* the path of the base uri with the provided path segments.
*
* @param base An uri. Must have a path.
* @param pathSegments One more more path fragments
* @returns A new uri which path is joined with the given fragments
*/
static joinPath(base: Uri, ...pathSegments: string[]): Uri;
/**
* Use the `file` and `parse` factory functions to create new `Uri` objects.
*/
@ -1257,6 +1267,43 @@ declare module '@podman-desktop/api' {
*/
readonly fragment: string;
/**
* Derive a new Uri from this Uri.
*
* ```ts
* const foo = Uri.parse('http://foo');
* const httpsFoo = foo.with({ scheme: 'https' });
* // httpsFoo is now 'https://foo'
* ```
*
* @param change An object that describes a change to this Uri. To unset components use `undefined` or
* the empty string.
* @returns A new Uri that reflects the given change. Will return `this` Uri if the change
* is not changing anything.
*/
with(change: {
/**
* The new scheme, defaults to this Uri's scheme.
*/
scheme?: string;
/**
* The new authority, defaults to this Uri's authority.
*/
authority?: string;
/**
* The new path, defaults to this Uri's path.
*/
path?: string;
/**
* The new query, defaults to this Uri's query.
*/
query?: string;
/**
* The new fragment, defaults to this Uri's fragment.
*/
fragment?: string;
}): Uri;
toString(): string;
}

View file

@ -49,3 +49,49 @@ test('toString', () => {
const uri = Uri.parse('https://podman-desktop.io');
expect(uri.toString()).toBe('https://podman-desktop.io/');
});
test('joinPath without path', () => {
const uri = Uri.parse('https://podman-desktop.io');
// delete path for the error
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (uri as any)._path;
expect(() => Uri.joinPath(uri, 'foo')).toThrowError('cannot call joinPath on Uri without a path');
});
test.each([
['file:///foo/', '../../bar', 'file:///bar'],
['file:///foo', '../../bar', 'file:///bar'],
['file:///foo/bar', './baz', 'file:///foo/bar/baz'],
['http://foo', 'bar', 'http://foo/bar'],
['https://foo', 'bar', 'https://foo/bar'],
])('joinPath %s %s', (left, right, expected) => {
const leftUri = Uri.parse(left);
const joinPathUri = Uri.joinPath(leftUri, right);
expect(joinPathUri.toString()).toBe(expected);
});
test('Uri.with without any change should return same object', () => {
const uri = Uri.parse('https://podman-desktop.io');
const updatedUri = uri.with();
expect(updatedUri).toBe(uri);
});
test('Uri.with and undefined path', () => {
const uri = Uri.parse('https://podman-desktop.io');
const updatedUri = uri.with({ scheme: 'http' });
expect(updatedUri.scheme).toBe('http');
});
test('Uri.with and same change', () => {
const uri = Uri.parse('https://podman-desktop.io');
const updatedUri = uri.with({ scheme: 'https', authority: 'podman-desktop.io', path: '/', query: '', fragment: '' });
expect(updatedUri).toBe(uri);
});

View file

@ -16,6 +16,9 @@
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/
import path, { join } from 'path';
import { isWindows } from '/@/util.js';
/**
* Represents a resource that can be manipulated. The resource is identified by a Uri.
*/
@ -42,6 +45,64 @@ export class Uri {
static file(path: string): Uri {
return new Uri('file', '', path, '', '');
}
/**
* Join a URI path with path fragments and normalizes the resulting path.
*
* @param uri The input URI.
* @param pathFragment The path fragment to add to the URI path.
* @returns The resulting URI.
*/
static joinPath(uri: Uri, ...pathFragment: string[]): Uri {
if (!uri.path) {
throw new Error('cannot call joinPath on Uri without a path');
}
let newPath: string = join(uri.path, ...pathFragment);
if (isWindows()) {
// normalize windows path
newPath = newPath.split(path.sep).join(path.posix.sep);
}
return uri.with({ path: newPath });
}
with(change?: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri {
if (!change) {
return this;
}
let { scheme, authority, path, query, fragment } = change;
if (scheme === undefined) {
scheme = this._scheme;
}
if (authority === undefined) {
authority = this._authority;
}
if (path === undefined) {
path = this._path;
}
if (query === undefined) {
query = this._query;
}
if (fragment === undefined) {
fragment = this._fragment;
}
if (
scheme === this.scheme &&
authority === this.authority &&
path === this.path &&
query === this.query &&
fragment === this.fragment
) {
return this;
}
return new Uri(scheme, authority, path, query, fragment);
}
get fsPath(): string {
return this._path;
}