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 279
    {
      'string' => [::String], 'boolean' => [::TrueClass, ::FalseClass],
      'number' => [::Numeric], 'integer' => [::Integer]
    }.freeze

Instance Method Summary

  • #from_json(json_payload) Internal use only

    Inbound: builds from the wire.

  • #new(**kwargs) Internal use only
  • #check_outbound_primitive(field, value) private Internal use only

    Outbound mirror of check_primitive: a primitive-typed arg (string/integer/…) must be the matching Ruby type, so a caller mistake (a string width, a float count) is a local ArgumentError here rather than a rejection the browser reports a round-trip later.

  • #check_outbound_scalar(field, value) private Internal use only

    Outbound mirror of scalar_value: a bare map key must match one of the arm's primitives.

  • #check_outbound_shape(field, value) private Internal use only

    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.

  • #check_primitive(field, raw) private Internal use only
  • #check_shape(field, raw) private Internal use only

    A declared list must arrive as an array; a scalar-shaped field (enum or ref, not a list) must not.

  • #enum_hash(field) private Internal use only
  • #extra(json_payload) private Internal use only
  • #fixed?(field) ⇒ Boolean private Internal use only
  • #missing_required(field) private Internal use only

    A required field absent from the response is tolerated as omitted (UNSET) and warned, so a schema ahead of the browser does not block the caller; strict mode (SE_BIDI_STRICT) escalates to an error for callers who want it.

  • #read(field, raw) private Internal use only
  • #read_list(field, raw, klass) private Internal use only

    Parses each element.

  • #read_map_entry(field, element, klass) private Internal use only

    A map entry is a [key, value] pair.

  • #read_ref(field, raw) private Internal use only

    Reads a ref-typed value into its class.

  • #scalar_value(field, value) private Internal use only

    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.

  • #validate_const(field, value) private Internal use only

    A nullable constant (literal / null) is caller-settable but its only non-null value is the literal, so a value that is neither the literal nor nil (nil is handled above) is a local error rather than a wire round-trip.

  • #validate_present(field, value) private Internal use only

    Checks a field that carries an actual value (neither omitted nor nil): a nullable-const field against its literal, list/scalar shape, primitive type (lists excepted, as inbound does), ref type, and enum membership (resolved lazily so a cross-domain enum need not load first).

  • #validate_ref(field, value) private Internal use only

    Outbound mirror of read_ref: a ref-typed value must be the type it declares, so a wrong record or a value no union variant accepts is a caller error caught here, not a browser round-trip.

  • #validate_ref_entry(field, klass, element) private Internal use only

    A [key, value] map entry: the key may be a variant or a bare scalar, the value is a variant.

  • #validate_ref_list(field, klass, list) private Internal use only

    Mirrors read_list: a scalar field is a [key, value] map, a nested list recurses, otherwise each element is checked against the ref.

  • #validate_ref_value(field, klass, value) private Internal use only

    A record ref must be an instance of that record; a union ref must be one the union accepts.

  • #validate_values(attributes) private Internal use only

    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), a nullable-const field must carry its literal (not some other value), a primitive field must be the matching Ruby type, and an enum field must be in its allowed set.

  • #warn_undeclared(undeclared) private Internal use only

    Forward-compat signal: a property a closed type does not model is dropped and warned so schema drift is visible (an extensible type keeps its extras silently — the spec sanctions them).

  • #wire_value(field, json_payload) private Internal use only

Instance Method Details

#check_outbound_primitive(field, value) (private)

Outbound mirror of check_primitive: a primitive-typed arg (string/integer/…) must be the matching Ruby type, so a caller mistake (a string width, a float count) is a local ArgumentError here rather than a rejection the browser reports a round-trip later. A field with no primitive descriptor (enum, ref, opaque) passes; lists are skipped, as inbound does.

Raises:

  • (::ArgumentError)
[ GitHub ]

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

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

  raise ::ArgumentError, "#{name}##{field.name} expected #{field.primitive}, got #{value.inspect}"
end

#check_outbound_scalar(field, value) (private)

Outbound mirror of scalar_value: a bare map key must match one of the arm's primitives.

Raises:

  • (::ArgumentError)
[ GitHub ]

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

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

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

#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 194

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 284

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

  raise Error::SerializationError, "#{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 267

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

  raise Error::SerializationError,
        "#{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 291

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 339

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 213

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

#from_json(json_payload)

