restate-sdk 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,24 +5,19 @@ module Restate
5
5
  # A durable future wrapping a VM handle. Lazily resolves on first +await+ and caches the result.
6
6
  # Returned by +ctx.run+ and +ctx.sleep+.
7
7
  class DurableFuture
8
- extend T::Sig
9
-
10
- sig { returns(Integer) }
11
8
  attr_reader :handle
12
9
 
13
- sig { params(ctx: ServerContext, handle: Integer, serde: T.untyped).void }
14
10
  def initialize(ctx, handle, serde: nil)
15
- @ctx = T.let(ctx, ServerContext)
16
- @handle = T.let(handle, Integer)
17
- @serde = T.let(serde, T.untyped)
18
- @resolved = T.let(false, T::Boolean)
19
- @value = T.let(nil, T.untyped)
11
+ @ctx = ctx
12
+ @handle = handle
13
+ @serde = serde
14
+ @resolved = false
15
+ @value = nil
20
16
  end
21
17
 
22
18
  # Block until the result is available and return it. Caches across calls.
23
19
  #
24
20
  # @return [Object] the deserialized result
25
- sig { returns(T.untyped) }
26
21
  def await
27
22
  unless @resolved
28
23
  raw = @ctx.resolve_handle(@handle)
@@ -35,7 +30,6 @@ module Restate
35
30
  # Check whether the future has completed (non-blocking).
36
31
  #
37
32
  # @return [Boolean]
38
- sig { returns(T::Boolean) }
39
33
  def completed?
40
34
  @resolved || @ctx.completed?(@handle)
41
35
  end
@@ -45,26 +39,15 @@ module Restate
45
39
  # Adds +invocation_id+ and +cancel+ on top of DurableFuture.
46
40
  # Returned by +ctx.service_call+, +ctx.object_call+, +ctx.workflow_call+.
47
41
  class DurableCallFuture < DurableFuture
48
- extend T::Sig
49
-
50
- sig do
51
- params(
52
- ctx: ServerContext,
53
- result_handle: Integer,
54
- invocation_id_handle: Integer,
55
- output_serde: T.untyped
56
- ).void
57
- end
58
42
  def initialize(ctx, result_handle, invocation_id_handle, output_serde:)
59
43
  super(ctx, result_handle)
60
- @invocation_id_handle = T.let(invocation_id_handle, Integer)
61
- @output_serde = T.let(output_serde, T.untyped)
62
- @invocation_id_resolved = T.let(false, T::Boolean)
63
- @invocation_id_value = T.let(nil, T.untyped)
44
+ @invocation_id_handle = invocation_id_handle
45
+ @output_serde = output_serde
46
+ @invocation_id_resolved = false
47
+ @invocation_id_value = nil
64
48
  end
65
49
 
66
50
  # Block until the result is available and return it. Deserializes via +output_serde+.
67
- sig { returns(T.untyped) }
68
51
  def await
69
52
  unless @resolved
70
53
  raw = @ctx.resolve_handle(@handle)
@@ -81,17 +64,15 @@ module Restate
81
64
  # Returns the invocation ID of the remote call. Lazily resolved.
82
65
  #
83
66
  # @return [String] the invocation ID
84
- sig { returns(String) }
85
67
  def invocation_id
86
68
  unless @invocation_id_resolved
87
69
  @invocation_id_value = @ctx.resolve_handle(@invocation_id_handle)
88
70
  @invocation_id_resolved = true
89
71
  end
90
- T.must(@invocation_id_value)
72
+ @invocation_id_value
91
73
  end
92
74
 
93
75
  # Cancel the remote invocation.
94
- sig { void }
95
76
  def cancel
96
77
  @ctx.cancel_invocation(invocation_id)
97
78
  end
@@ -100,30 +81,25 @@ module Restate
100
81
  # A handle for fire-and-forget send operations.
101
82
  # Returned by +ctx.service_send+, +ctx.object_send+, +ctx.workflow_send+.
102
83
  class SendHandle
103
- extend T::Sig
104
-
105
- sig { params(ctx: ServerContext, invocation_id_handle: Integer).void }
106
84
  def initialize(ctx, invocation_id_handle)
