pagination.ry

module pagination
  purpose: Fetch every page of a cursor-paginated API and count customers per country.

import std.console
import std.http exposing Url, HttpError
import std.json exposing JsonError

public type Customer
  purpose: One customer record as the API returns it.
  has id: Integer
  has name: Text
  has country: Text
  can FromJson
end

type Page
  has items: List of Customer
  has next_cursor: maybe Text as "nextCursor"
  can FromJson
end

public function fetch_all(base_url: Url)
  returns List of Customer
  or fails with HttpError or JsonError
  needs network.http
  purpose: Follow next_cursor from page to page until the API returns no cursor.
  tags: http, pagination

  let mutable customers: List of Customer be []
  let mutable cursor: maybe Text be nothing
  let mutable has_more be true
  repeat until not has_more
    let url be
      match cursor
        when some(token) then Url("{base_url}?cursor={token}")
        when nothing then base_url
      end
    let response be http.get(url) otherwise fail
    let page: Page be json.parse(response.body) otherwise fail
    change customers to customers.append_all(page.items)
    change cursor to page.next_cursor
    change has_more to page.next_cursor is not nothing
  end
  return customers
end

public function main() or fails with HttpError or JsonError needs console, network.http
  purpose: Print how many customers each country has, largest first.

  let customers be fetch_all(Url("https://api.example.com/customers")) otherwise fail
  let by_country be for each customer in customers group by customer.country
  for each country, members in by_country sorted by members.length() descending
    console.print("{country}: {members.length()}")
  end
end

test "every page is fetched" needs network.http replays "fixtures/pagination.json"
  let customers be fetch_all(Url("https://api.example.com/customers")) otherwise fail
  check customers.length() is 3
end