Current section
Files
Jump to
Current section
Files
src/pollux.gleam
import gleam/dynamic
import gleam/dynamic/decode
import gleam/int
import gleam/json
import gleam/list
import gleam/option.{None, Some}
import oas/generator/utils
import pollux/decodex
const jsonrpc = "2.0"
pub type Id {
StringId(String)
NumberId(Int)
}
fn id_decoder() {
decode.one_of(decode.string |> decode.map(StringId), [
decode.int |> decode.map(NumberId),
])
}
fn id_encode(id) {
case id {
StringId(string) -> json.string(string)
NumberId(number) -> json.int(number)
}
}
pub fn id_to_string(id) {
case id {
NumberId(id) -> int.to_string(id)
StringId(id) -> id
}
}
// Notifications have no id
pub type Request(r, n) {
Request(version: String, id: Id, value: r)
Notification(version: String, value: n)
}
pub fn request(id, value) {
Request(version: jsonrpc, id:, value:)
}
pub fn notification(value) {
Notification(version: jsonrpc, value:)
}
pub fn request_decoder(request_decoders, notification_decoders, zero) {
use version <- decode.field("jsonrpc", decode.string)
use id <- decodex.optional_field("id", id_decoder())
use method <- decode.field("method", decode.string)
case id {
Some(id) ->
case list.key_find(request_decoders, method) {
Ok(decoder) -> {
use maybe <- decodex.optional_field("params", decodex.any())
// In mcp there are optional fields
let params = option.unwrap(maybe, dynamic.properties([]))
case decode.run(params, decoder) {
Ok(value) -> decode.success(Request(version:, id:, value:))
Error(_reason) ->
decode.failure(Notification(version, zero), "params")
}
}
Error(Nil) ->
decode.failure(
Notification(version, zero),
"missing decoder " <> method,
)
}
None ->
case list.key_find(notification_decoders, method) {
Ok(decoder) -> {
use maybe <- decodex.optional_field("params", decodex.any())
// In mcp there are optional fields
let params = option.unwrap(maybe, dynamic.properties([]))
case decode.run(params, decoder) {
Ok(value) -> decode.success(Notification(version:, value:))
Error(_reason) ->
decode.failure(Notification(version, zero), "params")
}
}
Error(Nil) ->
decode.failure(
Notification(version, zero),
"missing decoder " <> method,
)
}
}
}
pub fn request_encode(request, request_encode, notification_encode) {
case request {
Request(version:, id:, value:) -> {
let #(method, params) = request_encode(value)
json.object([
#("jsonrpc", json.string(version)),
#("id", id_encode(id)),
#("method", json.string(method)),
..case params {
None -> []
Some(params) -> [
#("params", params),
]
}
])
}
Notification(version:, value:) -> {
let #(method, params) = notification_encode(value)
json.object([
#("jsonrpc", json.string(version)),
#("method", json.string(method)),
..case params {
None -> []
Some(params) -> [
#("params", params),
]
}
])
}
}
}
pub type ErrorObject {
ErrorObject(code: Int, message: String, data: utils.Any)
}
fn error_object_decoder() {
use code <- decode.field("code", decode.int)
use message <- decode.field("message", decode.string)
use data <- decode.optional_field("data", utils.Null, utils.any_decoder())
decode.success(ErrorObject(code:, message:, data:))
}
fn error_object_encode(obj) {
let ErrorObject(code:, message:, data:) = obj
json.object([
#("code", json.int(code)),
#("message", json.string(message)),
#("data", utils.any_to_json(data)),
])
}
pub type Response(t) {
Response(version: String, id: Id, return: Result(t, ErrorObject))
}
pub fn response(id, result) {
Response(jsonrpc, id, result)
}
pub fn result(id, result) {
Response(jsonrpc, id, Ok(result))
}
pub fn response_decoder(return_decoder) {
use version <- decode.field("jsonrpc", decode.string)
use id <- decode.field("id", id_decoder())
use return <- decode.then(
decode.one_of(
{
use value <- decode.field("result", return_decoder)
decode.success(Ok(value))
},
[
{
use reason <- decode.field("error", error_object_decoder())
decode.success(Error(reason))
},
],
),
)
decode.success(Response(version:, id:, return:))
}
pub fn response_encode(response, return_encode) {
let Response(version:, id:, return:) = response
json.object([
#("jsonrpc", json.string(version)),
#("id", id_encode(id)),
case return {
Ok(value) -> #("result", return_encode(value))
Error(obj) -> #("error", error_object_encode(obj))
},
])
}