Inbound: builds from the wire. A missing required field is omitted and warned (or raised in strict mode, in #wire_value); enum tokens are mapped back to symbols and an unrecognized one raises (in #read); an undeclared property is captured silently (extensible) or warned and dropped (closed) — strict on shape, lenient on extras.

[ GitHub ]

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

def from_json(json_payload)
  unless json_payload.is_a?(::Hash)
    raise Error::SerializationError, "#{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
  undeclared = extra(json_payload)
  if extensible?
    attributes[:extensions] = undeclared # the spec sanctions these extras; preserve them silently
  else
    warn_undeclared(undeclared) unless undeclared.empty?
  end
  construct(**attributes)
end

#missing_required(field) (private)

A required field absent from the response is tolerated as omitted (UNSET) and warned, so a schema ahead of the browser does not block the caller; strict mode (SE_BIDI_STRICT) escalates to an error for callers who want it. Omitted (UNSET) stays distinct from an explicit null (nil), which matters for the required-and-nullable fields the schema flags.

[ GitHub ]

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

def missing_required(field)
  message = "#{name}##{field.name} is required but was missing from the response"
  raise Error::SerializationError, message if Serialization.strict?

  WebDriver.logger.warn(message, id: :bidi_missing_required)
  UNSET
end

#new(**kwargs)

[ GitHub ]

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

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 237

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

    raise Error::SerializationError, "#{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 298

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 315

def read_map_entry(field, element, klass)
  unless element.is_a?(::Array) && element.size == 2
    raise Error::SerializationError,
          "#{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 258

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 331

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::SerializationError,
        "#{name}##{field.name} expected #{Array(field.scalar).join(' or ')}, got #{value.inspect}"
end

#validate_const(field, value) (private)

A nullable constant (literal / null) is caller-settable but its only non-null value is the literal, so a value that is neither the literal nor nil (nil is handled above) is a local error rather than a wire round-trip. A non-const field carries UNSET here and passes.

Raises:

  • (::ArgumentError)
[ GitHub ]

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

def validate_const(field, value)
  return if UNSET.equal?(field.const) || value == field.const

  raise ::ArgumentError, "#{name}##{field.name} must be #{field.const.inspect}, got #{value.inspect}"
end

#validate_present(field, value) (private)

Checks a field that carries an actual value (neither omitted nor nil): a nullable-const field against its literal, list/scalar shape, primitive type (lists excepted, as inbound does), ref type, and enum membership (resolved lazily so a cross-domain enum need not load first).

[ GitHub ]

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

def validate_present(field, value)
  validate_const(field, value)
  check_outbound_shape(field, value)
  check_outbound_primitive(field, value) unless field.list
  validate_ref(field, value) if field.ref
  Serialization.validate!("#{name}##{field.name}", value, Protocol.const_get(field.enum)) if field.enum
end

#validate_ref(field, value) (private)

Outbound mirror of read_ref: a ref-typed value must be the type it declares, so a wrong record or a value no union variant accepts is a caller error caught here, not a browser round-trip. Shape is already checked, so a list is an Array.

[ GitHub ]

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

def validate_ref(field, value)
  klass = (@refs ||= {})[field.name] ||= Protocol.const_get(field.ref)
  field.list ? validate_ref_list(field, klass, value) : validate_ref_value(field, klass, value)
end

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

A [key, value] map entry: the key may be a variant or a bare scalar, the value is a variant.

[ GitHub ]

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

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

  key, value = element
  key.is_a?(Serializable) ? validate_ref_value(field, klass, key) : check_outbound_scalar(field, key)
  validate_ref_value(field, klass, value)
end

#validate_ref_list(field, klass, list) (private)

Mirrors read_list: a scalar field is a [key, value] map, a nested list recurses, otherwise each element is checked against the ref.

[ GitHub ]

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

def validate_ref_list(field, klass, list)
  list.each do |element|
    if field.scalar
      validate_ref_entry(field, klass, element)
    elsif element.is_a?(::Array)
      validate_ref_list(field, klass, element)
    else
      validate_ref_value(field, klass, element)
    end
  end
end

#validate_ref_value(field, klass, value) (private)

A record ref must be an instance of that record; a union ref must be one the union accepts.

Raises:

  • (::ArgumentError)
[ GitHub ]

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

def validate_ref_value(field, klass, value)
  return if klass < Union ? klass.valid_outbound?(value) : value.is_a?(klass)

  raise ::ArgumentError, "#{name}##{field.name} expected #{field.ref}, 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), a nullable-const field must carry its literal (not some other value), a primitive field must be the matching Ruby type, 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/primitive/enum are checked separately in wire_value+/+read.

[ GitHub ]

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

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)

    validate_present(f, value)
  end
end

#warn_undeclared(undeclared) (private)

Forward-compat signal: a property a closed type does not model is dropped and warned so schema drift is visible (an extensible type keeps its extras silently — the spec sanctions them). Tagged :bidi_undeclared_property so a caller can silence it via logger.ignore.

[ GitHub ]

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

def warn_undeclared(undeclared)
  undeclared.each_key do |key|
    WebDriver.logger.warn("#{name} received an undeclared property: #{key.inspect}",
                          id: :bidi_undeclared_property)
  end
end

#wire_value(field, json_payload) (private)

[ GitHub ]

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

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

  missing_required(field)
end