//// awsJson special-float helpers. //// //// The AWS JSON protocols (`awsJson1_0`, `awsJson1_1`, plus other //// JSON-family wire formats) encode IEEE 754 specials as JSON strings: //// `"NaN"`, `"Infinity"`, `"-Infinity"`. Plain JSON numbers cover //// every finite float. //// //// **Why a wrapper type instead of raw `Float`:** Erlang's `float()` //// type cannot hold NaN or Infinity. `0.0/0.0` raises `badarith`; //// `binary_to_term` rejects the IEEE 754 NaN bit pattern with //// `badarg`; `<>` bit-syntax matching refuses to bind such //// values. So a faithful representation of an awsJson Float field on //// the BEAM target requires an explicit sum type — there's no way to //// pack `NaN` into an Erlang float. Rust's `f64` happens to support //// these natively, which is why aws-sdk-rust gets away with `Option`. //// //// Matches `aws-sdk-rust`'s `aws-smithy-types::Number::SpecialFloat` //// — same three string spellings, same case-sensitivity. import gleam/dynamic/decode.{type Decoder} import gleam/json.{type Json} /// Tagged float that can faithfully round-trip every IEEE 754 value /// reachable from JSON in the awsJson protocols. /// /// Generated code uses this for any field whose Smithy shape is /// `smithy.api#Float` or `smithy.api#Double`. User construction: /// /// FloatValue(1.5) /// NaN /// PosInfinity /// NegInfinity pub type SmithyFloat { FloatValue(Float) NaN PosInfinity NegInfinity } /// Encode a `SmithyFloat` as a JSON value. Finite values become JSON /// numbers; the three IEEE 754 specials become JSON strings, matching /// the awsJson wire spec. pub fn encode(v: SmithyFloat) -> Json { case v { FloatValue(f) -> json.float(f) NaN -> json.string("NaN") PosInfinity -> json.string("Infinity") NegInfinity -> json.string("-Infinity") } } /// Decoder for a `SmithyFloat`. Accepts plain JSON numbers and the /// three special-float strings. pub fn decoder() -> Decoder(SmithyFloat) { decode.one_of(decode.map(decode.float, FloatValue), [from_string()]) } fn from_string() -> Decoder(SmithyFloat) { decode.then(decode.string, fn(s) { case s { "NaN" -> decode.success(NaN) "Infinity" -> decode.success(PosInfinity) "-Infinity" -> decode.success(NegInfinity) _ -> decode.failure(NaN, "expected NaN / Infinity / -Infinity") } }) }