Skip to main content

src/aws/internal/codec/json_document.gleam

//// `smithy.api#Document` codec. Documents are "any JSON value" — the
//// service doesn't constrain the shape. We carry them as `json.Json`
//// so round-trips are byte-stable: decode a document field, re-encode
//// it, and you get the same JSON back out.
////
//// Backed by FFI because `gleam_json.Json` is opaque and there's no
//// public constructor for "raw JSON". On the Erlang side `Json` is
//// just iodata, so `raw/1` is a no-op cast.

import gleam/dynamic.{type Dynamic}
import gleam/dynamic/decode
import gleam/json.{type Json}

/// Take a dynamic value (whatever `decode.dynamic` produced) and turn
/// it into a `json.Json`. Round-trips through `aws_ffi:encode_dynamic_to_json`,
/// then casts the resulting binary back to the opaque `Json` type.
pub fn from_dynamic(d: Dynamic) -> Json {
  case encode_dynamic_ffi(d) {
    Ok(bin) -> raw(bin)
    Error(_) -> json.null()
  }
}

/// Decoder for a Document field. Always succeeds — Documents accept
/// any JSON shape including nulls, so failure is never the right
/// answer at this layer.
pub fn decoder() -> decode.Decoder(Json) {
  decode.then(decode.dynamic, fn(d) { decode.success(from_dynamic(d)) })
}

@external(erlang, "aws_ffi", "encode_dynamic_to_json")
fn encode_dynamic_ffi(d: Dynamic) -> Result(String, Nil)

@external(erlang, "gleam_json_ffi", "json_to_string")
fn raw(bin: String) -> Json