transform zod, type check and result processing with result/maybe

This commit is contained in:
2024-05-09 01:46:20 -05:00
parent a7a025bea7
commit d2f4b238b8
2 changed files with 64 additions and 28 deletions

View File

@@ -20,6 +20,8 @@ import {
import { truncated } from "@/helpers"; import { truncated } from "@/helpers";
import type { ZodSchema } from "zod"; import type { ZodSchema } from "zod";
import type { ParsedGeneric } from "@/components/lookup/Generic"; import type { ParsedGeneric } from "@/components/lookup/Generic";
import { Maybe, Result } from "true-myth";
import { err } from "true-myth/dist/es/result";
export type WarningHandler = (warning: { message: string }) => void; export type WarningHandler = (warning: { message: string }) => void;
@@ -132,22 +134,32 @@ const useLookup = (warningHandler?: WarningHandler) => {
async function getAndParse<T>( async function getAndParse<T>(
url: string, url: string,
schema: ZodSchema schema: ZodSchema
): Promise<T | undefined> { ): Promise<Maybe<T>> {
const response = await fetch(url); const response = await fetch(url);
if (response.status == 200) return schema.parse(await response.json()) as T; if (response.status == 200) {
const safeValue = schema.safeParse(await response.json());
if (safeValue.success) return Maybe.just(safeValue.data);
else {
console.error(safeValue.error);
return Maybe.nothing();
}
}
return Maybe.nothing();
} }
async function submitInternal(): Promise<ParsedGeneric | undefined> { async function submitInternal(): Promise<Result<ParsedGeneric, Error>> {
if (target == null || target.length == 0) if (target == null || target.length == 0)
throw new Error("A target must be given in order to execute a lookup."); return Result.err(
new Error("A target must be given in order to execute a lookup.")
);
const targetType = getType(target); const targetType = getType(target);
if (targetType.isNothing) { if (targetType.isErr) {
throw new Error( return Result.err(
`The type could not be detected given the target (${JSON.stringify( new Error("Unable to determine type, unable to send query", {
target cause: targetType.error,
)}).` })
); );
} }
@@ -156,12 +168,18 @@ const useLookup = (warningHandler?: WarningHandler) => {
case "ip4": { case "ip4": {
await loadBootstrap("ip4"); await loadBootstrap("ip4");
const url = getRegistryURL(targetType.value, target); const url = getRegistryURL(targetType.value, target);
return await getAndParse<IpNetwork>(url, IpNetworkSchema); const result = await getAndParse<IpNetwork>(url, IpNetworkSchema);
if (result.isNothing)
return Result.err(new Error("No data was returned from the lookup."));
return Result.ok(result.value);
} }
case "ip6": { case "ip6": {
await loadBootstrap("ip6"); await loadBootstrap("ip6");
const url = getRegistryURL(targetType.value, target); const url = getRegistryURL(targetType.value, target);
return await getAndParse<IpNetwork>(url, IpNetworkSchema); const result = await getAndParse<IpNetwork>(url, IpNetworkSchema);
if (result.isNothing)
return Result.err(new Error("No data was returned from the lookup."));
return Result.ok(result.value);
} }
case "domain": { case "domain": {
await loadBootstrap("domain"); await loadBootstrap("domain");
@@ -169,42 +187,60 @@ const useLookup = (warningHandler?: WarningHandler) => {
if (url.startsWith("http://") && url != repeatableRef.current) { if (url.startsWith("http://") && url != repeatableRef.current) {
repeatableRef.current = url; repeatableRef.current = url;
throw new Error( return Result.err(
new Error(
"The registry this domain belongs to uses HTTP, which is not secure. " + "The registry this domain belongs to uses HTTP, which is not secure. " +
"In order to prevent a cryptic error from appearing due to mixed active content, " + "In order to prevent a cryptic error from appearing due to mixed active content, " +
"or worse, a CORS error, this lookup has been blocked. Try again to force the lookup." "or worse, a CORS error, this lookup has been blocked. Try again to force the lookup."
)
); );
} }
return await getAndParse<Domain>(url, DomainSchema); const result = await getAndParse<Domain>(url, DomainSchema);
if (result.isNothing)
return Result.err(new Error("No data was returned from the lookup."));
return Result.ok(result.value);
} }
case "autnum": { case "autnum": {
await loadBootstrap("autnum"); await loadBootstrap("autnum");
const url = getRegistryURL(targetType.value, target); const url = getRegistryURL(targetType.value, target);
return await getAndParse<AutonomousNumber>(url, AutonomousNumberSchema); const result = await getAndParse<AutonomousNumber>(
url,
AutonomousNumberSchema
);
if (result.isNothing)
return Result.err(new Error("No data was returned from the lookup."));
return Result.ok(result.value);
} }
case "url": case "url":
case "tld": case "tld":
case "registrar": case "registrar":
case "json": case "json":
default: default:
throw new Error("The type detected has not been implemented."); return Result.err(
new Error("The type detected has not been implemented.")
);
} }
} }
async function submit({ async function submit({
target, target,
}: SubmitProps): Promise<ParsedGeneric | undefined> { }: SubmitProps): Promise<Maybe<ParsedGeneric>> {
try { try {
const response = await submitInternal(); const response = await submitInternal();
if (response == undefined) if (response.isErr) {
throw new Error("Internal submission failed to yield any data."); setError(response.error.message);
console.error(response.error);
}
else setError(null);
setError(null); return response.isOk ? Maybe.just(response.value) : Maybe.nothing();
return response;
} catch (e) { } catch (e) {
if (!(e instanceof Error)) if (!(e instanceof Error))
setError("An unknown, unprocessable error has occurred."); setError("An unknown, unprocessable error has occurred.");
setError((e as Error).message); else
setError(e.message);
console.error(e);
return Maybe.nothing();
} }
} }

View File

@@ -1,5 +1,5 @@
import type { TargetType } from "@/types"; import type { TargetType } from "@/types";
import { Maybe } from "true-myth"; import { Result } from "true-myth";
// keeps track of the elements we've created so we can assign a unique ID // keeps track of the elements we've created so we can assign a unique ID
// let elementCounter = 123456; // let elementCounter = 123456;
@@ -774,9 +774,9 @@ const URIPatterns: [RegExp, TargetType][] = [
], ],
]; ];
export function getType(value: string): Maybe<TargetType> { export function getType(value: string): Result<TargetType, Error> {
for (const [pattern, type] of URIPatterns) { for (const [pattern, type] of URIPatterns) {
if (pattern.test(value)) return Maybe.of(type); if (pattern.test(value)) return Result.ok(type);
} }
return Maybe.nothing(); return Result.err(new Error("No patterns matched the input"));
} }