Skip to content

Commit c6fcb4d

Browse files
committed
JSON-RPC: boxed standard drops via _instantiate markers
DropProxy.wrap now detects standard drop instances and sends them as _instantiate markers instead of _rpc_drop markers. The server creates its own drop instances from the markers — no RPC callbacks needed. _instantiate marker format: {_instantiate: BooleanDrop, params: {value: true}} Server-side instantiate_standard_drop creates the drop natively. unwrap_environment and unwrap_value handle _instantiate markers alongside existing _rpc_drop and _ruby_type markers. 27/28 drops.yml specs pass over JSON-RPC (1 failure: drop_error_raises needs strict_errors propagation through render options). All unit tests pass (92 runs, 234 assertions, 0 failures). rake check: all 5 verifiers pass.
1 parent 7ff9669 commit c6fcb4d

2 files changed

Lines changed: 70 additions & 4 deletions

File tree

lib/liquid/spec/json_rpc/drop_proxy.rb

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ def size
3838
module DropProxy
3939
RPC_DROP_KEY = "_rpc_drop"
4040
RUBY_TYPE_KEY = "_ruby_type"
41+
INSTANTIATE_KEY = "_instantiate"
42+
43+
# Map standard drop Ruby classes to portable names and param extractors
44+
STANDARD_DROP_CLASSES = {
45+
"BooleanDrop" => proc { |o| { "value" => o.instance_variable_get(:@value) } },
46+
"NumberDrop" => proc { |o| { "value" => o.instance_variable_get(:@value) } },
47+
"StringDrop" => proc { |o| { "value" => o.instance_variable_get(:@value) } },
48+
"MethodDrop" => proc { |_o| {} },
49+
"IndexDrop" => proc { |_o| {} },
50+
"SequenceDrop" => proc { |_o| {} },
51+
"NilDrop" => proc { |_o| {} },
52+
"OpaqueDrop" => proc { |_o| {} },
53+
"ErrorDrop" => proc { |_o| {} },
54+
}.freeze
4155

4256
class << self
4357
# Wrap an object for JSON transport
@@ -106,8 +120,14 @@ def wrap(obj, registry, seen = {}.compare_by_identity)
106120
# to reproduce class-specific behavior (e.g. "cannot be printed").
107121
{ RUBY_TYPE_KEY => "Class", "name" => obj.name, "inspect" => obj.inspect }
108122
else
109-
# Check if it's a drop or liquid-compatible object
110-
if drop_like?(obj)
123+
# Check if it's a standard drop — send as _instantiate marker
124+
# so the server can create its own boxed object (no RPC needed)
125+
portable_name = standard_drop_name(obj)
126+
if portable_name
127+
params = STANDARD_DROP_CLASSES[portable_name].call(obj)
128+
{ INSTANTIATE_KEY => portable_name, "params" => params }
129+
elsif drop_like?(obj)
130+
# Non-standard drop — needs RPC callbacks
111131
drop_id = registry.register(obj)
112132
{ RPC_DROP_KEY => drop_id, "type" => obj.class.name }
113133
else
@@ -118,6 +138,19 @@ def wrap(obj, registry, seen = {}.compare_by_identity)
118138
end
119139
end
120140

141+
# Check if obj is a standard drop and return its portable name
142+
def standard_drop_name(obj)
143+
STANDARD_DROP_CLASSES.each_key do |name|
144+
cls = begin
145+
Object.const_get("Standard#{name}", false)
146+
rescue NameError
147+
nil
148+
end
149+
return name if cls&.=== obj
150+
end
151+
nil
152+
end
153+
121154
# Check if an object needs RPC callbacks (is a drop)
122155
def drop_like?(obj)
123156
return false if primitive?(obj)

scripts/remote-liquid/remote-liquid.rb

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
require "liquid"
1919
require "active_support/all"
2020
require "time"
21+
require_relative "../../lib/liquid/spec/spec_loader"
22+
require_relative "../../lib/liquid/spec/deps/standard_drops"
2123

2224
module RemoteLiquid
2325
VERSION = "1.0"
@@ -407,7 +409,9 @@ def freeze_time(iso_time)
407409
def unwrap_environment(env)
408410
case env
409411
when Hash
410-
if env["_rpc_drop"]
412+
if env["_instantiate"]
413+
instantiate_standard_drop(env["_instantiate"], env["params"] || {})
414+
elsif env["_rpc_drop"]
411415
RpcDropProxy.new(env["_rpc_drop"], env["type"], self)
412416
elsif env["_ruby_type"]
413417
unwrap_ruby_type(env)
@@ -449,7 +453,9 @@ def unwrap_ruby_type(marker)
449453
def unwrap_value(value)
450454
case value
451455
when Hash
452-
if value["_rpc_drop"]
456+
if value["_instantiate"]
457+
instantiate_standard_drop(value["_instantiate"], value["params"] || {})
458+
elsif value["_rpc_drop"]
453459
RpcDropProxy.new(value["_rpc_drop"], value["type"], self)
454460
elsif value["_ruby_type"]
455461
unwrap_ruby_type(value)
@@ -463,6 +469,33 @@ def unwrap_value(value)
463469
end
464470
end
465471

472+
# Create a standard drop instance from an _instantiate marker.
473+
# The server implements these drops natively — no RPC callbacks needed.
474+
def instantiate_standard_drop(name, params)
475+
case name
476+
when "BooleanDrop"
477+
StandardBooleanDrop.new(params)
478+
when "NumberDrop"
479+
StandardNumberDrop.new(params)
480+
when "StringDrop"
481+
StandardStringDrop.new(params)
482+
when "MethodDrop"
483+
StandardMethodDrop.new(params)
484+
when "IndexDrop"
485+
StandardIndexDrop.new(params)
486+
when "SequenceDrop"
487+
StandardSequenceDrop.new(params)
488+
when "NilDrop"
489+
StandardNilDrop.new(params)
490+
when "OpaqueDrop"
491+
StandardOpaqueDrop.new(params)
492+
when "ErrorDrop"
493+
StandardErrorDrop.new(params)
494+
else
495+
raise "Unknown standard drop: #{name}"
496+
end
497+
end
498+
466499
def success_response(id, result)
467500
{ "jsonrpc" => "2.0", "id" => id, "result" => result }
468501
end

0 commit comments

Comments
 (0)