Class: Kobako::Codec::Factory

Inherits:
Object
  • Object
show all
Extended by:
Forwardable, SingleForwardable
Includes:
Singleton
Defined in:
lib/kobako/codec/factory.rb,
sig/kobako/codec/factory.rbs

Overview

Cached MessagePack::Factory that owns the kobako wire ext-type registration (docs/wire-codec.md § Ext Types).

The factory is the single place in the host gem that touches the msgpack API — both Encoder and Decoder delegate through it, so the three kobako ext codes (0x00 Symbol, 0x01 Capability Handle, 0x02 Exception envelope) are configured exactly once at first use.

Lifecycle is owned by Singleton from the Ruby standard library: Factory.instance is lazy, thread-safe, and process-wide. Class-level Factory.dump / Factory.load shortcuts are exposed via SingleForwardable so callers do not have to spell the .instance hop at every call site; the instance-level #dump / #load are in turn delegated to the wrapped MessagePack::Factory via Forwardable.

Constant Summary collapse

EXT_SYMBOL =

MessagePack ext type code reserved for Symbol (docs/wire-codec.md § Ext Types → ext 0x00). Class-private — mirrors codec::EXT_SYMBOL on the Rust side.

Returns:

  • (Integer)
0x00
EXT_HANDLE =

MessagePack ext type code reserved for Capability Handle (docs/wire-codec.md § Ext Types → ext 0x01). Class-private — mirrors codec::EXT_HANDLE on the Rust side.

Returns:

  • (Integer)
0x01
EXT_ERRENV =

MessagePack ext type code reserved for Exception envelope (docs/wire-codec.md § Ext Types → ext 0x02). Class-private — mirrors codec::EXT_ERRENV on the Rust side.

Returns:

  • (Integer)
0x02
MAX_EXT_DEPTH =

An ext 0x02 (Fault) envelope nests through its details field, and each level re-enters the codec with a fresh MessagePack unpacker whose built-in stack guard resets — so ext-envelope depth is tracked here instead. The cap matches the wire's overall nesting bound and keeps a nested chain from exhausting the native stack: an over-deep chain fails as a clean wire error, never a stack-level trap.

Returns:

  • (Integer)
128
EXT_DEPTH_KEY =

Returns:

  • (Symbol)
:__kobako_codec_ext_depth__

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeFactory

Returns a new instance of Factory.



73
74
75
76
77
78
# File 'lib/kobako/codec/factory.rb', line 73

def initialize
  @factory = MessagePack::Factory.new
  register_symbol
  register_handle
  register_fault
end

Class Method Details

.dumpString

Parameters:

  • value (Object)

Returns:

  • (String)


17
# File 'sig/kobako/codec/factory.rbs', line 17

def self.dump: (untyped value) -> String

.loadObject

Parameters:

  • bytes (String)

Returns:

  • (Object)


18
# File 'sig/kobako/codec/factory.rbs', line 18

def self.load: (String bytes) -> untyped

Instance Method Details

#dumpString

Parameters:

  • value (Object)

Returns:

  • (String)


14
# File 'sig/kobako/codec/factory.rbs', line 14

def dump: (untyped value) -> String

#loadObject

Parameters:

  • bytes (String)

Returns:

  • (Object)


15
# File 'sig/kobako/codec/factory.rbs', line 15

def load: (String bytes) -> untyped

#pack_fault(fault) ⇒ String

Encode the inner ext-0x02 map via Encoder (not factory.dump) so the embedded payload flows through the same boundary as a top-level encode — nested kobako values (Handle, nested Fault) reach the registered ext-type packers via the cached singleton. A details chain nested past MAX_EXT_DEPTH has no wire representation and surfaces as UnsupportedType.

Parameters:

Returns:

  • (String)


145
146
147
148
149
# File 'lib/kobako/codec/factory.rb', line 145

def pack_fault(fault)
  within_ext_frame(UnsupportedType) do
    Encoder.encode("type" => fault.type, "message" => fault.message, "details" => fault.details)
  end
end

#pack_symbol(symbol) ⇒ String

Symbol-to-name packer for the ext-0x00 registration.

Parameters:

  • symbol (Symbol)

Returns:

  • (String)


91
92
93
# File 'lib/kobako/codec/factory.rb', line 91

def pack_symbol(symbol)
  symbol.name
end

#register_faultvoid

This method returns an undefined value.



117
118
119
120
121
122
123
# File 'lib/kobako/codec/factory.rb', line 117

