chore: Change parseKubeApi into a no-throw version

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton committed 2023-04-24 10:17:24 -04:00
1 parent 8dce32153f
commit 2f21ba51d1
10 files changed
+113 -80

No files matched your search

@@ -114,7 +114,7 @@ const tests: KubeApiParseTestData[] = [
}],
];
const throwtests = [
const invalidTests = [
undefined,
"",
"ajklsmh",
@@ -125,7 +125,7 @@ describe("parseApi unit tests", () => {
expect(parseKubeApi(url)).toStrictEqual(expected);
});
it.each(throwtests)("testing %j should throw", (url) => {
expect(() => parseKubeApi(url as never)).toThrowError("invalid apiPath");
it.each(invalidTests)("testing %j should throw", (url) => {
expect(parseKubeApi(url as never)).toBe(undefined);
});
});
@@ -74,9 +74,13 @@ export class ApiManager {
return iter.find(this.apis.values(), pathOrCallback);
}
const { apiBase } = parseKubeApi(pathOrCallback);
const parsedApi = parseKubeApi(pathOrCallback);
return this.apis.get(apiBase);
if (!parsedApi) {
return undefined;
}
return this.apis.get(parsedApi.apiBase);
}
getApiByKind(kind: string, apiVersion: string) {
@@ -141,9 +145,10 @@ export class ApiManager {
}
const { apiBase } = typeof apiOrBase === "string"
? parseKubeApi(apiOrBase)
? parseKubeApi(apiOrBase) ?? {}
: apiOrBase;
const api = this.getApi(apiBase);
const api = apiBase && this.getApi(apiBase);
if (!api) {
return undefined;
@@ -22,16 +22,16 @@ export interface IKubeApiParsed extends IKubeApiLinkRef {
apiVersionWithGroup: string;
}
export function parseKubeApi(path: string): IKubeApiParsed {
export function parseKubeApi(path: string): IKubeApiParsed | undefined {
const apiPath = new URL(path, "https://localhost").pathname;
const [, prefix, ...parts] = apiPath.split("/");
const apiPrefix = `/${prefix}`;
const [left, right, namespaced] = array.split(parts, "namespaces");
let apiGroup!: string;
let apiVersion!: string;
let namespace!: string;
let resource!: string;
let name!: string;
let apiGroup: string;
let apiVersion: string | undefined;
let namespace: string | undefined;
let resource: string;
let name: string | undefined;
if (namespaced) {
switch (right.length) {
@@ -46,26 +46,21 @@ export function parseKubeApi(path: string): IKubeApiParsed {
break;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
apiVersion = left.pop()!;
apiGroup = left.join("/");
let rest: string[];
[apiVersion, ...rest] = left;
apiGroup = rest.join("/");
} else {
switch (left.length) {
case 0:
throw new Error(`invalid apiPath: ${apiPath}`);
case 4:
[apiGroup, apiVersion, resource, name] = left;
break;
case 2:
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
resource = left.pop()!;
// fallthrough
case 1:
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
apiVersion = left.pop()!;
apiGroup = "";
break;
default:
if (left.length === 0) {
return undefined;
}
if (left.length === 1 || left.length === 2) {
[apiVersion, resource] = left;
apiGroup = "";
} else if (left.length === 4) {
[apiGroup, apiVersion, resource, name] = left;
} else {
/**
* Given that
* - `apiVersion` is `GROUP/VERSION` and
@@ -82,15 +77,14 @@ export function parseKubeApi(path: string): IKubeApiParsed {
* 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);
}
break;
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);
}
}
}
@@ -98,7 +92,7 @@ export function parseKubeApi(path: string): IKubeApiParsed {
const apiBase = [apiPrefix, apiGroup, apiVersion, resource].filter(v => v).join("/");
if (!apiBase) {
throw new Error(`invalid apiPath: ${apiPath}`);
return undefined;
}
return {
@@ -110,7 +104,7 @@ export function parseKubeApi(path: string): IKubeApiParsed {
}
function isIKubeApiParsed(refOrParsed: IKubeApiLinkRef | IKubeApiParsed): refOrParsed is IKubeApiParsed {
return "apiGroup" in refOrParsed;
return "apiGroup" in refOrParsed && !!refOrParsed.apiGroup;
}
export function createKubeApiURL(linkRef: IKubeApiLinkRef): string;
+24 -18
View File
@@ -264,12 +264,16 @@ export class KubeApi<
allowedUsableVersions,
} = opts;
assert(fullApiPathname, "apiBase MUST be provied either via KubeApiOptions.apiBase or KubeApiOptions.objectConstructor.apiBase");
assert(fullApiPathname, "apiBase MUST be provided either via KubeApiOptions.apiBase or KubeApiOptions.objectConstructor.apiBase");
assert(request, "request MUST be provided if not in a cluster page frame context");
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(fullApiPathname);
const parsedApi = parseKubeApi(fullApiPathname);
assert(kind, "kind MUST be provied either via KubeApiOptions.kind or KubeApiOptions.objectConstructor.kind");
assert(parsedApi, "apiBase MUST be a valid kube api pathname");
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parsedApi;
assert(kind, "kind MUST be provided either via KubeApiOptions.kind or KubeApiOptions.objectConstructor.kind");
assert(apiPrefix, "apiBase MUST be parsable as a kubeApi selfLink style string");
this.doCheckPreferredVersion = doCheckPreferredVersion;
@@ -308,24 +312,26 @@ export class KubeApi<
const apiBases = new Set(rawApiBases);
for (const apiUrl of apiBases) {
try {
const { apiPrefix, apiGroup, resource } = parseKubeApi(apiUrl);
const list = await this.request.get(`${apiPrefix}/${apiGroup}`) as KubeApiResourceVersionList;
const resourceVersions = getOrderedVersions(list, this.allowedUsableVersions?.[apiGroup]);
const parsedApi = parseKubeApi(apiUrl);
for (const resourceVersion of resourceVersions) {
const { resources } = await this.request.get(`${apiPrefix}/${resourceVersion.groupVersion}`) as KubeApiResourceList;
if (!parsedApi) {
continue;
}
if (resources.some(({ name }) => name === resource)) {
return {
apiPrefix,
apiGroup,
apiVersionPreferred: resourceVersion.version,
};
}
const { apiPrefix, apiGroup, resource } = parsedApi;
const list = await this.request.get(`${apiPrefix}/${apiGroup}`) as KubeApiResourceVersionList;
const resourceVersions = getOrderedVersions(list, this.allowedUsableVersions?.[apiGroup]);
for (const resourceVersion of resourceVersions) {
const { resources } = await this.request.get(`${apiPrefix}/${resourceVersion.groupVersion}`) as KubeApiResourceList;
if (resources.some(({ name }) => name === resource)) {
return {
apiPrefix,
apiGroup,
apiVersionPreferred: resourceVersion.version,
};
}
} catch (error) {
// Exception is ignored as we can try the next url
}
}
@@ -7,18 +7,18 @@ import { parseKubeApi } from "../kube-api-parse";
import { kubeApiInjectionToken } from "./kube-api-injection-token";
import type { KubeApi } from "../kube-api";
export type GetKubeApiFromPath = (apiPath: string) => KubeApi | undefined;
const getKubeApiFromPathInjectable = getInjectable({
id: "get-kube-api-from-path",
instantiate: (di) => {
instantiate: (di): GetKubeApiFromPath => {
const kubeApis = di.injectMany(kubeApiInjectionToken);
return (apiPath: string) => {
const parsed = parseKubeApi(apiPath);
const kubeApi = kubeApis.find((api) => api.apiBase === parsed.apiBase);
return (kubeApi as KubeApi) || undefined;
return kubeApis.find((api) => api.apiBase === parsed?.apiBase);
};
},
});
@@ -324,7 +324,11 @@ export class KubeObjectStore<
@action
async loadFromPath(resourcePath: string) {
const { namespace, name } = parseKubeApi(resourcePath);
const parsedApi = parseKubeApi(resourcePath);
assert(parsedApi, "resourcePath must be a valid kube api");
const { namespace, name } = parsedApi;
assert(name, "name must be part of resourcePath");