mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
chore: Refactor legacy code to use pattern matching
Also add missing unit tests to cover more. Co-authored-by: Janne Savolainen <janne.savolainen@live.fi> Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
parent
bba0759d27
commit
befbe62e63
@ -21,57 +21,9 @@ import { parseKubeApi } from "../kube-api-parse";
|
|||||||
type KubeApiParseTestData = [string, IKubeApiParsed];
|
type KubeApiParseTestData = [string, IKubeApiParsed];
|
||||||
|
|
||||||
const tests: KubeApiParseTestData[] = [
|
const tests: KubeApiParseTestData[] = [
|
||||||
["/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com", {
|
[
|
||||||
apiBase: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions",
|
"http://some-irrelevant-domain/api/v1/secrets?some-irrelevant-parameter=some-irrelevant-value",
|
||||||
apiPrefix: "/apis",
|
{
|
||||||
apiGroup: "apiextensions.k8s.io",
|
|
||||||
apiVersion: "v1beta1",
|
|
||||||
apiVersionWithGroup: "apiextensions.k8s.io/v1beta1",
|
|
||||||
namespace: undefined,
|
|
||||||
resource: "customresourcedefinitions",
|
|
||||||
name: "prometheuses.monitoring.coreos.com",
|
|
||||||
}],
|
|
||||||
["/api/v1/namespaces/kube-system/pods/coredns-6955765f44-v8p27", {
|
|
||||||
apiBase: "/api/v1/pods",
|
|
||||||
apiPrefix: "/api",
|
|
||||||
apiGroup: "",
|
|
||||||
apiVersion: "v1",
|
|
||||||
apiVersionWithGroup: "v1",
|
|
||||||
namespace: "kube-system",
|
|
||||||
resource: "pods",
|
|
||||||
name: "coredns-6955765f44-v8p27",
|
|
||||||
}],
|
|
||||||
["/apis/stable.example.com/foo1/crontabs", {
|
|
||||||
apiBase: "/apis/stable.example.com/foo1/crontabs",
|
|
||||||
apiPrefix: "/apis",
|
|
||||||
apiGroup: "stable.example.com",
|
|
||||||
apiVersion: "foo1",
|
|
||||||
apiVersionWithGroup: "stable.example.com/foo1",
|
|
||||||
resource: "crontabs",
|
|
||||||
name: undefined,
|
|
||||||
namespace: undefined,
|
|
||||||
}],
|
|
||||||
["/apis/cluster.k8s.io/v1alpha1/clusters", {
|
|
||||||
apiBase: "/apis/cluster.k8s.io/v1alpha1/clusters",
|
|
||||||
apiPrefix: "/apis",
|
|
||||||
apiGroup: "cluster.k8s.io",
|
|
||||||
apiVersion: "v1alpha1",
|
|
||||||
apiVersionWithGroup: "cluster.k8s.io/v1alpha1",
|
|
||||||
resource: "clusters",
|
|
||||||
name: undefined,
|
|
||||||
namespace: undefined,
|
|
||||||
}],
|
|
||||||
["/api/v1/namespaces", {
|
|
||||||
apiBase: "/api/v1/namespaces",
|
|
||||||
apiPrefix: "/api",
|
|
||||||
apiGroup: "",
|
|
||||||
apiVersion: "v1",
|
|
||||||
apiVersionWithGroup: "v1",
|
|
||||||
resource: "namespaces",
|
|
||||||
name: undefined,
|
|
||||||
namespace: undefined,
|
|
||||||
}],
|
|
||||||
["/api/v1/secrets", {
|
|
||||||
apiBase: "/api/v1/secrets",
|
apiBase: "/api/v1/secrets",
|
||||||
apiPrefix: "/api",
|
apiPrefix: "/api",
|
||||||
apiGroup: "",
|
apiGroup: "",
|
||||||
@ -80,8 +32,39 @@ const tests: KubeApiParseTestData[] = [
|
|||||||
resource: "secrets",
|
resource: "secrets",
|
||||||
name: undefined,
|
name: undefined,
|
||||||
namespace: undefined,
|
namespace: undefined,
|
||||||
}],
|
},
|
||||||
["/api/v1/nodes/minikube", {
|
],
|
||||||
|
[
|
||||||
|
"/api/v1/secrets",
|
||||||
|
{
|
||||||
|
apiBase: "/api/v1/secrets",
|
||||||
|
apiPrefix: "/api",
|
||||||
|
apiGroup: "",
|
||||||
|
apiVersion: "v1",
|
||||||
|
apiVersionWithGroup: "v1",
|
||||||
|
resource: "secrets",
|
||||||
|
name: undefined,
|
||||||
|
namespace: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/api/v1/namespaces",
|
||||||
|
{
|
||||||
|
apiBase: "/api/v1/namespaces",
|
||||||
|
apiPrefix: "/api",
|
||||||
|
apiGroup: "",
|
||||||
|
apiVersion: "v1",
|
||||||
|
apiVersionWithGroup: "v1",
|
||||||
|
resource: "namespaces",
|
||||||
|
name: undefined,
|
||||||
|
namespace: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/api/v1/nodes/minikube",
|
||||||
|
{
|
||||||
apiBase: "/api/v1/nodes",
|
apiBase: "/api/v1/nodes",
|
||||||
apiPrefix: "/api",
|
apiPrefix: "/api",
|
||||||
apiGroup: "",
|
apiGroup: "",
|
||||||
@ -90,8 +73,12 @@ const tests: KubeApiParseTestData[] = [
|
|||||||
resource: "nodes",
|
resource: "nodes",
|
||||||
name: "minikube",
|
name: "minikube",
|
||||||
namespace: undefined,
|
namespace: undefined,
|
||||||
}],
|
},
|
||||||
["/api/foo-bar/nodes/minikube", {
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/api/foo-bar/nodes/minikube",
|
||||||
|
{
|
||||||
apiBase: "/api/foo-bar/nodes",
|
apiBase: "/api/foo-bar/nodes",
|
||||||
apiPrefix: "/api",
|
apiPrefix: "/api",
|
||||||
apiGroup: "",
|
apiGroup: "",
|
||||||
@ -100,8 +87,12 @@ const tests: KubeApiParseTestData[] = [
|
|||||||
resource: "nodes",
|
resource: "nodes",
|
||||||
name: "minikube",
|
name: "minikube",
|
||||||
namespace: undefined,
|
namespace: undefined,
|
||||||
}],
|
},
|
||||||
["/api/v1/namespaces/kube-public", {
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/api/v1/namespaces/kube-public",
|
||||||
|
{
|
||||||
apiBase: "/api/v1/namespaces",
|
apiBase: "/api/v1/namespaces",
|
||||||
apiPrefix: "/api",
|
apiPrefix: "/api",
|
||||||
apiGroup: "",
|
apiGroup: "",
|
||||||
@ -110,7 +101,50 @@ const tests: KubeApiParseTestData[] = [
|
|||||||
resource: "namespaces",
|
resource: "namespaces",
|
||||||
name: "kube-public",
|
name: "kube-public",
|
||||||
namespace: undefined,
|
namespace: undefined,
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/apis/stable.example.com/foo1/crontabs",
|
||||||
|
{
|
||||||
|
apiBase: "/apis/stable.example.com/foo1/crontabs",
|
||||||
|
apiPrefix: "/apis",
|
||||||
|
apiGroup: "stable.example.com",
|
||||||
|
apiVersion: "foo1",
|
||||||
|
apiVersionWithGroup: "stable.example.com/foo1",
|
||||||
|
resource: "crontabs",
|
||||||
|
name: undefined,
|
||||||
|
namespace: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/apis/cluster.k8s.io/v1alpha1/clusters",
|
||||||
|
{
|
||||||
|
apiBase: "/apis/cluster.k8s.io/v1alpha1/clusters",
|
||||||
|
apiPrefix: "/apis",
|
||||||
|
apiGroup: "cluster.k8s.io",
|
||||||
|
apiVersion: "v1alpha1",
|
||||||
|
apiVersionWithGroup: "cluster.k8s.io/v1alpha1",
|
||||||
|
resource: "clusters",
|
||||||
|
name: undefined,
|
||||||
|
namespace: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/api/v1/namespaces/kube-system/pods/coredns-6955765f44-v8p27",
|
||||||
|
{
|
||||||
|
apiBase: "/api/v1/pods",
|
||||||
|
apiPrefix: "/api",
|
||||||
|
apiGroup: "",
|
||||||
|
apiVersion: "v1",
|
||||||
|
apiVersionWithGroup: "v1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resource: "pods",
|
||||||
|
name: "coredns-6955765f44-v8p27",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
"/apis/apps/v1/namespaces/default/deployments/some-deployment",
|
"/apis/apps/v1/namespaces/default/deployments/some-deployment",
|
||||||
@ -125,20 +159,73 @@ const tests: KubeApiParseTestData[] = [
|
|||||||
resource: "deployments",
|
resource: "deployments",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||||
|
{
|
||||||
|
apiBase: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions",
|
||||||
|
apiPrefix: "/apis",
|
||||||
|
apiGroup: "apiextensions.k8s.io",
|
||||||
|
apiVersion: "v1beta1",
|
||||||
|
apiVersionWithGroup: "apiextensions.k8s.io/v1beta1",
|
||||||
|
namespace: undefined,
|
||||||
|
resource: "customresourcedefinitions",
|
||||||
|
name: "prometheuses.monitoring.coreos.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/api/v1/namespaces/kube-system/pods",
|
||||||
|
{
|
||||||
|
apiBase: "/api/v1/pods",
|
||||||
|
apiPrefix: "/api",
|
||||||
|
apiGroup: "",
|
||||||
|
apiVersion: "v1",
|
||||||
|
apiVersionWithGroup: "v1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resource: "pods",
|
||||||
|
name: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
[
|
||||||
|
"/apis/cluster.k8s.io/v1/namespaces/kube-system/pods",
|
||||||
|
{
|
||||||
|
apiBase: "/apis/cluster.k8s.io/v1/pods",
|
||||||
|
apiPrefix: "/apis",
|
||||||
|
apiGroup: "cluster.k8s.io",
|
||||||
|
apiVersion: "v1",
|
||||||
|
apiVersionWithGroup: "cluster.k8s.io/v1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resource: "pods",
|
||||||
|
name: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
const invalidTests = [
|
const invalidTests = [
|
||||||
undefined,
|
undefined,
|
||||||
"",
|
"",
|
||||||
"ajklsmh",
|
"some-invalid-path",
|
||||||
|
"//apiextensions.k8s.io/v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||||
|
"/apis//v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||||
|
"/apis/apiextensions.k8s.io//customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||||
|
"/apis/apiextensions.k8s.io/v1beta1//prometheuses.monitoring.coreos.com",
|
||||||
|
"/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/",
|
||||||
|
|
||||||
|
"//v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||||
|
"/api//v1beta1/customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||||
|
"/api//customresourcedefinitions/prometheuses.monitoring.coreos.com",
|
||||||
|
"/api/v1beta1//prometheuses.monitoring.coreos.com",
|
||||||
|
"/api/v1beta1/customresourcedefinitions/",
|
||||||
];
|
];
|
||||||
|
|
||||||
describe("parseApi unit tests", () => {
|
describe("parseApi unit tests", () => {
|
||||||
it.each(tests)("testing %j", (url, expected) => {
|
it.each(tests)(`given path %j, parses as expected`, (url, expected) => {
|
||||||
expect(parseKubeApi(url)).toStrictEqual(expected);
|
expect(parseKubeApi(url)).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each(invalidTests)("testing %j should throw", (url) => {
|
it.each(invalidTests)(`given path %j, parses as undefined`, (url) => {
|
||||||
expect(parseKubeApi(url as never)).toBe(undefined);
|
expect(parseKubeApi(url as never)).toBe(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,9 +3,12 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Parse kube-api path and get api-version, group, etc.
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
import { compact, join } from "lodash/fp";
|
||||||
|
import { getMatchFor } from "./get-match-for";
|
||||||
|
import { prepend } from "./prepend";
|
||||||
|
|
||||||
import { array } from "@k8slens/utilities";
|
// Parse kube-api path and get api-version, group, etc.
|
||||||
|
|
||||||
export interface IKubeApiLinkRef {
|
export interface IKubeApiLinkRef {
|
||||||
apiPrefix?: string;
|
apiPrefix?: string;
|
||||||
@ -22,92 +25,46 @@ export interface IKubeApiParsed extends IKubeApiLinkRef {
|
|||||||
apiVersionWithGroup: string;
|
apiVersionWithGroup: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseKubeApi(path: string): IKubeApiParsed | undefined {
|
export function parseKubeApi(
|
||||||
const apiPath = new URL(path, "https://localhost").pathname;
|
path: string | undefined,
|
||||||
const [, prefix, ...parts] = apiPath.split("/");
|
): IKubeApiParsed | undefined {
|
||||||
const apiPrefix = `/${prefix}`;
|
if (!path) {
|
||||||
const [left, right, namespaced] = array.split(parts, "namespaces");
|
|
||||||
let apiGroup: string;
|
|
||||||
let apiVersion: string | undefined;
|
|
||||||
let namespace: string | undefined;
|
|
||||||
let resource: string;
|
|
||||||
let name: string | undefined;
|
|
||||||
|
|
||||||
if (namespaced) {
|
|
||||||
switch (right.length) {
|
|
||||||
case 1:
|
|
||||||
name = right[0];
|
|
||||||
// fallthrough
|
|
||||||
case 0:
|
|
||||||
resource = "namespaces"; // special case this due to `split` removing namespaces
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
[namespace, resource, name] = right;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
apiVersion = left.at(-1)!;
|
|
||||||
const rest = left.slice(0, -1);
|
|
||||||
|
|
||||||
apiGroup = rest.join("/");
|
|
||||||
} else {
|
|
||||||
if (left.length === 0) {
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (left.length === 1 || left.length === 2) {
|
const parsedPath = getParsedPath(path);
|
||||||
[apiVersion, resource] = left;
|
|
||||||
apiGroup = "";
|
if (!parsedPath) {
|
||||||
} else if (left.length === 4) {
|
return undefined;
|
||||||
[apiGroup, apiVersion, resource, name] = left;
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* Given that
|
|
||||||
* - `apiVersion` is `GROUP/VERSION` and
|
|
||||||
* - `VERSION` is `DNS_LABEL` which is /^[a-z0-9]((-[a-z0-9])|[a-z0-9])*$/i
|
|
||||||
* where length <= 63
|
|
||||||
* - `GROUP` is /^D(\.D)*$/ where D is `DNS_LABEL` and length <= 253
|
|
||||||
*
|
|
||||||
* There is no well defined selection from an array of items that were
|
|
||||||
* separated by '/'
|
|
||||||
*
|
|
||||||
* Solution is to create a heuristic. Namely:
|
|
||||||
* 1. if '.' in left[0] then apiGroup <- left[0]
|
|
||||||
* 2. if left[1] matches /^v[0-9]/ then apiGroup, apiVersion <- left[0], left[1]
|
|
||||||
* 3. otherwise assume apiVersion <- left[0]
|
|
||||||
* 4. always resource, name <- left[(0 or 1)+1..]
|
|
||||||
*/
|
|
||||||
if (left[0].includes(".") || left[1].match(/^v[0-9]/)) {
|
|
||||||
[apiGroup, apiVersion] = left;
|
|
||||||
resource = left.slice(2).join("/");
|
|
||||||
} else {
|
|
||||||
apiGroup = "";
|
|
||||||
apiVersion = left[0];
|
|
||||||
[resource, name] = left.slice(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiVersionWithGroup = [apiGroup, apiVersion].filter(v => v).join("/");
|
const { apiOrApis, apiGroup, namespace, apiVersion, resource, name } =
|
||||||
const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/");
|
parsedPath;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
apiBase,
|
apiBase: getApiBase(apiOrApis, apiGroup, apiVersion, resource),
|
||||||
apiPrefix, apiGroup,
|
apiPrefix: getApiPrefix(apiOrApis),
|
||||||
apiVersion, apiVersionWithGroup,
|
apiGroup: getApiGroup(apiGroup),
|
||||||
namespace, resource, name,
|
apiVersion,
|
||||||
|
apiVersionWithGroup: getApiVersionWithGroup(apiGroup, apiVersion),
|
||||||
|
namespace,
|
||||||
|
resource,
|
||||||
|
name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIKubeApiParsed(refOrParsed: IKubeApiLinkRef | IKubeApiParsed): refOrParsed is IKubeApiParsed {
|
function isIKubeApiParsed(
|
||||||
|
refOrParsed: IKubeApiLinkRef | IKubeApiParsed,
|
||||||
|
): refOrParsed is IKubeApiParsed {
|
||||||
return "apiGroup" in refOrParsed && !!refOrParsed.apiGroup;
|
return "apiGroup" in refOrParsed && !!refOrParsed.apiGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createKubeApiURL(linkRef: IKubeApiLinkRef): string;
|
export function createKubeApiURL(linkRef: IKubeApiLinkRef): string;
|
||||||
export function createKubeApiURL(linkParsed: IKubeApiParsed): string;
|
export function createKubeApiURL(linkParsed: IKubeApiParsed): string;
|
||||||
|
|
||||||
export function createKubeApiURL(ref: IKubeApiLinkRef | IKubeApiParsed): string {
|
export function createKubeApiURL(
|
||||||
|
ref: IKubeApiLinkRef | IKubeApiParsed,
|
||||||
|
): string {
|
||||||
if (isIKubeApiParsed(ref)) {
|
if (isIKubeApiParsed(ref)) {
|
||||||
return createKubeApiURL({
|
return createKubeApiURL({
|
||||||
apiPrefix: ref.apiPrefix,
|
apiPrefix: ref.apiPrefix,
|
||||||
@ -133,3 +90,42 @@ export function createKubeApiURL(ref: IKubeApiLinkRef | IKubeApiParsed): string
|
|||||||
|
|
||||||
return parts.join("/");
|
return parts.join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getKubeApiPathMatch = getMatchFor(
|
||||||
|
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||||
|
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||||
|
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||||
|
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||||
|
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/namespaces\/(?<namespace>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||||
|
/^\/(?<apiOrApis>apis)\/(?<apiGroup>[^/]+?)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||||
|
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)\/(?<name>[^/]+?)$/,
|
||||||
|
/^\/(?<apiOrApis>api)\/(?<apiVersion>[^/]+?)\/(?<resource>[^/]+?)$/,
|
||||||
|
);
|
||||||
|
|
||||||
|
const getParsedPath = (path: string) =>
|
||||||
|
pipeline(path, withoutDomainAddressOrParameters, getKubeApiPathMatch, (match) => match?.groups);
|
||||||
|
|
||||||
|
const joinTruthy = (delimiter: string) => (toBeJoined: string[]) =>
|
||||||
|
pipeline(toBeJoined, compact, join(delimiter));
|
||||||
|
|
||||||
|
const getApiBase = (
|
||||||
|
apiOrApis: string,
|
||||||
|
apiGroup: string,
|
||||||
|
apiVersion: string,
|
||||||
|
resource: string,
|
||||||
|
) =>
|
||||||
|
pipeline(
|
||||||
|
[apiOrApis, apiGroup, apiVersion, resource],
|
||||||
|
joinTruthy("/"),
|
||||||
|
prepend("/"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const getApiPrefix = prepend("/");
|
||||||
|
|
||||||
|
const getApiVersionWithGroup = (apiGroup: string, apiVersion: string) =>
|
||||||
|
joinTruthy("/")([apiGroup, apiVersion]);
|
||||||
|
|
||||||
|
const getApiGroup = (apiGroup: string) => apiGroup || "";
|
||||||
|
|
||||||
|
const withoutDomainAddressOrParameters = (path: string) =>
|
||||||
|
new URL(path, "http://irrelevant").pathname;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user