107
- @ctx = T.let(ctx, ServerContext)
108
- @invocation_id_handle = T.let(invocation_id_handle, Integer)
109
- @invocation_id_resolved = T.let(false, T::Boolean)
110
- @invocation_id_value = T.let(nil, T.untyped)
85
+ @ctx = ctx
86
+ @invocation_id_handle = invocation_id_handle
87
+ @invocation_id_resolved = false
88
+ @invocation_id_value = nil
111
89
  end
112
90
 
113
91
  # Returns the invocation ID of the sent call. Lazily resolved.
114
92
  #
115
93
  # @return [String] the invocation ID
116
- sig { returns(String) }
117
94
  def invocation_id
118
95
  unless @invocation_id_resolved
119
96
  @invocation_id_value = @ctx.resolve_handle(@invocation_id_handle)
120
97
  @invocation_id_resolved = true
121
98
  end
122
- T.must(@invocation_id_value)
99
+ @invocation_id_value
123
100
  end
124
101
 
125
102
  # Cancel the remote invocation.
126
- sig { void }
127
103
  def cancel
128
104
  @ctx.cancel_invocation(invocation_id)
129
105
  end
@@ -4,26 +4,15 @@
4
4
  module Restate
5
5
  # Container for registered services. Bind services here, then create the Rack app.
6
6
  class Endpoint
7
- extend T::Sig
7
+ attr_reader :services, :identity_keys, :middleware
8
8
 
9
- sig { returns(T::Hash[String, T.untyped]) }
10
- attr_reader :services
11
-
12
- sig { returns(T::Array[String]) }
13
- attr_reader :identity_keys
14
-
15
- sig { returns(T.nilable(String)) }
16
9
  attr_accessor :protocol
17
10
 
18
- sig { returns(T::Array[T.untyped]) }
19
- attr_reader :middleware
20
-
21
- sig { void }
22
11
  def initialize
23
- @services = T.let({}, T::Hash[String, T.untyped])
24
- @protocol = T.let(nil, T.nilable(String))
25
- @identity_keys = T.let([], T::Array[String])
26
- @middleware = T.let([], T::Array[T.untyped])
12
+ @services = {}
13
+ @protocol = nil
14
+ @identity_keys = []
15
+ @middleware = []
27
16
  end
28
17
 
29
18
  # Bind one or more services to this endpoint.
@@ -31,7 +20,6 @@ module Restate
31
20
  # @param svcs [Array<Class<Service>, Class<VirtualObject>, Class<Workflow>>] services to bind
32
21
  # @return [self]
33
22
  # @raise [ArgumentError] if a service with the same name is already bound
34
- sig { params(svcs: T.untyped).returns(T.self_type) }
35
23
  def bind(*svcs)
36
24
  svcs.each do |svc|
37
25
  svc_name = svc.service_name
@@ -43,21 +31,18 @@ module Restate
43
31
  end
44
32
 
45
33
  # Force bidirectional streaming protocol.
46
- sig { returns(T.self_type) }
47
34
  def streaming_protocol
48
35
  @protocol = 'bidi'
49
36
  self
50
37
  end
51
38
 
52
39
  # Force request/response protocol.
53
- sig { returns(T.self_type) }
54
40
  def request_response_protocol
55
41
  @protocol = 'request_response'
56
42
  self
57
43
  end
58
44
 
59
45
  # Add an identity key for request verification.
60
- sig { params(key: String).returns(T.self_type) }
61
46
  def identity_key(key)
62
47
  @identity_keys << key
63
48
  self
@@ -116,7 +101,6 @@ module Restate
116
101
  # @param args [Array] positional arguments for the middleware constructor
117
102
  # @param kwargs [Hash] keyword arguments for the middleware constructor
118
103
  # @return [self]
119
- sig { params(klass: T.untyped, args: T.untyped, kwargs: T.untyped).returns(T.self_type) }
120
104
  def use(klass, *args, **kwargs)
121
105
  instance = if kwargs.empty?
122
106
  klass.new(*args)
@@ -128,7 +112,6 @@ module Restate
128
112
  end
129
113
 
130
114
  # Build and return the Rack-compatible application.
131
- sig { returns(T.untyped) }
132
115
  def app
133
116
  require_relative 'server'
134
117
  Server.new(self)
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Restate
@@ -7,24 +7,17 @@ module Restate
7
7
  # @example
8
8
  # raise Restate::TerminalError.new('not found', status_code: 404)
9
9
  class TerminalError < StandardError
10
- extend T::Sig
11
-
12
- sig { returns(Integer) }
13
10
  attr_reader :status_code
