123456789_123456789_123456789_123456789_123456789_

Module: Selenium::WebDriver::BiDi::Serialization::Record::Deserializer Private

Relationships & Source Files
Defined in: rb/lib/selenium/webdriver/bidi/serialization/record.rb

Overview

Inbound construction: the keyword #new (validated) and the wire #from_json.

Constant Summary

  • PRIMITIVE_TYPES =

    Ruby classes a checkable primitive admits. number is any Numeric (JSON has one number type); integer requires an Integer — a browser emits 5, not 5.0, for an integer (JS has no int/float split), so this rarely false-positives yet still rejects a genuine non-integer like 1.5. A field with no primitive descriptor is left unchecked.

    # File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 179
    {
      'string' => [::String], 'boolean' => [::TrueClass, ::FalseClass],
      'number' => [::Numeric], 'integer' => [::Integer]
    }.freeze

Instance Method Summary

Instance Method Details

#check_outbound_shape(field, value) (private)

Outbound mirror of check_shape: a list-typed arg must be an array, a scalar-shaped one (enum or ref, not a list) must not — a local ArgumentError, not a wire round-trip.

Raises:

  • (::ArgumentError)
[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 117

def check_outbound_shape(field, value)
  return if field.list == value.is_a?(::Array)
  return unless field.list || field.enum || field.ref

  kind = field.list ? 'a list' : 'a single value'
  raise ::ArgumentError, "#{name}##{field.name} expected #{kind}, got #{value.inspect}"
end

#check_primitive(field, raw) (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 184

def check_primitive(field, raw)
  expected = PRIMITIVE_TYPES[field.primitive]
  return if expected.nil? || expected.any? { |type| raw.is_a?(type) }

  raise Error::WebDriverError, "#{name}##{field.name} expected #{field.primitive}, got #{raw.inspect}"
end

#check_shape(field, raw) (private)

A declared list must arrive as an array; a scalar-shaped field (enum or ref, not a list) must not. An opaque field carries no shape descriptor, so it passes through.

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 167

def check_shape(field, raw)
  return if field.list == raw.is_a?(::Array)
  return unless field.list || field.enum || field.ref

  raise Error::WebDriverError,
        "#{name}##{field.name} expected #{field.list ? 'a list' : 'a single value'}, got #{raw.inspect}"
end

#enum_hash(field) (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 191

def enum_hash(field)
  (@enums ||= {})[field.name] ||= Protocol.const_get(field.enum)
end

#extra(json_payload) (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 239

def extra(json_payload)
  known = (@wire_keys ||= fields.map(&:wire_key))
  json_payload.except(*known)
end

#fixed?(field) ⇒ Boolean (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 125

def fixed?(field)
  !UNSET.equal?(field.fixed)
end

#from_json(json_payload)

Inbound: builds from the wire. A missing required field raises (in #wire_value), enum tokens are mapped back to symbols and an unrecognized one raises (in #read), and extra keys are captured (extensible) or ignored (closed) — strict on shape, lenient on extras.

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 84

def from_json(json_payload)
  unless json_payload.is_a?(::Hash)
    raise Error::WebDriverError, "#{name} expected an object on the wire, got #{json_payload.inspect}"
  end

  attributes = fields.to_h do |f|
    [f.name, wire_value(f, json_payload)]
  end
  attributes[:extensions] = extra(json_payload) if extensible?
  construct(**attributes)
end

#new(**kwargs)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 71

def new(**kwargs)
  # Start from what was passed so ::Data's constructor rejects an unknown key, then fill
  # each field with its value or UNSET (omitted), forcing fixed discriminators.
  attributes = kwargs.dup
  fields.each { |f| attributes[f.name] = fixed?(f) ? f.fixed : attributes.fetch(f.name, UNSET) }
  attributes[:extensions] = kwargs.fetch(:extensions, {}) if extensible?
  validate_values(attributes)
  construct(**attributes)
end

#read(field, raw) (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 137

def read(field, raw)
  if raw.nil?
    return raw if field.nullable

    raise Error::WebDriverError, "#{name}##{field.name} received null but is not nullable"
  end
  check_shape(field, raw)
  return Serialization.to_symbol("#{name}##{field.name}", raw, enum_hash(field)) if field.enum

  if field.ref.nil?
    check_primitive(field, raw) unless field.list
    return raw
  end

  read_ref(field, raw)
end

#read_list(field, raw, klass) (private)

Parses each element. A scalar field is a map encoded as [key, value] pairs, so every element must be a 2-item pair — each is read as one, and a malformed entry is rejected. Non-scalar lists recurse into nested lists; other elements deserialize.

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 198

def read_list(field, raw, klass)
  raw.map do |element|
    if field.scalar
      read_map_entry(field, element, klass)
    elsif element.is_a?(::Array)
      read_list(field, element, klass)
    else
      klass.from_json(element)
    end
  end
end

#read_map_entry(field, element, klass) (private)

A map entry is a [key, value] pair. The key is Ref / text — an object key deserializes, a bare-string key passes through once validated against the arm's primitive. The value is the object-only Ref and always deserializes, so a bare scalar there is rejected (object_only holds at the value position). A non-pair element is a malformed entry and is rejected outright.

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 215

def read_map_entry(field, element, klass)
  unless element.is_a?(::Array) && element.size == 2
    raise Error::WebDriverError,
          "#{name}##{field.name} expected a [key, value] pair, got #{element.inspect}"
  end

  key, value = element
  key = key.is_a?(::Hash) ? klass.from_json(key) : scalar_value(field, key)
  [key, klass.from_json(value)]
end

#read_ref(field, raw) (private)

Reads a ref-typed value into its class. A scalar position is an inline union with a scalar arm collapsed onto its union ref (a map's string keys): a non-object leaf passes through instead of being handed to the object_only union, but only when it matches the arm's primitive (scalar carries it). A list recurses per element.

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 158

def read_ref(field, raw)
  klass = (@refs ||= {})[field.name] ||= Protocol.const_get(field.ref)
  return read_list(field, raw, klass) if field.list

  field.scalar && !raw.is_a?(::Hash) ? scalar_value(field, raw) : klass.from_json(raw)
end

#scalar_value(field, value) (private)

A bare scalar at a scalar-tolerant union position must match one of the union's scalar-arm primitives (scalar is a primitive name or an array of them); a wrong-typed scalar (a number where a string is expected) is a wire error, not something to pass through. An unrecognized primitive (none in PRIMITIVE_TYPES) is left unchecked, matching the lenient default elsewhere.

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 231

def scalar_value(field, value)
  expected = Array(field.scalar).flat_map { |primitive| PRIMITIVE_TYPES[primitive] || [] }
  return value if expected.empty? || expected.any? { |type| value.is_a?(type) }

  raise Error::WebDriverError,
        "#{name}##{field.name} expected #{Array(field.scalar).join(' or ')}, got #{value.inspect}"
end

#validate_values(attributes) (private)

Checks each field's value: a required field cannot be omitted (UNSET), a non-nullable field cannot be nil (nil is neither a value nor the UNSET omit-sentinel, so it would be silently dropped on the wire), and an enum field must be in its allowed set. The enum constant is resolved lazily so a cross-domain enum need not be loaded first. Outbound only (from #new); inbound presence/enum are checked separately in wire_value+/+read.

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 103

def validate_values(attributes)
  fields.each do |f|
    value = attributes[f.name]
    raise ::ArgumentError, "#{name}##{f.name} is required" if UNSET.equal?(value) && f.required
    raise ::ArgumentError, "#{name}##{f.name} cannot be nil" if value.nil? && !f.nullable
    next if value.nil? || UNSET.equal?(value)

    check_outbound_shape(f, value)
    Serialization.validate!("#{name}##{f.name}", value, Protocol.const_get(f.enum)) if f.enum
  end
end

#wire_value(field, json_payload) (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/bidi/serialization/record.rb', line 129

def wire_value(field, json_payload)
  return field.fixed if fixed?(field)
  return read(field, json_payload[field.wire_key]) if json_payload.key?(field.wire_key)
  return UNSET unless field.required

  raise Error::WebDriverError, "#{name}##{field.name} is required but was missing from the response"
end