matching.ts 1.2 KB

123456789101112131415161718192021222324252627282930313233343536
  1. import { Option, Schema } from "effect"
  2. import type { RequestSnapshot } from "./schema"
  3. const JsonValue = Schema.fromJsonString(Schema.Unknown)
  4. export const decodeJson = Schema.decodeUnknownOption(JsonValue)
  5. const isRecord = (value: unknown): value is Record<string, unknown> =>
  6. value !== null && typeof value === "object" && !Array.isArray(value)
  7. export const canonicalizeJson = (value: unknown): unknown => {
  8. if (Array.isArray(value)) return value.map(canonicalizeJson)
  9. if (isRecord(value)) {
  10. return Object.fromEntries(
  11. Object.keys(value)
  12. .toSorted()
  13. .map((key) => [key, canonicalizeJson(value[key])]),
  14. )
  15. }
  16. return value
  17. }
  18. export type RequestMatcher = (incoming: RequestSnapshot, recorded: RequestSnapshot) => boolean
  19. export const canonicalSnapshot = (snapshot: RequestSnapshot): string =>
  20. JSON.stringify({
  21. method: snapshot.method,
  22. url: snapshot.url,
  23. headers: canonicalizeJson(snapshot.headers),
  24. body: Option.match(decodeJson(snapshot.body), {
  25. onNone: () => snapshot.body,
  26. onSome: canonicalizeJson,
  27. }),
  28. })
  29. export const defaultMatcher: RequestMatcher = (incoming, recorded) =>
  30. canonicalSnapshot(incoming) === canonicalSnapshot(recorded)