tina4ruby 3.13.29 → 3.13.31

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01fb453b8f6a6ea027dc04bc3e1d56c808969f50739ca60946152030883e7fd0
4
- data.tar.gz: b04d9e2902f61df6e37ae6eb81b95b6f60edf579739206ea97b9463a1ba5dd25
3
+ metadata.gz: 6140bb67a60387c462e70052c88bbde69ba3b669c0371380df401bd8f9a0b039
4
+ data.tar.gz: 7e0043ae026ed273a444f6185f289d0099458fc9ce605bfe4380c1fda9b6f672
5
5
  SHA512:
6
- metadata.gz: 4c3ce7f5a22fef09964273c1645a3b1756a2359bf2d670bcc2b3c640bd8f0689a38653dd046b12c000fdc78ce9cbbb0dec386317a5d13e3cf09243c39461888f
7
- data.tar.gz: dca9b42a7753fd2609d263867846d236e1c3e10b094f37a6030c690cadab75e3041b787e886c6feceb7dbafcb73b8bf3ba090c60fc69167e3309d1f3b79d378c
6
+ metadata.gz: 83b74a98d73ebcfdd0f22a9335b09382d5e518d51f3443b5e29dafdddb892374e41c8b9cbbc0b4a3d354e3bd405996a376c2e93df1f996778a73d349561a2879
7
+ data.tar.gz: 4eadb6a043ec78b5058465b73e4f10f2bf8d91ed19f5e22e5a5a00e2f4b57de8f59fe321b89b5e1e6417dc2dd15ff9142bb36635eed1275f85b59148149498fd
data/lib/tina4/graphql.rb CHANGED
@@ -1026,7 +1026,9 @@ module Tina4
1026
1026
 
1027
1027
  graphql = self
1028
1028
  Tina4.post path, auth: false do |request, response|
1029
- body = request.body
1029
+ # handle_request expects the raw JSON text (it JSON.parses internally),
1030
+ # so read body_raw — request.body now returns the PARSED payload.
1031
+ body = request.body_raw
1030
1032
  result = graphql.handle_request(body, context: { request: request })
1031
1033
  response.json(result)
1032
1034
  end
data/lib/tina4/request.rb CHANGED
@@ -35,6 +35,26 @@ module Tina4
35
35
  end
36
36
  end
37
37
 
38
+ # Per-file upload hash: indifferent access plus a lazily-materialised
39
+ # `content` field. The raw bytes are read from the tempfile only on first
40
+ # access to `content` (then rewound, so :tempfile streaming still works),
41
+ # so :tempfile-only handlers never buffer large uploads in memory.
42
+ class FileUpload < IndifferentHash
43
+ def [](key)
44
+ if key.to_s == "content" && !key?("content") && (tf = super("tempfile"))
45
+ self["content"] = begin
46
+ tf.rewind if tf.respond_to?(:rewind)
47
+ data = tf.read
48
+ tf.rewind if tf.respond_to?(:rewind)
49
+ data
50
+ rescue StandardError
51
+ nil
52
+ end
53
+ end
54
+ super(key)
55
+ end
56
+ end
57
+
38
58
  # Hash subclass for HTTP headers — string keys are case-insensitive.
39
59
  #
40
60
  # HTTP header field-names are case-insensitive per RFC 7230 §3.2. With
@@ -161,16 +181,25 @@ module Tina4
161
181
  @session ||= Tina4::Session.new(@env)
162
182
  end
163
183
 
164
- # Raw body string
184
+ # Parsed body (JSON -> Hash, form-urlencoded -> Hash, multipart ->
185
+ # fields Hash, else the current fallback). This matches Python's
186
+ # `request.body`, PHP's, and Node's: `body` is the PARSED payload, not
187
+ # the raw bytes. For the raw string use `body_raw`.
165
188
  def body
166
- @body_raw ||= read_body
189
+ @body_parsed ||= parse_body
167
190
  end
168
191
 
169
- # Parsed body (JSON or form data)
170
- def body_parsed
171
- @body_parsed ||= parse_body
192
+ # Raw body string the bytes exactly as the client sent them.
193
+ # (This is what `body` used to return before the cross-framework
194
+ # parity flip; SOAP/GraphQL and any consumer that needs the raw text
195
+ # reads this.)
196
+ def body_raw
197
+ @body_raw ||= read_body
172
198
  end
173
199
 
200
+ # Backwards-compatible alias of `body` — both return the parsed payload.
201
+ alias body_parsed body
202
+
174
203
  # Parsed query string as hash
175
204
  def query
176
205
  @query_hash ||= parse_query_to_hash(@query_string)
@@ -204,7 +233,7 @@ module Tina4
204
233
 
205
234
  def json_body
206
235
  @json_body ||= begin
207
- JSON.parse(body)
236
+ JSON.parse(body_raw)
208
237
  rescue JSON::ParserError, TypeError
209
238
  {}
210
239
  end
@@ -273,7 +302,7 @@ module Tina4
273
302
  if @content_type.include?("application/json")
274
303
  json_body
275
304
  elsif @content_type.include?("application/x-www-form-urlencoded")
276
- parse_query_to_hash(body)
305
+ parse_query_to_hash(body_raw)
277
306
  elsif @content_type.include?("multipart/form-data")
278
307
  # Extract form fields from Rack's parsed multipart data.
279
308
  # Files are handled separately by extract_files.
@@ -324,12 +353,19 @@ module Tina4
324
353
  if form_hash
325
354
  form_hash.each do |key, value|
