///|
/// JSON value used by MoonHAR.
pub(all) enum JVal {
  JNull
  JBool(Bool)
  JNum(String)
  JStr(String)
  JArr(Array[JVal])
  JObj(Map[String, JVal])
} derive(Eq, Debug)

///|
/// Parse or mapping problem with a stable path.
pub(all) struct HarError {
  path : String
  message : String
} derive(Eq, Debug)

///|
/// Name/value pair used by headers, cookies, and query strings.
pub(all) struct HarPair {
  name : String
  value : String
} derive(Eq, Debug)

///|
/// HAR request payload model.
pub(all) struct HarPostData {
  mime_type : String
  text : String
  params : Array[HarPair]
} derive(Eq, Debug)

///|
/// HAR request model.
pub(all) struct HarRequest {
  http_method : String
  url : String
  http_version : String
  headers : Array[HarPair]
  query_string : Array[HarPair]
  cookies : Array[HarPair]
  headers_size : Int
  body_size : Int
  post_data : HarPostData?
} derive(Eq, Debug)

///|
/// HAR response body content.
pub(all) struct HarContent {
  size : Int
  mime_type : String
  text : String
  encoding : String
} derive(Eq, Debug)

///|
/// HAR response model.
pub(all) struct HarResponse {
  status : Int
  status_text : String
  http_version : String
  headers : Array[HarPair]
  cookies : Array[HarPair]
  content : HarContent
  redirect_url : String
  headers_size : Int
  body_size : Int
} derive(Eq, Debug)

///|
/// Timing fields are milliseconds. A value of -1 means the phase was omitted.
pub(all) struct HarTimings {
  blocked : Int
  dns : Int
  connect : Int
  ssl : Int
  send : Int
  wait : Int
  receive : Int
} derive(Eq, Debug)

///|
/// HAR entry model.
pub(all) struct HarEntry {
  started_date_time : String
  time : Int
  request : HarRequest
  response : HarResponse
  timings : HarTimings
  server_ip_address : String
  connection : String
  pageref : String
} derive(Eq, Debug)

///|
/// Page timings stored at HAR page level.
pub(all) struct HarPageTimings {
  on_content_load : Int
  on_load : Int
} derive(Eq, Debug)

///|
/// HAR page model.
pub(all) struct HarPage {
  id : String
  title : String
  started_date_time : String
  page_timings : HarPageTimings
} derive(Eq, Debug)

///|
/// Top-level HAR log.
pub(all) struct HarLog {
  version : String
  creator_name : String
  creator_version : String
  pages : Array[HarPage]
  entries : Array[HarEntry]
} derive(Eq, Debug)

///|
/// Parsed HAR document.
pub(all) struct HarArchive {
  log : HarLog
} derive(Eq, Debug)

///|
/// Mutable parser state.
priv struct JsonParser {
  text : String
  mut pos : Int
}

///|
pub fn parse_json(text : String) -> Result[JVal, HarError] {
  let p = JsonParser::{ text, pos: 0 }
  p.skip_ws()
  match p.parse_value() {
    Ok(v) => {
      p.skip_ws()
      if p.pos == text.length() {
        Ok(v)
      } else {
        Err(HarError::{
          path: "$",
          message: "trailing characters after JSON value",
        })
      }
    }
    Err(e) => Err(e)
  }
}

///|
pub fn parse_har(text : String) -> Result[HarArchive, HarError] {
  match parse_json(text) {
    Ok(json) => har_from_json(json)
    Err(e) => Err(e)
  }
}

///|
fn JsonParser::current(self : JsonParser) -> Char? {
  self.text.get_char(self.pos)
}

///|
fn JsonParser::advance(self : JsonParser) -> Unit {
  self.pos = self.pos + 1
}

///|
fn JsonParser::err(self : JsonParser, message : String) -> HarError {
  HarError::{ path: "$@" + self.pos.to_string(), message }
}

///|
fn JsonParser::skip_ws(self : JsonParser) -> Unit {
  while self.pos < self.text.length() {
    match self.current() {
      Some(' ') | Some('\n') | Some('\r') | Some('\t') => self.advance()
      _ => break
    }
  }
}

