diff options
| author | cashmere <cashmere@autistici.org> | 2026-01-21 05:34:19 +0100 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-01-21 05:34:19 +0100 |
| commit | 21ca8efb5fe1bf4577e34f735e5aea0f767343b4 (patch) | |
| tree | d0743ff4651b72841c083e35fb76ddb15d019186 /tramp-rpc/lisp/tramp-rpc-protocol.el | |
| parent | 1d255ed82ceaac7561869c46f2183cea9eae2768 (diff) | |
| download | emacs-21ca8efb5fe1bf4577e34f735e5aea0f767343b4.tar.gz | |
sync
Diffstat (limited to 'tramp-rpc/lisp/tramp-rpc-protocol.el')
| -rw-r--r-- | tramp-rpc/lisp/tramp-rpc-protocol.el | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/tramp-rpc/lisp/tramp-rpc-protocol.el b/tramp-rpc/lisp/tramp-rpc-protocol.el new file mode 100644 index 0000000..59f60f1 --- /dev/null +++ b/tramp-rpc/lisp/tramp-rpc-protocol.el @@ -0,0 +1,137 @@ +;;; tramp-rpc-protocol.el --- JSON-RPC protocol for TRAMP-RPC -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Arthur Heymans <arthur@aheymans.xyz> + +;; Author: Arthur Heymans <arthur@aheymans.xyz> +;; Keywords: comm, processes +;; Package-Requires: ((emacs "30.1")) + +;; This file is part of tramp-rpc. + +;; tramp-rpc is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;;; Commentary: + +;; This file provides the JSON-RPC 2.0 protocol implementation for +;; communicating with the tramp-rpc-server binary. + +;;; Code: + +(require 'json) + +(defvar tramp-rpc-protocol--request-id 0 + "Counter for generating unique request IDs.") + +(defun tramp-rpc-protocol--next-id () + "Generate the next request ID." + (cl-incf tramp-rpc-protocol--request-id)) + +(defun tramp-rpc-protocol-encode-request (method params) + "Encode a JSON-RPC 2.0 request for METHOD with PARAMS. +Returns a JSON string." + (let ((request `((jsonrpc . "2.0") + (id . ,(tramp-rpc-protocol--next-id)) + (method . ,method) + (params . ,params)))) + (json-encode request))) + +(defun tramp-rpc-protocol-encode-request-with-id (method params) + "Encode a JSON-RPC 2.0 request for METHOD with PARAMS. +Returns a cons cell (ID . JSON-STRING) for pipelining support." + (let* ((id (tramp-rpc-protocol--next-id)) + (request `((jsonrpc . "2.0") + (id . ,id) + (method . ,method) + (params . ,params)))) + (cons id (json-encode request)))) + +(defun tramp-rpc-protocol-decode-response (json-string) + "Decode a JSON-RPC 2.0 response from JSON-STRING. +Returns a plist with :id, :result, and :error keys." + (let* ((response (json-parse-string json-string + :object-type 'alist + :false-object nil + :null-object nil)) + (id (alist-get 'id response)) + (result (alist-get 'result response)) + (error-obj (alist-get 'error response))) + (list :id id + :result result + :error (when error-obj + (list :code (alist-get 'code error-obj) + :message (alist-get 'message error-obj) + :data (alist-get 'data error-obj)))))) + +(defun tramp-rpc-protocol-error-p (response) + "Return non-nil if RESPONSE contains an error." + (plist-get response :error)) + +(defun tramp-rpc-protocol-error-message (response) + "Extract the error message from RESPONSE." + (plist-get (plist-get response :error) :message)) + +(defun tramp-rpc-protocol-error-code (response) + "Extract the error code from RESPONSE." + (plist-get (plist-get response :error) :code)) + +;; Error codes +(defconst tramp-rpc-protocol-error-parse -32700) +(defconst tramp-rpc-protocol-error-invalid-request -32600) +(defconst tramp-rpc-protocol-error-method-not-found -32601) +(defconst tramp-rpc-protocol-error-invalid-params -32602) +(defconst tramp-rpc-protocol-error-internal -32603) +(defconst tramp-rpc-protocol-error-file-not-found -32001) +(defconst tramp-rpc-protocol-error-permission-denied -32002) +(defconst tramp-rpc-protocol-error-io -32003) +(defconst tramp-rpc-protocol-error-process -32004) + +;; ============================================================================ +;; Batch request support +;; ============================================================================ + +(defun tramp-rpc-protocol-encode-batch-request (requests) + "Encode a batch request containing multiple REQUESTS. +REQUESTS is a list of (METHOD . PARAMS) cons cells. +Returns a JSON string for a single RPC call to the batch method." + (let ((batch-requests + (mapcar (lambda (req) + `((method . ,(car req)) + (params . ,(cdr req)))) + requests))) + (tramp-rpc-protocol-encode-request + "batch" + `((requests . ,(vconcat batch-requests)))))) + +(defun tramp-rpc-protocol-encode-batch-request-with-id (requests) + "Encode a batch request containing multiple REQUESTS. +REQUESTS is a list of (METHOD . PARAMS) cons cells. +Returns a cons cell (ID . JSON-STRING) for ID tracking." + (let ((batch-requests + (mapcar (lambda (req) + `((method . ,(car req)) + (params . ,(cdr req)))) + requests))) + (tramp-rpc-protocol-encode-request-with-id + "batch" + `((requests . ,(vconcat batch-requests)))))) + +(defun tramp-rpc-protocol-decode-batch-response (response) + "Decode a batch response into a list of individual results. +RESPONSE is the decoded response plist from +`tramp-rpc-protocol-decode-response'. +Returns a list where each element is either: + - The result value (if successful) + - A plist (:error CODE :message MSG) if that sub-request failed." + (let ((results-array (alist-get 'results (plist-get response :result)))) + (mapcar (lambda (result-obj) + (if-let ((error-obj (alist-get 'error result-obj))) + (list :error (alist-get 'code error-obj) + :message (alist-get 'message error-obj)) + (alist-get 'result result-obj))) + results-array))) + +(provide 'tramp-rpc-protocol) +;;; tramp-rpc-protocol.el ends here |
