Module: OmniSocials::Internal

Defined in:
lib/omnisocials/internal.rb

Overview

Internal helpers shared by the client core and the resource classes. Not part of the public API.

Constant Summary collapse

MAX_BACKOFF_SECONDS =
8.0

Class Method Summary collapse

Class Method Details

.backoff_delay(attempt, retry_after) ⇒ Object

Delay before retry number attempt (0-based): 0.5s, 1s, 2s... + jitter.

An explicit Retry-After from the server wins over the computed backoff.



77
78
79
80
81
82
# File 'lib/omnisocials/internal.rb', line 77

def backoff_delay(attempt, retry_after)
  return [retry_after, 60.0].min unless retry_after.nil?

  delay = [0.5 * (2**attempt), MAX_BACKOFF_SECONDS].min
  delay * (0.75 + rand * 0.5)
end

.coerce_file(file, filename) ⇒ Object

Normalize the file argument to [filename, binary_data].

Accepts:

  • an IO / File / StringIO (anything responding to #read)
  • a file path (a String or Pathname)
  • raw bytes (a binary-encoded String, e.g. from File.binread)

The content is read fully into memory so automatic retries can resend it.

Raises:

  • (ArgumentError)


92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/omnisocials/internal.rb', line 92

def coerce_file(file, filename)
  case file
  when Pathname
    path = file.to_s
    return [filename || File.basename(path), File.binread(path)]
  when String
    # A binary-encoded String is treated as raw bytes; any other String
    # is treated as a file path (mirrors the Python SDK where str = path).
    return [filename || "upload", file] if file.encoding == Encoding::BINARY

    return [filename || File.basename(file), File.binread(file)]
  end

  if file.respond_to?(:read)
    data = file.read.to_s.b
    inferred = file.respond_to?(:path) ? file.path : nil
    resolved = filename
    resolved ||= File.basename(inferred) if inferred.is_a?(String) && !inferred.empty?
    return [resolved || "upload", data]
  end

  raise ArgumentError,
        "file must be an IO, a file path (String or Pathname), or raw bytes " \
        "(a binary-encoded String, e.g. from File.binread); got #{file.class}."
end

.drop_nil(hash) ⇒ Object

Drop nil and NOT_GIVEN values (top level only).

Nested values are passed through untouched, so platform option hashes can still carry meaningful nils (e.g. x: { "thread_parts" => nil } on update clears an X thread).



33
34
35
# File 'lib/omnisocials/internal.rb', line 33

def drop_nil(hash)
  hash.reject { |_key, value| value.nil? || value.is_a?(NotGiven) }
end

.join_list(value) ⇒ Object

Join an array into a comma-separated string; pass strings through.



38
39
40
41
42
43
# File 'lib/omnisocials/internal.rb', line 38

def join_list(value)
  return nil if value.nil?
  return value if value.is_a?(String)

  value.map(&:to_s).join(",")
end

.multipart_body(boundary, fields, filename, data) ⇒ Object

Build a multipart/form-data body (single file part named "file" plus optional plain form fields), hand-rolled so the SDK stays dependency-free.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/omnisocials/internal.rb', line 120

def multipart_body(boundary, fields, filename, data)
  body = String.new(encoding: Encoding::BINARY)
  fields.each do |name, value|
    body << "--#{boundary}\r\n".b
    body << "Content-Disposition: form-data; name=\"#{name}\"\r\n\r\n".b
    body << value.to_s.b
    body << "\r\n".b
  end
  safe_name = filename.to_s.gsub('"', "%22").gsub(/[\r\n]/, " ")
  body << "--#{boundary}\r\n".b
  body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{safe_name}\"\r\n".b
  body << "Content-Type: application/octet-stream\r\n\r\n".b
  body << data.to_s.b
  body << "\r\n--#{boundary}--\r\n".b
  body
end

.parse_body(raw) ⇒ Object

Parse a response body as JSON, falling back to the raw text.



64
65
66
67
68
69
70
71
72
# File 'lib/omnisocials/internal.rb', line 64

def parse_body(raw)
  return nil if raw.nil? || raw.empty?

  begin
    JSON.parse(raw)
  rescue JSON::ParserError
    raw
  end
end

.serialize_query(params) ⇒ Object

Stringify query params, dropping nil/NOT_GIVEN values.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/omnisocials/internal.rb', line 46

def serialize_query(params)
  return nil if params.nil? || params.empty?

  out = {}
  params.each do |key, value|
    next if value.nil? || value.is_a?(NotGiven)

    out[key.to_s] =
      case value
      when true then "true"
      when false then "false"
      else value.to_s
      end
  end
  out.empty? ? nil : out
end