def register_fault
  @factory.register_type(
    EXT_ERRENV, Kobako::Fault,
    packer: ->(fault) { pack_fault(fault) },
    unpacker: ->(payload) { unpack_fault(payload) }
  )
end

#register_handlevoid

This method returns an undefined value.



109
110
111
112
113
114
115
# File 'lib/kobako/codec/factory.rb', line 109

def register_handle
  @factory.register_type(
    EXT_HANDLE, Kobako::Handle,
    packer: ->(handle) { [handle.id].pack("N") },
    unpacker: ->(payload) { unpack_handle(payload) }
  )
end

#register_symbolvoid

This method returns an undefined value.



82
83
84
85
86
87
88
# File 'lib/kobako/codec/factory.rb', line 82

def register_symbol
  @factory.register_type(
    EXT_SYMBOL, Symbol,
    packer: method(:pack_symbol),
    unpacker: method(:unpack_symbol)
  )
end

#unpack_fault(payload) ⇒ Kobako::Fault

Peel the embedded msgpack map and hand it to Kobako::Fault.new inside Decoder.decode's block form, so the value-object's ArgumentError invariants surface as InvalidType through the decoder boundary. Inner decode goes through Decoder (not factory.load) so the embedded str payloads flow through the same UTF-8 validation as a top-level decode. A nested ext 0x02 in details re-enters this method, so #within_ext_frame bounds the chain depth to keep it from exhausting the native stack.

Parameters:

  • payload (String)

Returns:



159
160
161
162
163
164
165
166
167
# File 'lib/kobako/codec/factory.rb', line 159

def unpack_fault(payload)
  within_ext_frame(InvalidType) do
    Decoder.decode(payload) do |map|
      raise InvalidType, "Fault payload must be a map" unless map.is_a?(Hash)

      Kobako::Fault.new(type: map["type"], message: map["message"], details: map["details"])
    end
  end
end

#unpack_handle(payload) ⇒ Kobako::Handle

Peel off the fixext-4 frame, hand the bytes to the Host-Gem-internal Kobako::Handle.restore factory, and translate the ArgumentError raised by Handle's invariants into a wire-layer InvalidType via Utils.with_boundary. The Value Object owns the id-range contract; this method only owns the frame shape.

Parameters:

  • payload (String)

Returns:

Raises:



131
132
133
134
135
136
137
# File 'lib/kobako/codec/factory.rb', line 131

def unpack_handle(payload)
  bytes = payload.b
  raise InvalidType, "Handle payload must be 4 bytes, got #{bytes.bytesize}" unless bytes.bytesize == 4

  id = bytes.unpack1("N") # : Integer
  Codec::Utils.with_boundary { Kobako::Handle.restore(id) }
end

#unpack_symbol(payload) ⇒ Symbol

Validate the ext-0x00 payload as UTF-8 and intern. Raises InvalidEncoding on invalid bytes — SPEC forbids the binary-encoding fallback that msgpack-gem's default unpacker would otherwise apply. The re-tag step lives here because the msgpack ext-type unpacker hands us binary bytes; the assertion itself is shared with Decoder via Utils.assert_utf8!. The "Symbol" label keeps the error message in Ruby vocabulary rather than wire-ext-code vocabulary.

Parameters:

  • payload (String)

Returns:

  • (Symbol)


103
104
105
106
107
# File 'lib/kobako/codec/factory.rb', line 103

def unpack_symbol(payload)
  name = payload.b.force_encoding(Encoding::UTF_8)
  Utils.assert_utf8!(name, "Symbol payload")
  name.to_sym
end

#within_ext_frame(over_limit) ⇒ void

This method returns an undefined value.

Track ext-envelope re-entry depth and refuse a chain past MAX_EXT_DEPTH, raising over_limit so the failure lands in the caller's existing wire-error class. The counter is thread-scoped and balanced by the ensure, so a raise mid-chain still unwinds it to its entry value.

Raises:

  • (over_limit)


174
175
176
177
178
179
180
181
182
183
184
# File 'lib/kobako/codec/factory.rb', line 174

def within_ext_frame(over_limit)
  depth = (Thread.current[EXT_DEPTH_KEY] || 0) + 1
  raise over_limit, "ext envelope nesting exceeds #{MAX_EXT_DEPTH} levels" if depth > MAX_EXT_DEPTH

  Thread.current[EXT_DEPTH_KEY] = depth
  begin
    yield
  ensure
    Thread.current[EXT_DEPTH_KEY] = depth - 1
  end
end