///|
/// A Request is a message sent to a peer to request behavior.
/// If it has an ID then it is a call and therefore requests a response,
/// otherwise it is a notification which receives no response.
pub(all) struct Request {
  // id of this request, used to tie the Response back to the request.
  // This will be None for notifications.
  id : ID?
  // method_ is a String containing the method name to invoke.
  method_ : String
  // params is either a struct or an array with the parameters of the method.
  params : Json
} derive(Debug, Eq)

///|
pub impl Show for Request with fn output(self, logger) {
  logger.write_string(
    (
      $|{id: \{Repr(self.id)}, method_: \{self.method_.escape(quote=true)}, params: \{Repr(self.params).to_string()}}
    ),
  )
}

///|
test "Request show interface" {
  let req1 = {
    id: Some(ID::number(42)),
    method_: "foo".to_string(),
    params: { "a": "b" }.to_json(),
  }
  inspect(
    req1,
    content=(
      #|{id: Some(Number(42)), method_: "foo", params: Object({ "a": String("b") })}
    ),
  )
}

///|
pub impl ToJson for Request with fn to_json(self) {
  let obj = { "jsonrpc": "2.0".to_json(), "method": self.method_.to_json() }
  if !(self.params is Null) {
    obj["params"] = self.params
  }
  match self.id {
    Some(String(s)) => obj["id"] = s.to_json()
    Some(Number(n)) => obj["id"] = n.to_double().to_json()
    None => ()
  }
  obj.to_json()
}

///|
pub impl @json.FromJson for Request with fn from_json(json, path) {
  guard json is Object(obj) else {
    raise @json.JsonDecodeError((path, "expected object"))
  }
  match obj {
    // with params
    {
      "jsonrpc": String("2.0"),
      "id": Number(id, ..),
      "method": String(method_),
      "params": params,
      ..
    } => { id: Some(Number(id.to_int64())), method_, params }
    {
      "jsonrpc": String("2.0"),
      "id": String(id),
      "method": String(method_),
      "params": params,
      ..
    } => { id: Some(String(id)), method_, params }
    {
      "jsonrpc": String("2.0"),
      "method": String(method_),
      "params": params,
      ..
    } => { id: None, method_, params }
    // without params
    {
      "jsonrpc": String("2.0"),
      "id": Number(id, ..),
      "method": String(method_),
      ..
    } => { id: Some(Number(id.to_int64())), method_, params: Json::null() }
    {
      "jsonrpc": String("2.0"),
      "id": String(id),
      "method": String(method_),
      ..
    } => { id: Some(String(id)), method_, params: Json::null() }
    { "jsonrpc": String("2.0"), "method": String(method_), .. } =>
      { id: None, method_, params: Json::null() }
    _ =>
      raise @json.JsonDecodeError((path, "expected jsonrpc and method fields"))
  }
}