14
11
 
15
- sig { params(message: String, status_code: Integer).void }
16
12
  def initialize(message = 'Internal Server Error', status_code: 500)
17
13
  super(message)
18
- @status_code = T.let(status_code, Integer)
14
+ @status_code = status_code
19
15
  end
20
16
  end
21
17
 
22
18
  # Internal: raised when the VM suspends execution.
23
19
  # User code should NOT catch this.
24
20
  class SuspendedError < StandardError
25
- extend T::Sig
26
-
27
- sig { void }
28
21
  def initialize
29
22
  super(
30
23
  "Invocation got suspended, Restate will resume this invocation when progress can be made.\n" \
@@ -37,9 +30,6 @@ module Restate
37
30
 
38
31
  # Internal: raised when the VM encounters a retryable error.
39
32
  class InternalError < StandardError
40
- extend T::Sig
41
-
42
- sig { void }
43
33
  def initialize
44
34
  super(
45
35
  "Invocation attempt raised a retryable error.\n" \
@@ -50,9 +40,6 @@ module Restate
50
40
 
51
41
  # Internal: raised when the HTTP connection is lost.
52
42
  class DisconnectedError < StandardError
53
- extend T::Sig
54
-
55
- sig { void }
56
43
  def initialize
57
44
  super('Disconnected. The connection to the restate server was lost. Restate will retry the attempt.')
58
45
  end
@@ -27,38 +27,39 @@ module Restate
27
27
  keyword_init: true
28
28
  )
29
29
 
30
- extend T::Sig
31
-
32
30
  module_function
33
31
 
34
32
  # Invoke a handler with the context and raw input bytes.
35
33
  # The context is passed as the first argument to every handler.
36
34
  # Middleware (if any) wraps the handler call.
37
35
  # Returns raw output bytes.
38
- sig do
39
- params(handler: T.untyped, ctx: T.untyped, in_buffer: String,
40
- middleware: T::Array[T.untyped]).returns(String)
36
+ def invoke_handler(handler:, ctx:, in_buffer:, middleware: [])
37
+ out_arg = if middleware.empty?
38
+ invoke_handler_direct(handler, in_buffer)
39
+ else
40
+ invoke_handler_with_middleware(handler, ctx, in_buffer, middleware)
41
+ end
42
+ handler.handler_io.output_serde.serialize(out_arg)
41
43
  end
42
- def invoke_handler(handler:, ctx:, in_buffer:, middleware: []) # rubocop:disable Metrics/AbcSize
43
- call_handler = Kernel.proc do
44
- if handler.arity == 2
45
- begin
46
- in_arg = handler.handler_io.input_serde.deserialize(in_buffer)
47
- rescue StandardError => e
48
- Kernel.raise TerminalError, "Unable to parse input argument: #{e.message}"
49
- end
50
- handler.callable.call(ctx, in_arg)
51
- else
52
- handler.callable.call(ctx)
44
+
45
+ def invoke_handler_direct(handler, in_buffer)
46
+ if handler.arity == 1
47
+ begin
48
+ in_arg = handler.handler_io.input_serde.deserialize(in_buffer)
49
+ rescue StandardError => e
50
+ Kernel.raise TerminalError, "Unable to parse input argument: #{e.message}"
53
51
  end
52
+ handler.callable.call(in_arg)
53
+ else
54
+ handler.callable.call
54
55
  end
56
+ end
55
57
 
56
- # Build the middleware chain so each middleware can use `yield` to call the next.
58
+ def invoke_handler_with_middleware(handler, ctx, in_buffer, middleware)
59
+ call_handler = Kernel.proc { invoke_handler_direct(handler, in_buffer) }
57
60
  chain = middleware.reverse.reduce(call_handler) do |nxt, mw|
58
61
  Kernel.proc { mw.call(handler, ctx, &nxt) }
59
62
  end
60
-
61
- out_arg = chain.call
62
- handler.handler_io.output_serde.serialize(out_arg)
63
+ chain.call
63
64
  end
64
65
  end
data/lib/restate/serde.rb CHANGED
@@ -4,23 +4,22 @@
4
4
  require 'json'
5
5
 
6
6
  module Restate
7
+ EMPTY_BYTES = ''.b.freeze
8
+ private_constant :EMPTY_BYTES
9
+
7
10
  # JSON serializer/deserializer (default).
8
11
  # Converts Ruby objects to JSON byte strings and back.
9
12
  module JsonSerde
10
- extend T::Sig
11
-
12
13
  module_function
13
14
 
14
15
  # Serialize a Ruby object to a JSON byte string. Returns empty bytes for nil.
15
- sig { params(obj: T.untyped).returns(String) }
16
16
  def serialize(obj)
17
- return ''.b if obj.nil?
17
+ return EMPTY_BYTES if obj.nil?
18
18
 
19
19
  JSON.generate(obj).b
20
20
  end
21
21
 
22
22
  # Deserialize a JSON byte string to a Ruby object. Returns nil for nil or empty input.
23
- sig { params(buf: T.nilable(String)).returns(T.untyped) }
24
23
  def deserialize(buf)
25
24
  return nil if buf.nil? || buf.empty?
26
25
 
@@ -28,7 +27,6 @@ module Restate
28
27
  end
29
28
 
30
29
  # Return the JSON Schema for this serde, or nil if unspecified.
31
- sig { returns(T.nilable(T::Hash[String, T.untyped])) }
32
30
  def json_schema
33
31
  nil
34
32
  end
@@ -40,26 +38,21 @@ module Restate
40
38
  # Pass-through bytes serializer/deserializer.
41
39
  # Passes binary data through without transformation.
42
40
  module BytesSerde
43
- extend T::Sig
44
-
45
41
  module_function
46
42
 
47
43
  # Serialize an object by returning its binary encoding. Returns empty bytes for nil.
48
- sig { params(obj: T.untyped).returns(String) }
49
44
  def serialize(obj)
50
- return ''.b if obj.nil?
45
+ return EMPTY_BYTES if obj.nil?
51
46
 
52
47
  obj.b
53
48
  end
54
49
 
55
50
  # Deserialize by returning the raw buffer unchanged.
56
- sig { params(buf: T.nilable(String)).returns(T.nilable(String)) }
57
51
  def deserialize(buf)
58
52
  buf
59
53
  end
60
54
 
61
55
  # Return the JSON Schema for this serde, or nil if unspecified.
62
- sig { returns(T.nilable(T::Hash[String, T.untyped])) }
63
56
  def json_schema
64
57
  nil
65
58
  end
@@ -67,34 +60,29 @@ module Restate
67
60
 
68
61
  # Maps Ruby primitive types to JSON Schema snippets for discovery.
69
62
  PRIMITIVE_SCHEMAS = {
70
- String => { 'type' => 'string' },
71
- Integer => { 'type' => 'integer' },
72
- Float => { 'type' => 'number' },
73
- TrueClass => { 'type' => 'boolean' },
74
- FalseClass => { 'type' => 'boolean' },
75
- Array => { 'type' => 'array' },
76
- Hash => { 'type' => 'object' },
77
- NilClass => { 'type' => 'null' }
63
+ String => { 'type' => 'string' }.freeze,
64
+ Integer => { 'type' => 'integer' }.freeze,
65
+ Float => { 'type' => 'number' }.freeze,
66
+ TrueClass => { 'type' => 'boolean' }.freeze,
67
+ FalseClass => { 'type' => 'boolean' }.freeze,
68
+ Array => { 'type' => 'array' }.freeze,
69
+ Hash => { 'type' => 'object' }.freeze,
70
+ NilClass => { 'type' => 'null' }.freeze
78
71
  }.freeze
79
72
 
80
73
  # Serde resolution utilities: converts a type or serde into a serde object.
81
74
  module Serde
82
- extend T::Sig
83
-
84
75
  module_function
85
76
 
86
77
  # Check if an object quacks like a serde (has serialize + deserialize).
87
- sig { params(obj: T.untyped).returns(T::Boolean) }
88
78
  def serde?(obj)
89
79
  obj.respond_to?(:serialize) && obj.respond_to?(:deserialize)
90
80
  end
91
81
 
92
82
  # Resolve a type or serde into a serde object with serialize/deserialize/json_schema.
93
- sig { params(type_or_serde: T.untyped).returns(T.untyped) }
94
83
  def resolve(type_or_serde)
95
84
  return JsonSerde if type_or_serde.nil?
96
85
  return type_or_serde if serde?(type_or_serde)
97
- return TStructSerde.new(type_or_serde) if t_struct?(type_or_serde)
98
86
  return DryStructSerde.new(type_or_serde) if dry_struct?(type_or_serde)
99
87
  return TypeSerde.new(type_or_serde, PRIMITIVE_SCHEMAS[type_or_serde]) if PRIMITIVE_SCHEMAS.key?(type_or_serde)
100
88
  return TypeSerde.new(type_or_serde, type_or_serde.json_schema) if type_or_serde.respond_to?(:json_schema)
@@ -102,60 +90,12 @@ module Restate
102
90
  JsonSerde
103
91
  end
104
92
 
105
- # Check if a value is a T::Struct subclass.
106
- sig { params(val: T.untyped).returns(T::Boolean) }
107
- def t_struct?(val)
108
- !!(val.is_a?(Class) && val < T::Struct)
109
- end
110
-
111
93
  # Check if a value is a Dry::Struct subclass.
112
- sig { params(val: T.untyped).returns(T.nilable(T::Boolean)) }
113
94
  def dry_struct?(val)
114
95
  defined?(::Dry::Struct) && val.is_a?(Class) && val < ::Dry::Struct
115
96
  end
116
97
 
117
- # Generate a JSON Schema from a T::Struct class by introspecting its props.
118
- sig { params(struct_class: T.class_of(T::Struct)).returns(T::Hash[String, T.untyped]) }
119
- def t_struct_to_json_schema(struct_class) # rubocop:disable Metrics
120
- properties = {}
121
- required = []
122
-
123
- T.unsafe(struct_class).props.each do |name, meta|
124
- prop_name = (meta[:serialized_form] || name).to_s
125
- properties[prop_name] = t_type_to_json_schema(meta[:type_object] || meta[:type])
126
- required << prop_name unless meta[:fully_optional] || meta[:_tnilable]
127
- end
128
-
129
- schema = { 'type' => 'object', 'properties' => properties }
130
- schema['required'] = required unless required.empty?
131
- schema
132
- end
133
-
134
- # Convert a Sorbet T::Types type object to a JSON Schema hash.
135
- sig { params(type: T.untyped).returns(T::Hash[String, T.untyped]) }
136
- def t_type_to_json_schema(type) # rubocop:disable Metrics
137
- case type
138
- when T::Types::Simple
139
- PRIMITIVE_SCHEMAS[type.raw_type] || {}
140
- when T::Types::Union
141
- schemas = type.types.map { |t| t_type_to_json_schema(t) }
142
- schemas.uniq!
143
- schemas.length == 1 ? schemas.first : { 'anyOf' => schemas }
144
- when T::Types::TypedArray
145
- { 'type' => 'array', 'items' => t_type_to_json_schema(type.type) }
146
- when T::Types::TypedHash
147
- { 'type' => 'object' }
148
- when Class
149
- return t_struct_to_json_schema(type) if type < T::Struct
150
-
151
- PRIMITIVE_SCHEMAS[type] || {}
152
- else
153
- {}
154
- end
155
- end
156
-
157
98
  # Generate a JSON Schema from a Dry::Struct class.
158
- sig { params(struct_class: T.untyped).returns(T::Hash[String, T.untyped]) }
159
99
  def dry_struct_to_json_schema(struct_class)
160
100
  properties = {}
161
101
  required = []
@@ -172,7 +112,6 @@ module Restate
172
112
  end
173
113
 
174
114
  # Convert a dry-types type to a JSON Schema hash.
175
- sig { params(type: T.untyped).returns(T::Hash[String, T.untyped]) }
176
115
  def dry_type_to_json_schema(type) # rubocop:disable Metrics
177
116
  type_class = type.class.name || ''
178
117
 
@@ -198,7 +137,6 @@ module Restate
198
137
  end
199
138
 
200
139
  # Convert a nominal dry-type (with .primitive) to JSON Schema.
201
- sig { params(type: T.untyped).returns(T::Hash[String, T.untyped]) }
202
140
  def nominal_to_json_schema(type)
203
141
  prim = type.primitive
204
142
  return dry_struct_to_json_schema(prim) if dry_struct?(prim)
@@ -210,32 +148,25 @@ module Restate
210
148
  # Serde wrapper for primitive types and classes with a .json_schema method.
211
149
  # Delegates serialize/deserialize to JsonSerde, adds schema.
212
150
  class TypeSerde
213
- extend T::Sig
214
-
215
- sig { returns(T.untyped) }
216
151
  attr_reader :type_class
217
152
 
218
153
  # Create a TypeSerde for the given type with a precomputed JSON Schema.
219
- sig { params(type: T.untyped, schema: T.nilable(T::Hash[String, T.untyped])).void }
220
154
  def initialize(type, schema)
221
155
  @type_class = type
222
156
  @schema = schema
223
157
  end
224
158
 
225
159
  # Serialize a Ruby object to JSON bytes via JsonSerde.
226
- sig { params(obj: T.untyped).returns(String) }
227
160
  def serialize(obj)
228
161
  JsonSerde.serialize(obj)
229
162
  end
230
163
 
231
164
  # Deserialize JSON bytes to a Ruby object via JsonSerde.
232
- sig { params(buf: T.nilable(String)).returns(T.untyped) }
233
165
  def deserialize(buf)
234
166
  JsonSerde.deserialize(buf)
235
167
  end
236
168
 
237
169
  # Return the JSON Schema for this type.
238
- sig { returns(T.nilable(T::Hash[String, T.untyped])) }
239
170
  def json_schema
240
171
  @schema
241
172
  end
@@ -244,28 +175,22 @@ module Restate
244
175
  # Serde for Dry::Struct types.
245
176
  # Deserializes JSON into struct instances, serializes structs to JSON.
246
177
  class DryStructSerde
247
- extend T::Sig
248
-
249
- sig { returns(T.untyped) }
250
178
  attr_reader :struct_class
251
179
 
252
180
  # Create a DryStructSerde for the given Dry::Struct class.
253
- sig { params(struct_class: T.untyped).void }
254
181
  def initialize(struct_class)
255
182
  @struct_class = struct_class
256
183
  end
257
184
 
258
185
  # Serialize a Dry::Struct (or hash-like object) to JSON bytes.
259
- sig { params(obj: T.untyped).returns(String) }
260
186
  def serialize(obj)
261
- return ''.b if obj.nil?
187
+ return EMPTY_BYTES if obj.nil?
262
188
 
263
189
  hash = obj.respond_to?(:to_h) ? obj.to_h : obj
264
190
  JSON.generate(hash).b
265
191
  end
266
192
 
267
193
  # Deserialize JSON bytes into a Dry::Struct instance.
268
- sig { params(buf: T.nilable(String)).returns(T.untyped) }
269
194
  def deserialize(buf)
270
195
  return nil if buf.nil? || buf.empty?
271
196
 
@@ -274,49 +199,8 @@ module Restate
274
199
  end
275
200
 
276
201
  # Return the JSON Schema derived from the Dry::Struct definition.
277
- sig { returns(T::Hash[String, T.untyped]) }
278
202
  def json_schema
279
203
  @json_schema ||= Serde.dry_struct_to_json_schema(@struct_class)
280
204
  end
281
205
  end
282
-
283
- # Serde for T::Struct types (Sorbet's native typed structs).
284
- # Uses T::Struct#serialize for output and T::Struct.from_hash for input.
285
- # Generates JSON Schema from T::Struct props introspection.
286
- class TStructSerde
287
- extend T::Sig
288
-
289
- sig { returns(T.class_of(T::Struct)) }
290
- attr_reader :struct_class
291
-
292
- # Create a TStructSerde for the given T::Struct subclass.
293
- sig { params(struct_class: T.class_of(T::Struct)).void }
294
- def initialize(struct_class)
295
- @struct_class = struct_class
296
- end
297
-
298
- # Serialize a T::Struct instance to JSON bytes.
299
- sig { params(obj: T.untyped).returns(String) }
300
- def serialize(obj)
301
- return ''.b if obj.nil?
302
-
303
- hash = obj.is_a?(T::Struct) ? obj.serialize : obj
304
- JSON.generate(hash).b
305
- end
306
-
307
- # Deserialize JSON bytes into a T::Struct instance.
308
- sig { params(buf: T.nilable(String)).returns(T.untyped) }
309
- def deserialize(buf)
310
- return nil if buf.nil? || buf.empty?
311
-
312
- hash = JSON.parse(buf)
313
- T.unsafe(@struct_class).from_hash(hash)
314
- end
315
-
316
- # Return the JSON Schema derived from the T::Struct props.
317
- sig { returns(T::Hash[String, T.untyped]) }
318
- def json_schema
319
- @json_schema ||= Serde.t_struct_to_json_schema(@struct_class)
320
- end
321
- end
322
206
  end