Module: Kobako::RPC::Dispatcher
- Defined in:
- lib/kobako/rpc/dispatcher.rb
Overview
Pure-function dispatcher for guest-initiated RPC calls. Decodes a msgpack-encoded Request envelope, resolves the target object through the Server (path lookup or HandleTable lookup), invokes the method, and returns a msgpack-encoded Response envelope.
The module is stateless — all mutable state is threaded through the server argument so Dispatcher has no instance variables and no side effects beyond mutating the HandleTable via alloc when a non-wire- representable return value must be wrapped (docs/behavior.md B-14).
Entry point:
Kobako::RPC::Dispatcher.dispatch(request_bytes, server)
# => msgpack-encoded Response bytes (never raises)
Defined Under Namespace
Classes: DisconnectedTargetError, UndefinedTargetError
Class Method Summary collapse
-
.dispatch(request_bytes, server, handle_table) ⇒ Object
Dispatch a single RPC request and return the encoded response bytes.
-
.encode_caught_error(error) ⇒ Object
Map an error caught at the dispatch boundary to a
Response.errorenvelope. - .encode_error(type, message) ⇒ Object
-
.encode_ok(value, handle_table) ⇒ Object
Encode
valueas aResponse.okenvelope. -
.invoke(target, method, args, kwargs) ⇒ Object
Dispatch
methodontarget. -
.require_live_object!(id, handle_table) ⇒ Object
Resolve
idthrough the HandleTable, distinguishing the:disconnectedsentinel (E-14) from an unknown id (E-13). -
.resolve_arg(value, handle_table) ⇒ Object
docs/behavior.md B-16 — An Kobako::Handle arriving as a positional or keyword argument identifies a host-side object previously allocated by a prior RPC’s Handle wrap (B-14).
- .resolve_handle(handle, handle_table) ⇒ Object
- .resolve_path(path, server) ⇒ Object
-
.resolve_target(target, server, handle_table) ⇒ Object
Resolve a Request target to the Ruby object the Server (or HandleTable) holds.
-
.wrap_as_handle(value, handle_table) ⇒ Object
Allocate
valuein the Sandbox’s HandleTable and return aHandlethat the wire codec can carry (docs/behavior.md B-14).
Class Method Details
.dispatch(request_bytes, server, handle_table) ⇒ Object
Dispatch a single RPC request and return the encoded response bytes. Called by Kobako::RPC::Server#dispatch which is invoked from the Rust ext inside __kobako_dispatch. request_bytes is the msgpack-encoded Request envelope. server resolves path-based Member targets via #lookup. handle_table is the Sandbox’s HandleTable, injected separately so Dispatcher does not depend on Server publishing a Handle accessor — Handle is a Sandbox-level domain entity (B-19) and the dispatcher is its only consumer here. Always returns a binary String — never raises. Any failure during decode, lookup, or method invocation is reified as a Response.error envelope so the guest sees the failure as a normal RPC error rather than a wasm trap (docs/behavior.md B-12).
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/kobako/rpc/dispatcher.rb', line 47 def dispatch(request_bytes, server, handle_table) request = Kobako::RPC.decode_request(request_bytes) target = resolve_target(request.target, server, handle_table) args = request.args.map { |v| resolve_arg(v, handle_table) } kwargs = request.kwargs.transform_values { |v| resolve_arg(v, handle_table) } value = invoke(target, request.method_name, args, kwargs) encode_ok(value, handle_table) rescue StandardError => e encode_caught_error(e) end |
.encode_caught_error(error) ⇒ Object
Map an error caught at the dispatch boundary to a Response.error envelope. error is the StandardError caught by #dispatch‘s rescue. Returns a msgpack-encoded Response envelope (binary). Four error buckets (docs/behavior.md B-12): Kobako::Codec::Error → type=“runtime” (malformed RPC request); DisconnectedTargetError → type=“disconnected” (E-14); UndefinedTargetError → type=“undefined” (E-13); ArgumentError →type=“argument” (B-12 arity mismatch); everything else →type=“runtime”.
67 68 69 70 71 72 73 74 75 76 |
# File 'lib/kobako/rpc/dispatcher.rb', line 67 def encode_caught_error(error) case error when Kobako::Codec::Error then encode_error("runtime", "Sandbox received a malformed RPC request: #{error.}") when DisconnectedTargetError then encode_error("disconnected", error.) when UndefinedTargetError then encode_error("undefined", error.) when ArgumentError then encode_error("argument", error.) else encode_error("runtime", "#{error.class}: #{error.}") end end |
.encode_error(type, message) ⇒ Object
164 165 166 167 168 |
# File 'lib/kobako/rpc/dispatcher.rb', line 164 def encode_error(type, ) fault = Kobako::RPC::Fault.new(type: type, message: ) response = Kobako::RPC::Response.error(fault) Kobako::RPC.encode_response(response) end |
.encode_ok(value, handle_table) ⇒ Object
Encode value as a Response.ok envelope. When the value is not wire-representable per B-13[link:../../../docs/behavior.md]‘s type mapping, the UnsupportedType rescue routes it through the HandleTable via #wrap_as_handle and re-encodes with the Capability Handle in place (docs/behavior.md B-14). The happy path encodes exactly once.
149 150 151 152 153 154 |
# File 'lib/kobako/rpc/dispatcher.rb', line 149 def encode_ok(value, handle_table) response = Kobako::RPC::Response.ok(value) Kobako::RPC.encode_response(response) rescue Kobako::Codec::UnsupportedType encode_ok(wrap_as_handle(value, handle_table), handle_table) end |
.invoke(target, method, args, kwargs) ⇒ Object
Dispatch method on target. kwargs is already Symbol-keyed (the Envelope::Request invariant pins it). The empty-kwargs branch omits the ** splat so Ruby 3.x’s strict kwargs separation does not reject calls to no-kwarg methods when the wire carries the uniform empty-map shape.
83 84 85 86 87 88 89 |
# File 'lib/kobako/rpc/dispatcher.rb', line 83 def invoke(target, method, args, kwargs) if kwargs.empty? target.public_send(method.to_sym, *args) else target.public_send(method.to_sym, *args, **kwargs) end end |
.require_live_object!(id, handle_table) ⇒ Object
Resolve id through the HandleTable, distinguishing the :disconnected sentinel (E-14) from an unknown id (E-13).
134 135 136 137 138 139 140 141 |
# File 'lib/kobako/rpc/dispatcher.rb', line 134 def require_live_object!(id, handle_table) object = handle_table.fetch(id) raise DisconnectedTargetError, "Handle id #{id} is disconnected" if object == :disconnected object rescue Kobako::HandleTableError => e raise UndefinedTargetError, e. end |
.resolve_arg(value, handle_table) ⇒ Object
docs/behavior.md B-16 — An Kobako::Handle arriving as a positional or keyword argument identifies a host-side object previously allocated by a prior RPC’s Handle wrap (B-14). Resolve it back to the Ruby object before the dispatch reaches public_send. A Handle whose entry is the :disconnected sentinel (E-14) raises DisconnectedTargetError so the dispatcher emits a Response.error with type=“disconnected”.
97 98 99 100 101 102 103 104 |
# File 'lib/kobako/rpc/dispatcher.rb', line 97 def resolve_arg(value, handle_table) case value when Kobako::Handle require_live_object!(value.id, handle_table) else value end end |
.resolve_handle(handle, handle_table) ⇒ Object
128 129 130 |
# File 'lib/kobako/rpc/dispatcher.rb', line 128 def resolve_handle(handle, handle_table) require_live_object!(handle.id, handle_table) end |
.resolve_path(path, server) ⇒ Object
122 123 124 125 126 |
# File 'lib/kobako/rpc/dispatcher.rb', line 122 def resolve_path(path, server) server.lookup(path) rescue KeyError => e raise UndefinedTargetError, e. end |
.resolve_target(target, server, handle_table) ⇒ Object
Resolve a Request target to the Ruby object the Server (or HandleTable) holds. String targets go through the Server; Handle targets (ext 0x01) go through the HandleTable.
Target type is already validated by RPC.decode_request before this method is reached, so no else-branch is needed here —the wire layer is the system boundary that enforces the invariant.
113 114 115 116 117 118 119 120 |
# File 'lib/kobako/rpc/dispatcher.rb', line 113 def resolve_target(target, server, handle_table) case target when String resolve_path(target, server) when Kobako::Handle resolve_handle(target, handle_table) end end |
.wrap_as_handle(value, handle_table) ⇒ Object
Allocate value in the Sandbox’s HandleTable and return a Handle that the wire codec can carry (docs/behavior.md B-14). Used as the fallback path of #encode_ok when value has no wire representation.
160 161 162 |
# File 'lib/kobako/rpc/dispatcher.rb', line 160 def wrap_as_handle(value, handle_table) handle_table.alloc(value) end |