Current section

Files

Jump to
oaspec src oaspec internal util http.gleam
Raw

src/oaspec/internal/util/http.gleam

import gleam/int
import gleam/list
/// Type-safe HTTP status code representation.
/// Replaces raw String status codes throughout the codebase.
pub type HttpStatusCode {
/// An exact HTTP status code, e.g. 200, 404, 500.
Status(Int)
/// A wildcard range, e.g. 2XX is StatusRange(2), 4XX is StatusRange(4).
StatusRange(Int)
/// The OpenAPI "default" catch-all response.
DefaultStatus
}
/// Parse a raw status code string from an OpenAPI spec into an HttpStatusCode.
/// Returns Error(Nil) for unrecognisable strings.
pub fn parse_status_code(code: String) -> Result(HttpStatusCode, Nil) {
case code {
"default" -> Ok(DefaultStatus)
"1XX" | "1xx" -> Ok(StatusRange(1))
"2XX" | "2xx" -> Ok(StatusRange(2))
"3XX" | "3xx" -> Ok(StatusRange(3))
"4XX" | "4xx" -> Ok(StatusRange(4))
"5XX" | "5xx" -> Ok(StatusRange(5))
_ ->
case int.parse(code) {
Ok(n) -> Ok(Status(n))
Error(_) -> Error(Nil)
}
}
}
/// Convert an HttpStatusCode back to its canonical string form.
pub fn status_code_to_string(code: HttpStatusCode) -> String {
case code {
Status(n) -> int.to_string(n)
StatusRange(n) -> int.to_string(n) <> "XX"
DefaultStatus -> "default"
}
}
/// Shared HTTP status code utilities for code generation.
/// Get a human-readable suffix for a given HTTP status code.
/// Used to build anonymous type names like "ListPetsResponseOk".
pub fn status_code_suffix(code: HttpStatusCode) -> String {
case code {
Status(200) -> "Ok"
Status(201) -> "Created"
Status(204) -> "NoContent"
Status(400) -> "BadRequest"
Status(401) -> "Unauthorized"
Status(403) -> "Forbidden"
Status(404) -> "NotFound"
Status(409) -> "Conflict"
Status(422) -> "UnprocessableEntity"
Status(500) -> "InternalServerError"
Status(n) -> "Status" <> int.to_string(n)
StatusRange(1) -> "Status1xx"
StatusRange(2) -> "Status2xx"
StatusRange(3) -> "Status3xx"
StatusRange(4) -> "Status4xx"
StatusRange(5) -> "Status5xx"
StatusRange(n) -> "Status" <> int.to_string(n) <> "xx"
DefaultStatus -> "Default"
}
}
/// Convert an HttpStatusCode to a valid Gleam case pattern.
/// Exact codes become integer literals, range codes become guard expressions,
/// and DefaultStatus becomes "_" (catch-all).
pub fn status_code_to_int_pattern(code: HttpStatusCode) -> String {
case code {
DefaultStatus -> "_"
StatusRange(n) -> {
let low = int.to_string(n * 100)
let high = int.to_string(n * 100 + 99)
"status if status >= " <> low <> " && status <= " <> high
}
Status(n) -> int.to_string(n)
}
}
/// Sort response entries so that exact codes come before ranges,
/// and ranges come before the default catch-all. This ensures
/// correct pattern matching order in generated case expressions.
pub fn sort_response_entries(
entries: List(#(HttpStatusCode, a)),
) -> List(#(HttpStatusCode, a)) {
list.sort(entries, fn(a, b) {
int.compare(status_sort_priority(a.0), status_sort_priority(b.0))
})
}
/// Convert an HttpStatusCode to an integer string for use in ServerResponse.
/// Range codes and DefaultStatus map to a representative status code.
pub fn status_code_to_int(code: HttpStatusCode) -> String {
case code {
DefaultStatus -> "500"
StatusRange(n) -> int.to_string(n * 100)
Status(n) -> int.to_string(n)
}
}
fn status_sort_priority(code: HttpStatusCode) -> Int {
case code {
DefaultStatus -> 9999
StatusRange(n) -> n * 100 + 1000
Status(n) -> n
}
}