No content requester

use courier = "courier"
use "promises"
use ssl = "ssl/net"

interface tag DeleteResultReceiver
  """
  Receives the result of an HTTP request that expects no response body (204 No
  Content). Used for DELETE and PUT operations like deleting a label or starring
  a gist.
  """
  be success()
  be failure(status: U16, response_body: String, message: String)

type DeletedOrError is (Deleted | RequestError)

actor DeletedResultReceiver
  """
  Bridges a DeleteResultReceiver to a Promise[DeletedOrError], fulfilling the
  promise with Deleted on success or a RequestError on failure.
  """
  let _p: Promise[DeletedOrError]

  new create(p: Promise[DeletedOrError]) =>
    _p = p

  be success() =>
    _p(Deleted)

  be failure(status: U16, response_body: String, message: String) =>
    _p(RequestError(status, response_body, message))

primitive Deleted
  """
  Marker type indicating a successful deletion or no-content operation.
  """

actor NoContentRequester is courier.HTTPClientConnectionActor
  """
  Issues an HTTP request that expects a 204 No Content response. Supports
  DELETE and PUT methods. On success, calls `receiver.success()`; on any other
  status or connection failure, calls `receiver.failure()` with details.
  """
  var _http: courier.HTTPClientConnection = courier.HTTPClientConnection.none()
  var _collector: courier.ResponseCollector = courier.ResponseCollector
  let _creds: Credentials
  let _receiver: DeleteResultReceiver
  let _method: courier.Method
  var _request_path: String = ""
  var _status: U16 = 0

  new delete(creds: Credentials,
    url: String,
    receiver: DeleteResultReceiver)
  =>
    """
    Issues an HTTP DELETE request expecting a 204 response.
    """
    _creds = creds
    _receiver = receiver
    _method = courier.DELETE
    _connect(url)

  new put(creds: Credentials,
    url: String,
    receiver: DeleteResultReceiver)
  =>
    """
    Issues an HTTP PUT request with no body, expecting a 204 response. Used for
    operations like starring a gist.
    """
    _creds = creds
    _receiver = receiver
    _method = courier.PUT
    _connect(url)

  fun ref _connect(url: String) =>
    match courier.URL.parse(url)
    | let parsed: courier.ParsedURL =>
      _request_path = parsed.request_path()
      let ctx = match _creds.ssl_ctx
      | let c: ssl.SSLContext val => c
      | None => SSLContextFactory()
      end
      let config = courier.ClientConnectionConfig
      _http = courier.HTTPClientConnection.ssl(
        _creds.auth, ctx, parsed.host, parsed.port,
        this, config)
    | let _: courier.URLParseError =>
      _fail("Unable to parse URL: " + url)
    end

  fun ref _http_client_connection(): courier.HTTPClientConnection =>
    _http

  fun ref on_connected() =>
    let hdrs = recover trn courier.Headers end
    hdrs.set("User-Agent", "Pony GitHub Rest API Client")
    hdrs.set("Accept", "application/vnd.github.v3+json")
    match _creds.token
    | let t: String =>
      (let n, let v) = courier.BearerAuth(t)
      hdrs.set(n, v)
    end
    hdrs.set("Content-Length", "0")
    let request = courier.HTTPRequest(
      _method,
      _request_path,
      consume hdrs)
    _http.send_request(request)

  fun ref on_response(response: courier.Response val) =>
    _status = response.status
    _collector = courier.ResponseCollector
    _collector.set_response(response)

  fun ref on_body_chunk(data: Array[U8] val) =>
    _collector.add_chunk(data)

  fun ref on_response_complete() =>
    _http.close()
    if _status == 204 then
      _receiver.success()
    else
      try
        let response = _collector.build()?
        let body_str = String.from_array(response.body)
        _receiver.failure(_status, consume body_str, "")
      else
        _receiver.failure(_status, "", "")
      end
    end

  fun ref on_connection_failure(reason: courier.ConnectionFailureReason) =>
    let msg = match \exhaustive\ reason
    | courier.ConnectionFailedDNS => "DNS resolution failed"
    | courier.ConnectionFailedTCP => "Unable to connect"
    | courier.ConnectionFailedSSL => "SSL handshake failed"
    | courier.ConnectionFailedTimeout => "Connection timed out"
    end
    _receiver.failure(0, "", consume msg)

  fun ref on_parse_error(err: courier.ParseError) =>
    _http.close()
    _receiver.failure(0, "", "HTTP parse error")

  be _fail(message: String) =>
    _receiver.failure(0, "", message)