harn-stdlib 0.10.68

Embedded Harn standard library source catalog
Documentation
/** std/net - local networking helpers for scripts. */
type UnixSocketJsonOptions = {timeout_ms?: int, max_response_bytes?: int}

type UnixSocketJsonResult = {
  ok: bool,
  status: string,
  path: string,
  duration_ms: int,
  response?: any,
  raw_response?: string,
  bytes_read?: int,
  error?: string,
}

/**
 * Send one JSON request line to a Unix-domain socket and read one JSON response line.
 *
 * Expected readiness failures return `{ok: false, status, error?}` instead of throwing.
 *
 * @effects: [network]
 * @errors: []
 * @example: unix_socket_json_request(harness.net, path, {id: "ping", method: "ping"})
 */
pub fn unix_socket_json_request(
  net: HarnessNet,
  path: string,
  request,
  options: UnixSocketJsonOptions = {},
) -> UnixSocketJsonResult {
  return net.unix_socket_json_request(path, request, options ?? {})
}

fn __http_date_pad2(value) {
  const text = to_string(to_int(value) ?? 0)
  if len(text) == 1 {
    return "0" + text
  }
  return text
}

fn __http_date_month_number(name) {
  const months = {
    apr: "04",
    aug: "08",
    dec: "12",
    feb: "02",
    jan: "01",
    jul: "07",
    jun: "06",
    mar: "03",
    may: "05",
    nov: "11",
    oct: "10",
    sep: "09",
  }
  return months[lowercase(name)]
}

fn __http_date_ms(clock: HarnessClock, raw) {
  const matches = regex_captures(
    "^[A-Za-z]{3},\\s+(\\d{1,2})\\s+([A-Za-z]{3})\\s+(\\d{4})\\s+(\\d{2}):(\\d{2}):(\\d{2})\\s+GMT$",
    raw,
  )
  if len(matches) == 0 {
    return nil
  }
  const parts = matches[0].groups
  const month = __http_date_month_number(parts[1])
  if month == nil {
    return nil
  }
  const iso = parts[2] + "-" + month + "-" + __http_date_pad2(parts[0])
    + "T"
    + parts[3]
    + ":"
    + parts[4]
    + ":"
    + parts[5]
    + "Z"
  const parsed = try {
    date_parse(iso)
  }
  if is_err(parsed) {
    return nil
  }
  const delta_seconds = to_float(unwrap(parsed)) - clock.timestamp()
  if delta_seconds <= 0.0 {
    return 0
  }
  return to_int(delta_seconds * 1000.0)
}

/**
 * Parse an HTTP `Retry-After` header value into a wait in milliseconds.
 *
 * Accepts both RFC 9110 forms — delay seconds (integer or fractional) and an
 * HTTP-date — returning `0` for values already in the past and `nil` when the
 * value is empty or unparseable. One owner for every retry surface: hand-rolled
 * copies that assumed the numeric form crashed on legal date headers.
 *
 * @effects: []
 * @errors: []
 * @example: http_retry_after_ms(harness.clock, "120")
 */
pub fn http_retry_after_ms(clock: HarnessClock, value: any) -> int? {
  const raw = trim(to_string(value ?? ""))
  if raw == "" {
    return nil
  }
  const seconds = to_float(raw)
  if seconds != nil {
    if seconds <= 0.0 {
      return 0
    }
    return to_int(seconds * 1000.0)
  }
  const parsed = try {
    date_parse(raw)
  }
  if is_ok(parsed) {
    const delta_seconds = to_float(unwrap(parsed)) - clock.timestamp()
    if delta_seconds <= 0.0 {
      return 0
    }
    return to_int(delta_seconds * 1000.0)
  }
  return __http_date_ms(clock, raw)
}