///|
fn last_seen_id_to_json(id : Int64?) -> Json {
match id {
Some(n) => Json::number(n.to_double(), repr=n.to_string())
None => Json::null()
}
}
///|
fn id_to_json(id : Int64) -> Json {
Json::number(id.to_double(), repr=id.to_string())
}
///|
fn parse_optional_int64(
m : Map[String, Json],
key : String,
path : @json.JsonPath,
) -> Int64? raise @json.JsonDecodeError {
match m.get(key) {
None | Some(Null) => None
Some(Number(n, ..)) => Some(n.to_int64())
Some(String(s)) =>
Some(
@string.parse_int64(s) catch {
_ => raise @json.JsonDecodeError((path, "invalid int64 for '\{key}'"))
},
)
_ => raise @json.JsonDecodeError((path, "invalid type for '\{key}'"))
}
}
///|
pub impl ToJson for SysMessage with to_json(self) {
match self {
Register(task_uuid~, hostname~, cwd~) =>
{
"ch": "sys",
"type": "register",
"task_uuid": task_uuid.to_string(),
"hostname": hostname,
"cwd": cwd,
}
Registered(task_uuid~) =>
{ "ch": "sys", "type": "registered", "task_uuid": task_uuid.to_string() }
RegisterRejected(reason~) =>
{ "ch": "sys", "type": "register_rejected", "reason": reason.to_wire() }
TaskExited(exit_code~) =>
{ "ch": "sys", "type": "task_exited", "exit_code": exit_code }
Connect(task_uuid~, last_seen_id~) =>
{
"ch": "sys",
"type": "connect",
"task_uuid": task_uuid.to_string(),
"last_seen_id": last_seen_id_to_json(last_seen_id),
}
Connected => { "ch": "sys", "type": "connected" }
TaskOnline => { "ch": "sys", "type": "task_online" }
NewClient(client_id~, last_seen_id~) =>
{
"ch": "sys",
"type": "new_client",
"client_id": client_id,
"last_seen_id": last_seen_id_to_json(last_seen_id),
}
ClientDisconnected(client_id~) =>
{ "ch": "sys", "type": "client_disconnected", "client_id": client_id }
Error(code) => { "ch": "sys", "type": "error", "code": code.to_wire() }
AuthError(message~) =>
{ "ch": "sys", "type": "auth_error", "message": message }
}
}
///|
pub impl @json.FromJson for SysMessage with from_json(json, path) {
guard json is Object(fields) else {
raise @json.JsonDecodeError((path, "expected JSON object"))
}
let t = match fields.get("type") {
Some(String(s)) => s
_ => raise @json.JsonDecodeError((path, "missing field 'type'"))
}
match t {
"register" => {
guard fields.get("task_uuid") is Some(String(uuid_str)) else {
raise @json.JsonDecodeError((path, "missing field 'task_uuid'"))
}
let task_uuid = @uuid.from_hex(uuid_str) catch {
_ =>
raise @json.JsonDecodeError((path, "invalid task_uuid: \{uuid_str}"))
}
let hostname = match fields.get("hostname") {
Some(String(s)) => s
_ => ""
}
let cwd = match fields.get("cwd") {
Some(String(s)) => s
_ => ""
}
Register(task_uuid~, hostname~, cwd~)
}
"registered" => {
guard fields.get("task_uuid") is Some(String(uuid_str)) else {
raise @json.JsonDecodeError((path, "missing field 'task_uuid'"))
}
let task_uuid = @uuid.from_hex(uuid_str) catch {
_ =>
raise @json.JsonDecodeError((path, "invalid task_uuid: \{uuid_str}"))
}
Registered(task_uuid~)
}
"register_rejected" => {
guard fields.get("reason") is Some(String(reason_str)) else {
raise @json.JsonDecodeError((path, "missing field 'reason'"))
}
let reason = match RegisterRejectReason::from_wire(reason_str) {
Some(r) => r
None =>
raise @json.JsonDecodeError(
(path, "unknown register_rejected reason: \{reason_str}"),
)
}
RegisterRejected(reason~)
}
"task_exited" => {
guard fields.get("exit_code") is Some(Number(n, ..)) else {
raise @json.JsonDecodeError((path, "missing field 'exit_code'"))
}
TaskExited(exit_code=n.to_int())
}
"connect" => {
guard fields.get("task_uuid") is Some(String(uuid_str)) else {
raise @json.JsonDecodeError((path, "missing field 'task_uuid'"))
}
let task_uuid = @uuid.from_hex(uuid_str) catch {
_ =>
raise @json.JsonDecodeError((path, "invalid task_uuid: \{uuid_str}"))
}
let last_seen_id = parse_optional_int64(fields, "last_seen_id", path)
Connect(task_uuid~, last_seen_id~)
}
"connected" => Connected
"task_online" => TaskOnline
"new_client" => {
guard fields.get("client_id") is Some(String(id)) else {
raise @json.JsonDecodeError((path, "missing field 'client_id'"))
}
let last_seen_id = parse_optional_int64(fields, "last_seen_id", path)
NewClient(client_id=id, last_seen_id~)
}
"client_disconnected" => {
guard fields.get("client_id") is Some(String(id)) else {
raise @json.JsonDecodeError((path, "missing field 'client_id'"))
}
ClientDisconnected(client_id=id)
}
"error" => {
guard fields.get("code") is Some(String(code_str)) else {
raise @json.JsonDecodeError((path, "missing field 'code'"))
}
let code = match ErrorCode::from_wire(code_str) {
Some(c) => c
None =>
raise @json.JsonDecodeError((path, "unknown error code: \{code_str}"))
}
Error(code)
}
"auth_error" => {
guard fields.get("message") is Some(String(msg)) else {
raise @json.JsonDecodeError((path, "missing field 'message'"))
}
AuthError(message=msg)
}
_ => raise @json.JsonDecodeError((path, "unknown sys message type: \{t}"))
}
}
///|
pub impl ToJson for TermMessage with to_json(self) {
match self {
Data(client_id~, id~, data~) =>
{
"ch": "t",
"type": "data",
"client_id": client_id,
"id": id_to_json(id),
"data": data,
}
Exited(client_id~, id~) =>
{
"ch": "t",
"type": "exited",
"client_id": client_id,
"id": id_to_json(id),
}
}
}
///|
pub impl @json.FromJson for TermMessage with from_json(json, path) {
guard json is Object(fields) else {
raise @json.JsonDecodeError((path, "expected JSON object"))
}
let t = match fields.get("type") {
Some(String(s)) => s
_ => raise @json.JsonDecodeError((path, "missing field 'type'"))
}
match t {
"data" => {
let client_id = match fields.get("client_id") {
Some(String(s)) => s
_ => ""
}
let id = parse_optional_int64(fields, "id", path).unwrap_or(0L)
guard fields.get("data") is Some(String(data)) else {
raise @json.JsonDecodeError((path, "missing field 'data'"))
}
Data(client_id~, id~, data~)
}
"exited" => {
let client_id = match fields.get("client_id") {
Some(String(s)) => s
_ => ""
}
let id = parse_optional_int64(fields, "id", path).unwrap_or(0L)
Exited(client_id~, id~)
}
_ => raise @json.JsonDecodeError((path, "unknown term message type: \{t}"))
}
}
///|
pub impl ToJson for Message with to_json(self) {
match self {
Sys(msg) => msg.to_json()
Fs(msg) => msg.to_json()
Term(msg) => msg.to_json()
Codex(client_id~, data~) =>
{ "ch": "codex", "client_id": client_id, "data": data }
}
}
///|
pub impl @json.FromJson for Message with from_json(json, path) {
guard json is Object(fields) else {
raise @json.JsonDecodeError((path, "expected JSON object"))
}
let ch = match fields.get("ch") {
Some(String(c)) => c
_ => "sys"
}
match ch {
"sys" => Sys(@json.from_json(json, path~))
"t" => Term(@json.from_json(json, path~))
"fs" => Fs(@json.from_json(json, path~))
"codex" => {
let client_id = match fields.get("client_id") {
Some(String(s)) => s
_ => ""
}
guard fields.get("data") is Some(String(data)) else {
raise @json.JsonDecodeError((path, "missing field 'data'"))
}
Codex(client_id~, data~)
}
_ => raise @json.JsonDecodeError((path, "unknown channel: \{ch}"))
}
}