326
355
  if value.is_a?(Hash) && value[:tempfile]
327
- result[key] = {
328
- filename: value[:filename],
329
- type: value[:type],
330
- tempfile: value[:tempfile],
331
- size: value[:tempfile].size
332
- }
356
+ tempfile = value[:tempfile]
357
+
358
+ # Indifferent-access per-file hash so file["content"],
359
+ # file[:content], file["filename"], file[:filename] all work.
360
+ # `content` (raw bytes, never base64) is materialised lazily on
361
+ # first access (see FileUpload) — :tempfile-only handlers never
362
+ # buffer large uploads in memory.
363
+ file = FileUpload.new
364
+ file[:filename] = value[:filename]
365
+ file[:type] = value[:type]
366
+ file[:tempfile] = tempfile
367
+ file[:size] = tempfile.size
368
+ result[key] = file
333
369
  end
334
370
  end
335
371
  end
@@ -285,9 +285,19 @@ module Tina4
285
285
  self
286
286
  end
287
287
 
288
- # Stream response from a block for Server-Sent Events (SSE).
288
+ # Stream a response for Server-Sent Events (SSE) / chunked transfer.
289
289
  #
290
- # Usage:
290
+ # Two equivalent call styles (cross-framework parity — Python/PHP/Node
291
+ # pass a generator positionally; Ruby additionally supports a block):
292
+ #
293
+ # # 1. Positional generator (Enumerator, or anything responding to
294
+ # # #each or #call that yields string chunks):
295
+ # gen = Enumerator.new do |y|
296
+ # 10.times { |i| y << "data: message #{i}\n\n" }
297
+ # end
298
+ # response.stream(gen)
299
+ #
300
+ # # 2. Block form (unchanged):
291
301
  # Tina4::Router.get "/events" do |request, response|
292
302
  # response.stream do |out|
293
303
  # 10.times do |i|
@@ -297,16 +307,18 @@ module Tina4
297
307
  # end
298
308
  # end
299
309
  #
310
+ # @param generator [#each, #call, nil] Optional source of string chunks.
300
311
  # @param content_type [String] Content type (default: text/event-stream)
301
312
  # @yield [Enumerator::Yielder] Block receives a yielder to push chunks
302
313
  # @return [self]
303
- def stream(content_type: "text/event-stream", &block)
314
+ def stream(generator = nil, content_type: "text/event-stream", &block)
304
315
  @status_code = @status_code || 200
305
316
  @headers["content-type"] = content_type
306
317
  @headers["cache-control"] = "no-cache"
307
318
  @headers["connection"] = "keep-alive"
308
319
  @headers["x-accel-buffering"] = "no"
309
320
  @_streaming = true
321
+ @_stream_generator = generator
310
322
  @_stream_block = block
311
323
  self
312
324
  end
@@ -330,9 +342,24 @@ module Tina4
330
342
  final_headers["set-cookie"] = @cookies.join("\n") if @cookies && !@cookies.empty?
331
343
 
332
344
  if @_streaming
333
- # Streaming mode — return an Enumerator as the body
345
+ # Streaming mode — return an Enumerator as the Rack body. A positional
346
+ # generator wins over a block when both are somehow present.
347
+ gen = @_stream_generator
348
+ blk = @_stream_block
334
349
  body = Enumerator.new do |yielder|
335
- @_stream_block.call(yielder)
350
+ if gen
351
+ if gen.respond_to?(:each)
352
+ # Enumerator / array / any Enumerable of string chunks
353
+ gen.each { |chunk| yielder << chunk }
354
+ elsif gen.respond_to?(:call)
355
+ # Callable that receives the yielder, like the block form
356
+ gen.call(yielder)
357
+ else
358
+ yielder << gen.to_s
359
+ end
360
+ elsif blk
361
+ blk.call(yielder)
362
+ end
336
363
  end
337
364
  return [@status_code, final_headers, body]
338
365
  end
data/lib/tina4/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tina4
4
- VERSION = "3.13.29"
4
+ VERSION = "3.13.31"
5
5
  end
data/lib/tina4/wsdl.rb CHANGED
@@ -116,9 +116,18 @@ module Tina4
116
116
  def handle
117
117
  return generate_wsdl if @request.nil?
118
118
 
119
+ # SOAP bodies are raw XML, so read the raw bytes. On a real
120
+ # Tina4::Request use body_raw (request.body now returns the PARSED
121
+ # payload); test stubs that only expose `body` fall back to it.
122
+ raw_body = if @request.respond_to?(:body_raw)
123
+ @request.body_raw
124
+ elsif @request.respond_to?(:body)
125
+ @request.body
126
+ end
127
+
119
128
  method = if @request.respond_to?(:method)
120
129
  @request.method.to_s.upcase
121
- elsif @request.respond_to?(:body) && @request.body && !@request.body.to_s.empty?
130
+ elsif raw_body && !raw_body.to_s.empty?
122
131
  "POST"
123
132
  else
124
133
  "GET"
@@ -131,11 +140,7 @@ module Tina4
131
140
  return generate_wsdl
132
141
  end
133
142
 
134
- body = if @request.respond_to?(:body)
135
- @request.body.is_a?(String) ? @request.body : @request.body.to_s
136
- else
137
- ""
138
- end
143
+ body = raw_body.is_a?(String) ? raw_body : raw_body.to_s
139
144
 
140
145
  process_soap(body)
141
146
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tina4ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.29
4
+ version: 3.13.31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tina4 Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-16 00:00:00.000000000 Z
11
+ date: 2026-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack