Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+235
View File
@@ -0,0 +1,235 @@
module jsonrpc
import json
import strings
import io
pub struct ServerConfig {
pub mut:
stream io.ReaderWriter
handler Handler @[required]
interceptors Interceptors
}
// Server represents a JSONRPC server that sends/receives data
// from a stream (an io.ReaderWriter) and uses Content-Length framing. :contentReference[oaicite:6]{index=6}
@[heap]
pub struct Server {
mut:
stream io.ReaderWriter
handler Handler @[required]
interceptors Interceptors
}
// new_server creates new `jsonrpc.Server` with `stream` to read/write,
// the `jsonrpc.Handler` to handle Requests/Responses and `interceptors`
pub fn new_server(cfg ServerConfig) Server {
return Server{
stream: cfg.stream
handler: cfg.handler
interceptors: cfg.interceptors
}
}
// respond reads bytes from stream, pass them to the `interceptors.encoded_request`,
// tries to decode into `jsonrpc.Request` and pass to `interceptors.request`
// and on fail it responds with `jsonrpc.parse_error` after that it calls handlers
// (batch requests are handled automatically as well as writing batch response)
// and passes recieved `jsonrpc.Response` into `interceptors.response` and the
// last step is to encode `jsonrpc.Response`, pass it into `interceptors.encoded_response`
// and write to stream
pub fn (mut s Server) respond() ! {
mut rw := s.writer()
mut rx := []u8{len: 4096}
bytes_read := s.stream.read(mut rx) or {
if err is io.Eof {
return
}
return err
}
if bytes_read == 0 {
return
}
intercept_encoded_request(s.interceptors.encoded_request, rx) or {
rw.write_error(response_error(error: err))
return err
}
req_str := rx.bytestr()
mut req_batch := []Request{}
match req_str[0].ascii_str() {
'[' {
req_batch = decode_batch_request(req_str) or {
rw.write_error(response_error(error: parse_error))
return err
}
rw.start_batch()
}
'{' {
req := decode_request(req_str) or {
rw.write_error(response_error(error: parse_error))
return err
}
req_batch.prepend(req)
}
else {
rw.write_error(response_error(error: parse_error))
return parse_error
}
}
for rq in req_batch {
rw.req_id = rq.id
intercept_request(s.interceptors.request, &rq) or {
rw.write_error(response_error(error: err))
return err
}
s.handler(&rq, mut rw)
}
if req_batch.len > 1 {
rw.close_batch()
}
}
fn (s &Server) writer() &ResponseWriter {
return &ResponseWriter{
writer: s.stream
sb: strings.new_builder(4096)
server: s
}
}
// start `Server` loop to operate on `stream` passed into constructor
// it calls `Server.respond()` method in loop
pub fn (mut s Server) start() {
for {
s.respond() or {
if err is io.Eof {
return
}
}
}
}
// Handler is the function called when `jsonrpc.Request` is
// decoded and `jsonrpc.Response` is required which is written
// into `jsonrpc.ResponseWriter`. Before returning from Handler
// either `wr.write()` or `wr.write_error()` must be called
// or the stream will stuck awaiting writing `jsonrpc.Response`
pub type Handler = fn (req &Request, mut wr ResponseWriter)
// Router is simple map of method names and their `Handler`s
pub struct Router {
mut:
methods map[string]Handler
}
// handle_jsonrpc must be passed into `Server` handler field to operate
// it simply tries to invoke registered methods and if none valid found
// writes `jsonrpc.method_not_found` error into `jsonrpc.ResponseWriter`
pub fn (r Router) handle_jsonrpc(req &Request, mut wr ResponseWriter) {
if h := r.methods[req.method] {
h(req, mut wr)
return
}
wr.write_error(method_not_found)
}
// register `handler` to operate when `method` found in incoming `jsonrpc.Request`
pub fn (mut r Router) register(method string, handler Handler) bool {
if method in r.methods {
return false
}
r.methods[method] = handler
return true
}
pub struct ResponseWriter {
mut:
sb strings.Builder
is_batch bool
server &Server
pub mut:
req_id string
writer io.ReaderWriter
}
fn (mut rw ResponseWriter) start_batch() {
rw.is_batch = true
rw.sb.write_string('[')
}
fn (mut rw ResponseWriter) close_batch() {
rw.is_batch = false
rw.sb.go_back(2)
rw.sb.write_string(']')
rw.close()
}
fn (mut rw ResponseWriter) close() {
intercept_encoded_response(rw.server.interceptors.encoded_response, rw.sb)
rw.writer.write(rw.sb) or {}
rw.sb.go_back_to(0)
}
// write payload into `jsonrpc.Response.result`.
// call when need to send data in response
pub fn (mut rw ResponseWriter) write[T](payload T) {
final_resp := Response{
id: rw.req_id
result: json.encode(payload)
}
intercept_response(rw.server.interceptors.response, final_resp)
if rw.req_id.len == 0 {
return
}
rw.sb.write_string(final_resp.encode())
if rw.is_batch == true {
rw.sb.write_string(', ')
return
}
rw.close()
}
// write_empty writes `jsonrpc.null` as response
pub fn (mut rw ResponseWriter) write_empty() {
rw.write[Null](null)
}
// write_error into the `jsonrpc.Response` of current request
pub fn (mut rw ResponseWriter) write_error(err IError) {
mut res_err := err
if err !is ResponseError {
if err.code() !in error_codes {
res_err = response_error(error: unknown_error)
} else {
res_err = response_error(error: err)
}
}
final_resp := Response{
id: rw.req_id
error: res_err as ResponseError
}
intercept_response(rw.server.interceptors.response, final_resp)
rw.sb.write_string(final_resp.encode())
if rw.is_batch {
rw.sb.write_string(', ')
return
}
rw.close()
}