remove shitty duration parser
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
c4385abb33
commit
2e8f6a1d14
12
src/api.ts
12
src/api.ts
@ -1,4 +1,3 @@
|
||||
import { parse } from "./duration";
|
||||
import { perform } from "./email";
|
||||
import type { EmailJob } from "./job";
|
||||
import { ConsoleLogger } from "./logger";
|
||||
@ -11,16 +10,7 @@ export const main = (port: number) => {
|
||||
|
||||
const url = new URL(req.url);
|
||||
if (req.method === "POST" && url.pathname === "/api/email") {
|
||||
const prevalidatedJob = 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 job: EmailJob = await req.json();
|
||||
|
||||
const jobInsensitive = structuredClone(job);
|
||||
jobInsensitive.from.username = "****REDACTED****";
|
||||
|
156
src/duration.ts
156
src/duration.ts
@ -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),
|
||||
);
|
||||
};
|
@ -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"'));
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user