///|
fn JsonParser::parse_value(self : JsonParser) -> Result[JVal, HarError] {
  self.skip_ws()
  match self.current() {
    Some('{') => self.parse_object()
    Some('[') => self.parse_array()
    Some('"') => self.parse_string_value()
    Some('t') => self.parse_literal("true", JBool(true))
    Some('f') => self.parse_literal("false", JBool(false))
    Some('n') => self.parse_literal("null", JNull)
    Some('-') => self.parse_number()
    Some(ch) =>
      if is_digit(ch) {
        self.parse_number()
      } else {
        Err(self.err("unexpected character while parsing JSON value"))
      }
    None => Err(self.err("unexpected end of JSON input"))
  }
}

///|
fn JsonParser::parse_literal(
  self : JsonParser,
  literal : String,
  value : JVal,
) -> Result[JVal, HarError] {
  let end = self.pos + literal.length()
  if end <= self.text.length() && self.text[self.pos:end].to_owned() == literal {
    self.pos = end
    Ok(value)
  } else {
    Err(self.err("invalid JSON literal"))
  }
}

///|
fn JsonParser::parse_string_value(self : JsonParser) -> Result[JVal, HarError] {
  match self.parse_string_raw() {
    Ok(s) => Ok(JStr(s))
    Err(e) => Err(e)
  }
}

///|
fn JsonParser::parse_string_raw(self : JsonParser) -> Result[String, HarError] {
  match self.current() {
    Some('"') => self.advance()
    _ => return Err(self.err("expected string opening quote"))
  }
  let mut out = ""
  while self.pos < self.text.length() {
    match self.current() {
      Some('"') => {
        self.advance()
        return Ok(out)
      }
      Some('\\') => {
        self.advance()
        match self.current() {
          Some('"') => {
            out = out + "\""
            self.advance()
          }
          Some('\\') => {
            out = out + "\\"
            self.advance()
          }
          Some('/') => {
            out = out + "/"
            self.advance()
          }
          Some('b') => {
            out = out + "\b"
            self.advance()
          }
          Some('f') => {
            out = out + "\u{000c}"
            self.advance()
          }
          Some('n') => {
            out = out + "\n"
            self.advance()
          }
          Some('r') => {
            out = out + "\r"
            self.advance()
          }
          Some('t') => {
            out = out + "\t"
            self.advance()
          }
          Some('u') => {
            self.advance()
            match self.parse_unicode_escape() {
              Ok(decoded) => out = out + decoded
              Err(e) => return Err(e)
            }
          }
          _ => return Err(self.err("invalid string escape"))
        }
      }
      Some(ch) => {
        out = out + ch.to_string()
        self.advance()
      }
      None => return Err(self.err("unterminated JSON string"))
    }
  }
  Err(self.err("unterminated JSON string"))
}

///|
fn JsonParser::parse_number(self : JsonParser) -> Result[JVal, HarError] {
  let start = self.pos
  if self.current() == Some('-') {
    self.advance()
  }
  if self.current() == Some('0') {
    self.advance()
  } else {
    if !self.current().map_or(false, is_digit) {
      return Err(self.err("expected digit in number"))
    }
    while self.current().map_or(false, is_digit) {
      self.advance()
    }
  }
  if self.current() == Some('.') {
    self.advance()
    if !self.current().map_or(false, is_digit) {
      return Err(self.err("expected digit after decimal point"))
    }
    while self.current().map_or(false, is_digit) {
      self.advance()
    }
  }
  match self.current() {
    Some('e') | Some('E') => {
      self.advance()
      match self.current() {
        Some('+') | Some('-') => self.advance()
        _ => ()
      }
      if !self.current().map_or(false, is_digit) {
        return Err(self.err("expected digit in exponent"))
      }
      while self.current().map_or(false, is_digit) {
        self.advance()
      }
    }
    _ => ()
  }
  Ok(JNum(self.text[start:self.pos].to_owned()))
}

///|
fn JsonParser::parse_array(self : JsonParser) -> Result[JVal, HarError] {
  self.advance()
  let arr : Array[JVal] = []
  self.skip_ws()
  if self.current() == Some(']') {
    self.advance()
    return Ok(JArr(arr))
  }
  while true {
    match self.parse_value() {
      Ok(v) => arr.push(v)
      Err(e) => return Err(e)
    }
    self.skip_ws()
    match self.current() {
      Some(',') => {
        self.advance()
        self.skip_ws()
      }
      Some(']') => {
        self.advance()
        return Ok(JArr(arr))
      }
      _ => return Err(self.err("expected comma or array closing bracket"))
    }
  }
  Err(self.err("unreachable array parser state"))
}

