Current section
Files
Jump to
Current section
Files
src/dagger/types.gleam
import gleam/dynamic.{type Dynamic}
import gleam/dynamic/decode
import gleam/float
import gleam/int
import gleam/json
import gleam/list
import gleam/string
// --- TIPI CORE PER IL CLIENT E ERRORI ---
pub type Client {
Client(endpoint: String, token: String)
}
pub type QueryError {
NetworkError(String)
DecodingError(String)
ExecutionError(List(GraphQLError))
}
pub type GraphQLError {
GraphQLError(message: String, path: List(Dynamic))
}
pub type Try(a) =
Result(a, QueryError)
// --- LA MONADE DAGGER ---
pub type DaggerOp(a) {
Pure(a)
/// Fetch memorizza i campi, il decoder per il risultato e la continuazione.
Fetch(
fields: List(Field),
decoder: decode.Decoder(Dynamic),
next: fn(Dynamic) -> DaggerOp(a),
)
}
pub fn pure(val: a) -> DaggerOp(a) {
Pure(val)
}
pub fn bind(op: DaggerOp(a), callback: fn(a) -> DaggerOp(b)) -> DaggerOp(b) {
case op {
Pure(val) -> callback(val)
Fetch(fields, decoder, next) -> {
Fetch(fields: fields, decoder: decoder, next: fn(dyn) {
bind(next(dyn), callback)
})
}
}
}
pub fn continue(op: DaggerOp(a), callback: fn(a) -> DaggerOp(b)) -> DaggerOp(b) {
case op {
Pure(a) -> callback(a)
Fetch(fields, decoder, next) ->
Fetch(fields, decoder, fn(dyn) { continue(next(dyn), callback) })
}
}
pub fn get_query(op: DaggerOp(List(Field))) -> List(Field) {
case op {
Pure(q) -> q
Fetch(q, _, _) -> q
}
}
// --- AST GRAPHQL ---
pub type Field {
Field(name: String, args: List(#(String, Value)), subfields: List(Field))
}
pub type Value {
GString(String)
GInt(Int)
GFloat(Float)
GBool(Bool)
GList(List(Value))
GObject(List(#(String, Value)))
GNull
/// GDeferred permette di annidare un'operazione che restituisce una lista di campi
/// (ovvero un oggetto Dagger come Directory o Container) come argomento.
GDeferred(DaggerOp(List(Field)))
}
// --- UTILITY PER SERIALIZZAZIONE ---
pub fn make_path(fields: List(Field)) -> List(String) {
let names = list.map(fields, fn(f) { f.name })
["data", ..names]
}
pub fn serialize(fields: List(Field)) -> String {
let nested_fields = nest(fields)
"{ " <> list.map(nested_fields, serialize_field) |> string.join(" ") <> " }"
}
fn serialize_field(field: Field) -> String {
let args = case field.args {
[] -> ""
args -> "(" <> serialize_args(args) <> ")"
}
let subs = case field.subfields {
[] -> ""
subs -> " { " <> list.map(subs, serialize_field) |> string.join(" ") <> " }"
}
field.name <> args <> subs
}
fn serialize_args(args: List(#(String, Value))) -> String {
args
|> list.map(fn(arg) { arg.0 <> ": " <> serialize_value(arg.1) })
|> string.join(", ")
}
fn serialize_value(value: Value) -> String {
case value {
GString(s) -> json.to_string(json.string(s))
// gestisce tutti gli escape
GInt(i) -> int.to_string(i)
GFloat(f) -> float.to_string(f)
GBool(True) -> "true"
GBool(False) -> "false"
GList(items) ->
"[" <> list.map(items, serialize_value) |> string.join(", ") <> "]"
GObject(fields) ->
"{"
<> list.map(fields, fn(f) { f.0 <> ": " <> serialize_value(f.1) })
|> string.join(", ")
<> "}"
GNull -> "null"
// Nota: GDeferred non deve essere serializzato direttamente.
// L'interprete deve risolverlo in GString(id) prima della serializzazione finale.
GDeferred(op) -> {
let fields = get_query(op)
// produce: setSecret(name: "my_token", plaintext: "***")
// NON: { setSecret(...) }
case nest(fields) {
[field] -> serialize_field(field)
// singolo campo radice
_ -> panic as "GDeferred deve avere un solo campo radice"
}
}
}
}
pub fn nest(fields: List(Field)) -> List(Field) {
case list.reverse(fields) {
[] -> []
[last, ..rest] -> {
list.fold(rest, last, fn(acc, f) {
Field(name: f.name, args: f.args, subfields: [acc])
})
|> list.wrap
}
}
}