remove shitty duration parser
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Elizabeth Hunt 2024-12-15 02:30:42 -08:00
parent c4385abb33
commit 2e8f6a1d14
Signed by: simponic
GPG Key ID: 2909B9A7FF6213EE
3 changed files with 1 additions and 245 deletions

View File

@ -1,4 +1,3 @@
import { parse } from "./duration";
import { perform } from "./email"; import { perform } from "./email";
import type { EmailJob } from "./job"; import type { EmailJob } from "./job";
import { ConsoleLogger } from "./logger"; import { ConsoleLogger } from "./logger";
@ -11,16 +10,7 @@ export const main = (port: number) => {
const url = new URL(req.url); const url = new URL(req.url);
if (req.method === "POST" && url.pathname === "/api/email") { if (req.method === "POST" && url.pathname === "/api/email") {
const prevalidatedJob = await req.json(); const job: EmailJob = await req.json();
const interval = parse(prevalidatedJob.readRetry.interval);
if (interval._tag === "Left") {
return new Response(interval.left, { status: 400 });
}
prevalidatedJob.readRetry.interval = interval;
const job: EmailJob = {
...prevalidatedJob,
readRetry: { ...prevalidatedJob.readRetry, interval },
};
const jobInsensitive = structuredClone(job); const jobInsensitive = structuredClone(job);
jobInsensitive.from.username = "****REDACTED****"; jobInsensitive.from.username = "****REDACTED****";

View File

@ -1,156 +0,0 @@
import { flow, pipe } from "fp-ts/function";
import * as E from "fp-ts/lib/Either";
import * as S from "fp-ts/lib/string";
import * as O from "fp-ts/lib/Option";
import * as R from "fp-ts/lib/ReadonlyArray";
export type Duration = number;
export enum DurationUnit {
MILLISECOND,
SECOND,
MINUTE,
HOUR,
}
const durationUnitMap: Record<string, DurationUnit> = {
ms: DurationUnit.MILLISECOND,
milliseconds: DurationUnit.MILLISECOND,
sec: DurationUnit.SECOND,
seconds: DurationUnit.SECOND,
min: DurationUnit.MINUTE,
minutes: DurationUnit.MINUTE,
hr: DurationUnit.HOUR,
hour: DurationUnit.HOUR,
hours: DurationUnit.HOUR,
};
const getDurationUnit = (key: string): O.Option<DurationUnit> =>
O.fromNullable(durationUnitMap[key.toLowerCase()]);
export const getMs = (duration: Duration): number => duration;
export const getSeconds = (duration: Duration): number => duration / 1000;
export const getMinutes = (duration: Duration): number =>
getSeconds(duration) / 60;
export const getHours = (duration: Duration): number =>
getMinutes(duration) / 60;
export const format = (duration: Duration): string => {
const ms = getMs(duration) % 1000;
const seconds = getSeconds(duration) % 60;
const minutes = getMinutes(duration) % 60;
const hours = getHours(duration);
return (
[hours, minutes, seconds]
.map((x) => Math.floor(x).toString().padStart(2, "0"))
.join(":") +
"." +
ms.toString().padStart(3, "0")
);
};
export interface DurationBuilder {
readonly millis: number;
readonly seconds: number;
readonly minutes: number;
readonly hours: number;
}
export const createDurationBuilder = (): DurationBuilder => ({
millis: 0,
seconds: 0,
minutes: 0,
hours: 0,
});
export type DurationBuilderField<T> = (
arg: T,
) => (builder: DurationBuilder) => DurationBuilder;
export const withMillis: DurationBuilderField<number> =
(millis) => (builder) => ({
...builder,
millis,
});
export const withSeconds: DurationBuilderField<number> =
(seconds) => (builder) => ({
...builder,
seconds,
});
export const withMinutes: DurationBuilderField<number> =
(minutes) => (builder) => ({
...builder,
minutes,
});
export const withHours: DurationBuilderField<number> =
(hours) => (builder) => ({
...builder,
hours,
});
export const build = (builder: DurationBuilder): Duration =>
builder.millis +
builder.seconds * 1000 +
builder.minutes * 60 * 1000 +
builder.hours * 60 * 60 * 1000;
export const parse = (duration: string): E.Either<string, Duration> => {
const parts = pipe(
duration,
S.split(" "),
R.map(S.trim),
R.filter((part) => !S.isEmpty(part)),
);
const valueUnitPairs = pipe(
parts,
R.mapWithIndex((i, part) => {
const isUnit = i % 2 !== 0;
if (!isUnit) return E.right(O.none);
const value = Number(parts[i - 1]);
if (isNaN(value)) return E.left(`bad value: "${parts[i - 1]}"`);
const unit = getDurationUnit(part);
if (O.isNone(unit)) return E.left(`unknown duration type: ${part}`);
return E.right(O.some([unit.value, value] as [DurationUnit, number]));
}),
E.sequenceArray,
E.map(
flow(
R.filter(O.isSome),
R.map(({ value }) => value),
),
),
);
return pipe(
valueUnitPairs,
E.flatMap(
R.reduce(
E.of<string, DurationBuilder>(createDurationBuilder()),
(builderEither, [unit, value]) =>
pipe(
builderEither,
E.chain((builder) => {
switch (unit) {
case DurationUnit.MILLISECOND:
return E.right(withMillis(value)(builder));
case DurationUnit.SECOND:
return E.right(withSeconds(value)(builder));
case DurationUnit.MINUTE:
return E.right(withMinutes(value)(builder));
case DurationUnit.HOUR:
return E.right(withHours(value)(builder));
default:
return E.left(`unknown unit: ${unit}`);
}
}),
),
),
),
E.map(build),
);
};

View File

@ -1,78 +0,0 @@
import { pipe } from "fp-ts/function";
import * as E from "fp-ts/Either";
import { describe, test, expect } from "bun:test";
import * as D from "../src/duration";
describe("Duration Utility", () => {
test("get unit should convert correctly", () => {
expect(D.getMs(1000)).toBe(1000);
expect(D.getSeconds(1000)).toBe(1);
expect(D.getMinutes(60000)).toBe(1);
expect(D.getHours(3600000)).toBe(1);
});
test("format should format duration correctly", () => {
expect(D.format(3600000 + 237 + 5 * 60 * 1000)).toBe("01:05:00.237");
});
});
describe("DurationBuilder", () => {
test("createDurationBuilder should create a builder with zero values", () => {
const builder = D.createDurationBuilder();
expect(builder.millis).toBe(0);
expect(builder.seconds).toBe(0);
expect(builder.minutes).toBe(0);
expect(builder.hours).toBe(0);
});
test("withMillis should set fields correctly and with precedence", () => {
const builder = pipe(
D.createDurationBuilder(),
D.withMillis(0),
D.withSeconds(20),
D.withMinutes(30),
D.withHours(40),
D.withMillis(10),
);
expect(builder.millis).toBe(10);
expect(builder.seconds).toBe(20);
expect(builder.minutes).toBe(30);
expect(builder.hours).toBe(40);
});
test("build should calculate total duration correctly", () => {
const duration = pipe(
D.createDurationBuilder(),
D.withMillis(10),
D.withSeconds(20),
D.withMinutes(30),
D.withHours(40),
D.build,
);
expect(duration).toBe(
10 + 20 * 1000 + 30 * 60 * 1000 + 40 * 60 * 60 * 1000,
);
});
});
describe("parse", () => {
test("should return right for a valid duration", () => {
expect(D.parse("10 seconds 1 hr 30 min")).toEqual(
E.right(1 * 60 * 60 * 1000 + 30 * 60 * 1000 + 10 * 1000),
);
});
test("should operate with order", () => {
expect(D.parse("1 hr 30 min 2 hours")).toEqual(
E.right(2 * 60 * 60 * 1000 + 30 * 60 * 1000),
);
});
test("returns left for unknown duration unit", () => {
expect(D.parse("1 xyz")).toEqual(E.left("unknown duration type: xyz"));
});
test("return left for invalid number", () => {
expect(D.parse("abc ms")).toEqual(E.left('bad value: "abc"'));
});
});