///|
fn JsonParser::parse_object(self : JsonParser) -> Result[JVal, HarError] {
  self.advance()
  let obj : Map[String, JVal] = Map([])
  self.skip_ws()
  if self.current() == Some('}') {
    self.advance()
    return Ok(JObj(obj))
  }
  while true {
    self.skip_ws()
    let key = match self.parse_string_raw() {
      Ok(s) => s
      Err(e) => return Err(e)
    }
    self.skip_ws()
    if self.current() != Some(':') {
      return Err(self.err("expected colon after object key"))
    }
    self.advance()
    let value = match self.parse_value() {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    obj[key] = value
    self.skip_ws()
    match self.current() {
      Some(',') => {
        self.advance()
        self.skip_ws()
      }
      Some('}') => {
        self.advance()
        return Ok(JObj(obj))
      }
      _ => return Err(self.err("expected comma or object closing brace"))
    }
  }
  Err(self.err("unreachable object parser state"))
}

///|
fn JsonParser::parse_unicode_escape(
  self : JsonParser,
) -> Result[String, HarError] {
  let high = match self.parse_hex_quad() {
    Ok(value) => value
    Err(e) => return Err(e)
  }
  if high >= 0xD800 && high <= 0xDBFF {
    if self.current() != Some('\\') {
      return Err(self.err("high surrogate must be followed by a low surrogate"))
    }
    self.advance()
    if self.current() != Some('u') {
      return Err(
        self.err("high surrogate must be followed by a unicode escape"),
      )
    }
    self.advance()
    let low = match self.parse_hex_quad() {
      Ok(value) => value
      Err(e) => return Err(e)
    }
    if low < 0xDC00 || low > 0xDFFF {
      return Err(self.err("invalid low surrogate"))
    }
    let codepoint = 0x10000 + (high - 0xD800) * 0x400 + (low - 0xDC00)
    Ok(codepoint.unsafe_to_char().to_string())
  } else if high >= 0xDC00 && high <= 0xDFFF {
    Err(self.err("unexpected low surrogate"))
  } else {
    Ok(high.unsafe_to_char().to_string())
  }
}

///|
fn JsonParser::parse_hex_quad(self : JsonParser) -> Result[Int, HarError] {
  let mut value = 0
  for i = 0; i < 4; i = i + 1 {
    let digit = match self.current() {
      Some(ch) => hex_value(ch)
      None => return Err(self.err("incomplete unicode escape"))
    }
    if digit < 0 {
      return Err(self.err("invalid unicode escape"))
    }
    value = value * 16 + digit
    self.advance()
  }
  Ok(value)
}

///|
fn hex_value(ch : Char) -> Int {
  if ch >= '0' && ch <= '9' {
    ch.to_int() - '0'.to_int()
  } else if ch >= 'a' && ch <= 'f' {
    ch.to_int() - 'a'.to_int() + 10
  } else if ch >= 'A' && ch <= 'F' {
    ch.to_int() - 'A'.to_int() + 10
  } else {
    -1
  }
}

///|
fn is_digit(ch : Char) -> Bool {
  ch >= '0' && ch <= '9'
}

///|
fn parse_int_text(text : String) -> Int {
  let mut i = 0
  let mut sign = 1
  let mut value = 0
  if text.get_char(0) == Some('-') {
    sign = -1
    i = 1
  }
  while i < text.length() {
    match text.get_char(i) {
      Some(ch) =>
        if is_digit(ch) {
          value = value * 10 + (ch.to_int() - '0'.to_int())
          i = i + 1
        } else {
          return value * sign
        }
      None => return value * sign
    }
  }
  value * sign
}

///|
fn expect_obj(
  value : JVal,
  path : String,
) -> Result[Map[String, JVal], HarError] {
  match value {
    JObj(obj) => Ok(obj)
    _ => Err(HarError::{ path, message: "expected object" })
  }
}

///|
fn expect_arr(value : JVal, path : String) -> Result[Array[JVal], HarError] {
  match value {
    JArr(arr) => Ok(arr)
    _ => Err(HarError::{ path, message: "expected array" })
  }
}

///|
fn expect_str(value : JVal, path : String) -> Result[String, HarError] {
  match value {
    JStr(s) => Ok(s)
    _ => Err(HarError::{ path, message: "expected string" })
  }
}

///|
fn expect_int(value : JVal, path : String) -> Result[Int, HarError] {
  match value {
    JNum(s) => Ok(parse_int_text(s))
    _ => Err(HarError::{ path, message: "expected number" })
  }
}

///|
fn opt_str(obj : Map[String, JVal], key : String, default : String) -> String {
  match obj.get(key) {
    Some(JStr(s)) => s
    _ => default
  }
}

///|
fn opt_int(obj : Map[String, JVal], key : String, default : Int) -> Int {
  match obj.get(key) {
    Some(JNum(s)) => parse_int_text(s)
    _ => default
  }
}

///|
fn req_obj(
  obj : Map[String, JVal],
  key : String,
  path : String,
) -> Result[Map[String, JVal], HarError] {
  match obj.get(key) {
    Some(v) => expect_obj(v, path + "." + key)
    None =>
      Err(HarError::{
        path: path + "." + key,
        message: "missing required object",
      })
  }
}

///|
fn req_arr(
  obj : Map[String, JVal],
  key : String,
  path : String,
) -> Result[Array[JVal], HarError] {
  match obj.get(key) {
    Some(v) => expect_arr(v, path + "." + key)
    None =>
      Err(HarError::{
        path: path + "." + key,
        message: "missing required array",
      })
  }
}

///|
fn req_str(
  obj : Map[String, JVal],
  key : String,
  path : String,
) -> Result[String, HarError] {
  match obj.get(key) {
    Some(v) => expect_str(v, path + "." + key)
    None =>
      Err(HarError::{
        path: path + "." + key,
        message: "missing required string",
      })
  }
}

///|
fn req_int(
  obj : Map[String, JVal],
  key : String,
  path : String,
) -> Result[Int, HarError] {
  match obj.get(key) {
    Some(v) => expect_int(v, path + "." + key)
    None =>
      Err(HarError::{
        path: path + "." + key,
        message: "missing required number",
      })
  }
}

///|
fn parse_pairs(
  values : Array[JVal],
  path : String,
) -> Result[Array[HarPair], HarError] {
  let out : Array[HarPair] = []
  for i = 0; i < values.length(); i = i + 1 {
    let obj = match expect_obj(values[i], path + "[" + i.to_string() + "]") {
      Ok(v) => v
      Err(e) => return Err(e)
    }
    out.push(HarPair::{
      name: opt_str(obj, "name", ""),
      value: opt_str(obj, "value", ""),
    })
  }
  Ok(out)
}

///|
fn parse_post_data(
  obj : Map[String, JVal],
  path : String,
) -> Result[HarPostData, HarError] {
  let params = match obj.get("params") {
    Some(JArr(arr)) =>
      match parse_pairs(arr, path + ".params") {
        Ok(v) => v
        Err(e) => return Err(e)
      }
    _ => []
  }
  Ok(HarPostData::{
    mime_type: opt_str(obj, "mimeType", ""),
    text: opt_str(obj, "text", ""),
    params,
  })
}

///|
fn parse_content(
  obj : Map[String, JVal],
  _path : String,
) -> Result[HarContent, HarError] {
  Ok(HarContent::{
    size: opt_int(obj, "size", 0),
    mime_type: opt_str(obj, "mimeType", ""),
    text: opt_str(obj, "text", ""),
    encoding: opt_str(obj, "encoding", ""),
  })
}

///|
fn parse_request(
  obj : Map[String, JVal],
  path : String,
) -> Result[HarRequest, HarError] {
  let headers = match obj.get("headers") {
    Some(JArr(arr)) =>
      match parse_pairs(arr, path + ".headers") {
        Ok(v) => v
        Err(e) => return Err(e)
      }
    _ => []
  }
  let query_string = match obj.get("queryString") {
    Some(JArr(arr)) =>
      match parse_pairs(arr, path + ".queryString") {
        Ok(v) => v
        Err(e) => return Err(e)
      }
    _ => []
  }
  let cookies = match obj.get("cookies") {
    Some(JArr(arr)) =>
      match parse_pairs(arr, path + ".cookies") {
        Ok(v) => v
        Err(e) => return Err(e)
      }
    _ => []
  }
  let post_data = match obj.get("postData") {
    Some(JObj(pd)) =>
      match parse_post_data(pd, path + ".postData") {
        Ok(v) => Some(v)
        Err(e) => return Err(e)
      }
    _ => None
  }
  Ok(HarRequest::{
    http_method: match req_str(obj, "method", path) {
      Ok(v) => v
      Err(e) => return Err(e)
    },
    url: match req_str(obj, "url", path) {
      Ok(v) => v
      Err(e) => return Err(e)
    },
    http_version: opt_str(obj, "httpVersion", ""),
    headers,
    query_string,
    cookies,
    headers_size: opt_int(obj, "headersSize", -1),
    body_size: opt_int(obj, "bodySize", -1),
    post_data,
  })
}

///|
fn parse_response(
  obj : Map[String, JVal],
  path : String,
) -> Result[HarResponse, HarError] {
  let headers = match obj.get("headers") {
    Some(JArr(arr)) =>
      match parse_pairs(arr, path + ".headers") {
        Ok(v) => v
        Err(e) => return Err(e)
      }
    _ => []
  }
  let cookies = match obj.get("cookies") {
    Some(JArr(arr)) =>
      match parse_pairs(arr, path + ".cookies") {
        Ok(v) => v
        Err(e) => return Err(e)
      }
    _ => []
  }
  let content_obj = match req_obj(obj, "content", path) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let content = match parse_content(content_obj, path + ".content") {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  Ok(HarResponse::{
    status: match req_int(obj, "status", path) {
      Ok(v) => v
      Err(e) => return Err(e)
    },
    status_text: opt_str(obj, "statusText", ""),
    http_version: opt_str(obj, "httpVersion", ""),
    headers,
    cookies,
    content,
    redirect_url: opt_str(obj, "redirectURL", ""),
    headers_size: opt_int(obj, "headersSize", -1),
    body_size: opt_int(obj, "bodySize", -1),
  })
}

///|
fn parse_timings(obj : Map[String, JVal]) -> HarTimings {
  HarTimings::{
    blocked: opt_int(obj, "blocked", -1),
    dns: opt_int(obj, "dns", -1),
    connect: opt_int(obj, "connect", -1),
    ssl: opt_int(obj, "ssl", -1),
    send: opt_int(obj, "send", -1),
    wait: opt_int(obj, "wait", -1),
    receive: opt_int(obj, "receive", -1),
  }
}

///|
fn parse_entry(value : JVal, index : Int) -> Result[HarEntry, HarError] {
  let path = "$.log.entries[" + index.to_string() + "]"
  let obj = match expect_obj(value, path) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let req = match req_obj(obj, "request", path) {
    Ok(v) =>
      match parse_request(v, path + ".request") {
        Ok(r) => r
        Err(e) => return Err(e)
      }
    Err(e) => return Err(e)
  }
  let res = match req_obj(obj, "response", path) {
    Ok(v) =>
      match parse_response(v, path + ".response") {
        Ok(r) => r
        Err(e) => return Err(e)
      }
    Err(e) => return Err(e)
  }
  let timings = match req_obj(obj, "timings", path) {
    Ok(v) => parse_timings(v)
    Err(e) => return Err(e)
  }
  Ok(HarEntry::{
    started_date_time: opt_str(obj, "startedDateTime", ""),
    time: opt_int(obj, "time", 0),
    request: req,
    response: res,
    timings,
    server_ip_address: opt_str(obj, "serverIPAddress", ""),
    connection: opt_str(obj, "connection", ""),
    pageref: opt_str(obj, "pageref", ""),
  })
}

///|
fn parse_page_timings(obj : Map[String, JVal]) -> HarPageTimings {
  HarPageTimings::{
    on_content_load: opt_int(obj, "onContentLoad", -1),
    on_load: opt_int(obj, "onLoad", -1),
  }
}

///|
fn parse_page(value : JVal, index : Int) -> Result[HarPage, HarError] {
  let path = "$.log.pages[" + index.to_string() + "]"
  let obj = match expect_obj(value, path) {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let timings = match obj.get("pageTimings") {
    Some(JObj(t)) => parse_page_timings(t)
    _ => HarPageTimings::{ on_content_load: -1, on_load: -1 }
  }
  Ok(HarPage::{
    id: opt_str(obj, "id", ""),
    title: opt_str(obj, "title", ""),
    started_date_time: opt_str(obj, "startedDateTime", ""),
    page_timings: timings,
  })
}

///|
fn har_from_json(value : JVal) -> Result[HarArchive, HarError] {
  let root = match expect_obj(value, "$") {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let log = match req_obj(root, "log", "$") {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let creator = match req_obj(log, "creator", "$.log") {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let pages_json = match log.get("pages") {
    Some(JArr(arr)) => arr
    _ => []
  }
  let entries_json = match req_arr(log, "entries", "$.log") {
    Ok(v) => v
    Err(e) => return Err(e)
  }
  let pages : Array[HarPage] = []
  for i = 0; i < pages_json.length(); i = i + 1 {
    match parse_page(pages_json[i], i) {
      Ok(page) => pages.push(page)
      Err(e) => return Err(e)
    }
  }
  let entries : Array[HarEntry] = []
  for i = 0; i < entries_json.length(); i = i + 1 {
    match parse_entry(entries_json[i], i) {
      Ok(entry) => entries.push(entry)
      Err(e) => return Err(e)
    }
  }
  Ok(HarArchive::{
    log: HarLog::{
      version: opt_str(log, "version", ""),
      creator_name: opt_str(creator, "name", ""),
      creator_version: opt_str(creator, "version", ""),
      pages,
      entries,
    },
  })
}

///|
pub fn sample_har() -> String {
  (
    #|{
    #|  "log": {
    #|    "version": "1.2",
    #|    "creator": { "name": "MoonHAR fixture", "version": "1.0" },
    #|    "pages": [
    #|      {
    #|        "id": "page_1",
    #|        "startedDateTime": "2026-08-11T00:00:00.000Z",
    #|        "title": "Example",
    #|        "pageTimings": { "onContentLoad": 80, "onLoad": 140 }
    #|      }
    #|    ],
    #|    "entries": [
    #|      {
    #|        "pageref": "page_1",
    #|        "startedDateTime": "2026-08-11T00:00:00.010Z",
    #|        "time": 120,
    #|        "request": {
    #|          "method": "GET",
    #|          "url": "https://example.com/index.html",
    #|          "httpVersion": "HTTP/2",
    #|          "cookies": [],
    #|          "headers": [{ "name": "accept", "value": "text/html" }],
    #|          "queryString": [],
    #|          "headersSize": 128,
    #|          "bodySize": 0
    #|        },
    #|        "response": {
    #|          "status": 200,
    #|          "statusText": "OK",
    #|          "httpVersion": "HTTP/2",
    #|          "cookies": [],
    #|          "headers": [{ "name": "content-type", "value": "text/html" }],
    #|          "content": { "size": 2048, "mimeType": "text/html", "text": "" },
    #|          "redirectURL": "",
    #|          "headersSize": 96,
    #|          "bodySize": 2048
    #|        },
    #|        "cache": {},
    #|        "timings": { "blocked": 1, "dns": 2, "connect": 3, "ssl": 1, "send": 4, "wait": 90, "receive": 19 },
    #|        "serverIPAddress": "93.184.216.34",
    #|        "connection": "443"
    #|      },
    #|      {
    #|        "pageref": "page_1",
    #|        "startedDateTime": "2026-08-11T00:00:00.060Z",
    #|        "time": 35,
    #|        "request": {
    #|          "method": "POST",
    #|          "url": "https://api.example.com/v1/events?debug=1",
    #|          "httpVersion": "HTTP/2",
    #|          "cookies": [],
    #|          "headers": [{ "name": "content-type", "value": "application/json" }],
    #|          "queryString": [{ "name": "debug", "value": "1" }],
    #|          "postData": { "mimeType": "application/json", "text": "{\"ok\":true}", "params": [] },
    #|          "headersSize": 132,
    #|          "bodySize": 11
    #|        },
    #|        "response": {
    #|          "status": 201,
    #|          "statusText": "Created",
    #|          "httpVersion": "HTTP/2",
    #|          "cookies": [],
    #|          "headers": [{ "name": "content-type", "value": "application/json" }],
    #|          "content": { "size": 32, "mimeType": "application/json", "text": "{}" },
    #|          "redirectURL": "",
    #|          "headersSize": 88,
    #|          "bodySize": 32
    #|        },
    #|        "cache": {},
    #|        "timings": { "blocked": 0, "dns": -1, "connect": -1, "ssl": -1, "send": 2, "wait": 20, "receive": 13 }
    #|      }
    #|    ]
    #|  }
    #|}
  )
}