Class: BiDiGenerate::Schema Private
| Relationships & Source Files | |
| Inherits: | Object |
| Defined in: | rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb |
Constant Summary
-
BOUND_KEYS =
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1318
The schema's numeric bound keys, in the order the field facts render them.
%w[min max exclusiveMin exclusiveMax].freeze
-
CHECKABLE_PRIMITIVES =
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1314
The scalar primitives that carry an inbound type-check. A field with no primitive (a ref, const, or opaque value) gets no descriptor and is left unchecked — lenient, so a missed check fails open rather than a wrong strict default rejecting valid data.
%w[string number integer boolean].freeze
-
NUMERIC_PRIMITIVES =
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1315%w[number integer].freeze
-
OPAQUE =
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 939
Resolution for anything not modeled as a value type (scalar, enum, empty record). Frozen because it is shared across callers.
{ref: nil, list: false, rbs: 'untyped'}.freeze -
PRIMITIVE_RBS =
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1307
Every primitive the projector can emit maps to an RBS type.
unknownis intentionally absent — the projector rejects it (an unhandled CDDL construct fails the build), so it never reaches here; any other unlisted primitive fails generation at scalar_rbs rather than slipping through as untyped.{ 'string' => 'String', 'number' => 'Numeric', 'integer' => 'Integer', 'boolean' => 'bool', 'null' => 'nil' }.freeze
Class Method Summary
-
.new(schema) ⇒ Schema
constructor
Internal use only
Vendor content (schema
vendor.) is folded in beside the spec's: a vendor module's commands and events become a domain of their own, and its types resolve like spec types and are emitted in that module (see owned_types).
Instance Method Summary
-
#build_added_command(cmd, namespace, domain)
Internal use only
The vendor variant already scopes the command, so its name drops the namespace (
moz:listExtensions→ list_extensions); a name that would silently override a spec command fails generation instead. -
#build_vendor_override(cmd, type_name, fields, namespace)
Internal use only
The vendor variant's params record and the command that builds it, as [record, command].
-
#command_wrapper_refs(domain)
Internal use only
The domain's command param/result wrapper type names — the classes a command constructs (
params) or parses its result into. - #commands_for(domain) Internal use only
-
#domain_href(domain)
Internal use only
The domain's
#module-spec link, or nil when the schema has none. -
#domain_relative_path(name)
Internal use only
The class path to a type relative to its domain class (an accessor body resolves in the Domain subclass scope): "ExtensionData", or "AccessibilityLocator::Value" for a synthetic nested under its owner.
-
#domains
Internal use only
Domains that carry a command or event each become one generated module.
-
#enums_for(domain)
Internal use only
Enumtypes declared under "." become nested constant modules. -
#envelope_synthetic?(type) ⇒ Boolean
Internal use only
A synthetic record lifted out as an envelope's params (its owner is an envelope).
-
#error_codes
Internal use only
The protocol-root ErrorCode enum's wire values (e.g. "no such frame"), in schema order.
- #events_for(domain) Internal use only
-
#fold_vendor_extensions!
Internal use only
A vendor's fields on a spec type are declared fields (ADR 17786), so they are typed and never ride the extensions map.
-
#hoist_vendor_enum(type_name, field)
Internal use only
The projector leaves a vendor field's enum inline (
moz:scope), so name it after the field in the extended type's domain (browsingContext.MozScope) to emit and validate it like a spec enum. -
#message_envelope?(type) ⇒ Boolean
Internal use only
A protocol message envelope is a record with a baked
methoddiscriminator ({method: <const>, params: …}) — the wire shape of a command/event message. -
#owned_types(domain)
Internal use only
The types a domain's module declares.
-
#params_command(type_name)
Internal use only
The spec command that sends a type as its params, or nil.
-
#params_for(params_ref)
Internal use only
Flat params for a command: the record's fields, or — for a union of records — the merged superset of variant fields.
-
#plainly_reached_types
Internal use only
Type names reached by at least one non-union-arm reference: used as a record field, list element, map value, or alias target somewhere — not solely as a named union's variant.
-
#promote_command_params_records!
Internal use only
A command written in CDDL map form carries its params as an inline object (rather than the usual group form referencing a named params type).
-
#promote_to_domain_type!(name)
Internal use only
Strip the synthetic/owner/label tags so a lifted-out type emits as a top-level domain record instead of nesting under its (suppressed) envelope.
-
#ruby_path_for(name)
Internal use only
Public ruby-path resolver (
Owner::Labelfor a synthetic), matching how a variant's ref is emitted — so a caller can map a variant ref back to its emitted record. -
#structured_ref(name)
Internal use only
The Protocol-relative class path a command result parses into, or nil when it is non-structured (or a bare list, returned raw).
-
#suppressed_record?(type) ⇒ Boolean
Internal use only
Records the generator deliberately does not emit: a message envelope, or a synthetic params record lifted out of one.
- #type_kind(ref) Internal use only
- #type_prefixes(domain) Internal use only
-
#types_for(domain)
Internal use only
Structured value classes (records + discriminated unions) declared under "
." Empty records are projector artifacts with nothing to carry, so they stay opaque hashes; only non-empty records and unions become classes. -
#vendor_added_commands(domain, namespace)
Internal use only
The commands a vendor defines in a spec domain (
webExtension.moz:listExtensions). -
#vendor_domain?(domain) ⇒ Boolean
Internal use only
A vendor module's domain keeps its wire prefix (
moz:debugging); a spec domain has none. -
#vendor_events
Internal use only
A vendor event in a spec domain would need the same vendor-variant routing as a command; none exists yet, so fail generation rather than leak it into the shared class.
-
#vendor_extension_fields(namespace, type_name, entry)
Internal use only
Also records the vendor types the fields reference, so vendor_types_reached emits them.
-
#vendor_field(field, namespace, taken)
Internal use only
A vendor field's ruby name drops its namespace prefix (
moz:permanent→ permanent): the module already scopes it, so re-encoding the namespace in every identifier is redundant. - #vendor_messages(kind) Internal use only
-
#vendor_modules_for(domain)
Internal use only
The vendor modules a domain carries, one per namespace (
moz→ moduleMoz). -
#vendor_types_reached(domain)
Internal use only
The vendor types a vendor-added command in this spec domain reaches through its params and result, or a vendor field folded onto a spec type references, so they are emitted in the domain's module beside what uses them.
-
#annotate_discriminator_enum!(params, selector)
private
Internal use only
A discriminated union's
byfield is validated against the whole allowed set: the const values that tag each variant plus the default variant's own enum values (e.g. continueWithAuth.action =provideCredentials+cancel). -
#baked_discriminator?(field) ⇒ Boolean
private
Internal use only
A const field is a baked discriminator tag, unless it is also nullable: the spec's
literal | null(browsingContext.setBypassCSP, emulation.setScriptingEnabled) is a settable value (the literal to set, null to clear), so it stays a normal field that can serialize null rather than a fixed tag that can only ever emit the literal. -
#checkable_primitive(node)
private
Internal use only
Reached through alias chains too (resolve_named), so js-uint is checked as an integer.
- #default_variant_enum_values(selector, by) private Internal use only
-
#directionality(name)
private
Internal use only
The type's send/receive tags (schema
outbound/inbound) as constructor kwargs, coerced to plain booleans — shared by every structured-type builder. - #discriminated_variants(selector) private Internal use only
- #domain_path(name) private Internal use only
-
#enum_const(field_type)
private
Internal use only
The allowed-values constant path when a field (or a list's element) is an enum type, else nil.
- #extensible_record?(name) ⇒ Boolean private Internal use only
- #field_ir(field, extensible) private Internal use only
- #field_name(field, extensible) private Internal use only
-
#guard_union_dispatch_keys_simple!(selector, ref)
private
Internal use only
Union.buildmatches the command's kwargs to the selector's dispatch keys by symbol, which holds only while each dispatch wire key equals its ruby kwarg. -
#leaf_const(node, seen = {})
private
Internal use only
The literal value of a const field, following alias chains, so the runtime can reject a value that is neither the literal nor null (a
literal / nullparam such as emulation.setScriptingEnabled'senabled). -
#map_record(type)
private
Internal use only
A field-less record is either a genuinely empty one (EmptyParams, Extensible — opaque) or the projector's stand-in for an inline map (
{*text => text}is hoisted to a record with no fields and the value type undermap, e.g. script.NodePropertiesAttributes). -
#map_value_primitives(node)
private
Internal use only
Each arm's checkable primitive, following aliases (js-int is an integer); nil if any arm has none.
-
#merged_params(variant_fields, extensible)
private
Internal use only
Merge variant field lists into one flat param superset.
-
#named_type(name)
private
Internal use only
A named structured type's serialization ref (nil for a dotless/global type, never emitted as a class) and its absolute RBS class path, derived independently so each output keeps its own treatment of dotless names.
-
#named_union(name)
private
Internal use only
Like named_type, but a union is typed to its value alias (variant | variant | …), not its class — the class has no instances, so a variant must be assignable where the union is expected.
-
#narrow_bounds(bounds, narrowing)
private
Internal use only
A value must satisfy both, so each side keeps the tighter bound, and at an equal value the exclusive one.
-
#narrowed(named, node)
private
Internal use only
A ref's own bounds narrow those of the alias it names (
js-uint {.ge} 1is 1..2**53-1). - #nilable(type, flag) private Internal use only
- #ordered_variants(selector) private Internal use only
-
#plain_refs(node)
private
Internal use only
The refs a node exposes through a NON-arm position: a record's fields and map value, or an alias's target.
- #rbs_abs(path) private Internal use only
- #rbs_const(value) private Internal use only
- #rbs_type(node) private Internal use only
- #record_class(name, type) private Internal use only
- #record_params(fields, extensible) private Internal use only
-
#refs_in_type(node)
private
Internal use only
Every type name a type expression references (mirrors the projector's refsInType), descending list element, map value, inline union arms, and inline record fields.
-
#resolve(node)
private
Internal use only
Projects a schema type node to
list:, nullable:, primitive:, bounds:, rbs:. -
#resolve_list(element_node)
private
Internal use only
A list's facts describe its element, so
primitive(likeref) is the element's. -
#resolve_named(name, seen = {})
private
Internal use only
Resolves a named ref to the same
list:, rbs:facts, transparently following aliases — including alias-to-list — so an element type behind an alias (e.g. script.ListLocalValue -> [script.LocalValue]) is preserved. - #resolve_named_alias(name, inner, seen) private Internal use only
-
#resolve_union(node, nullable)
private
Internal use only
An inline union of one union-typed arm plus scalars (e.g. a MappingRemoteValue entry, RemoteValue / string) is carried as that union ref so nested entries are typed.
-
#ruby_path(name)
private
Internal use only
Class path, nesting a synthetic type under its owner as
Owner::Labelso a ref resolves to the same nested constant the type is emitted as. -
#scalar_rbs(node)
private
Internal use only
The leaf of #resolve: the bare scalar type, before any nullable wrap.
- #scalar_union(primitives, nullable) private Internal use only
- #tighter_bound?(side, flag, candidate, current) ⇒ Boolean private Internal use only
-
#union_alias_path(name)
private
Internal use only
Absolute RBS path of a union's value alias: its class path with the last segment snake-cased (WebExtension::ExtensionData -> ...::WebExtension::extension_data), matching the
typealias emitted alongside the class. -
#union_bounds(arms)
private
Internal use only
The runtime holds every numeric value a field carries to one set of bounds, so numeric arms that disagree (
js-uint / float) would be checked wrongly; fail generation instead. - #union_class(name) private Internal use only
-
#union_from_alias(name)
private
Internal use only
The sole alias-union is input.Origin ("viewport" | "pointer" | ElementOrigin): a scalar-or-object union the object-payload selector model doesn't cover, so the projector leaves it an alias with no selector.
-
#union_from_selector(name, selector)
private
Internal use only
Map a union
selectorto dispatch variants the template renders: -
#union_params(type, ref = nil)
private
Internal use only
Merge a union's record variants into one flat param list for the command signature.
-
#union_primitives(arms)
private
Internal use only
The primitives a union of scalars admits (pageRanges'
js-uint / text), or nil when any arm is something else (an enum, a const, a ref), which no primitive check can admit. -
#union_ref?(name) ⇒ Boolean
private
Internal use only
True when a ref (following aliases) is a union — the only arm whose from_json tolerates a scalar sibling.
Constructor Details
.new(schema) ⇒ Schema
Vendor content (schema vendor.) is folded in beside the spec's: a vendor
module's commands and events become a domain of their own, and its types resolve like
spec types and are emitted in that module (see owned_types).
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 555
def initialize(schema) @vendor = schema['vendor'] || {} @vendor_types = @vendor.values.map { |section| section['types'] || {} }.reduce({}, :merge) @types = schema['types'].merge(@vendor_types) @commands = schema['commands'] + ('commands').select { |c| vendor_domain?(c['domain']) } @events = schema['events'] + vendor_events @domains = schema['domains'] || {} promote_command_params_records! @vendor_params = fold_vendor_extensions! end
Instance Method Details
#annotate_discriminator_enum!(params, selector) (private)
A discriminated union's by field is validated against the whole allowed set:
the const values that tag each variant plus the default variant's own enum
values (e.g. continueWithAuth.action = provideCredentials + cancel).
That spans variants, so no single enum constant fits — emit an inline symbol=>wire
hash so the check accepts the idiomatic symbol like every other enum.
Boolean discriminators (handleRequestDevicePrompt.accept) need no membership check.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1374
def annotate_discriminator_enum!(params, selector) by = selector['by'] tagged = by ? selector['variants'].map { |v| v['value'] } : [] return unless !tagged.empty? && tagged.all?(String) allowed = (tagged + default_variant_enum_values(selector, by)).uniq pairs = allowed.map { |v| "#{BiDiGenerate.enum_key(v)}: '#{v}'" } param = params.find { |p| p.wire_name == by } return unless param param.enum = "{#{pairs.join(', ')}}" param.rbs = 'Symbol' end
#baked_discriminator?(field) ⇒ Boolean (private)
A const field is a baked discriminator tag, unless it is also nullable: the spec's
literal | null (browsingContext.setBypassCSP, emulation.setScriptingEnabled) is a
settable value (the literal to set, null to clear), so it stays a normal field that
can serialize null rather than a fixed tag that can only ever emit the literal.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1154
def baked_discriminator?(field) field['type'].key?('const') && !field['type']['nullable'] end
#build_added_command(cmd, namespace, domain)
The vendor variant already scopes the command, so its name drops the namespace (moz:listExtensions
→ list_extensions); a name that would silently override a spec command fails generation instead.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 731
def build_added_command(cmd, namespace, domain) command = BiDiGenerate.build_command(self, cmd.merge('name' => cmd['name'].delete_prefix("#{namespace}:"))) spec_names = commands_for(domain).map do |c| BiDiGenerate.safe_method_name(BiDiGenerate.camel_to_snake(c['name'])) end if spec_names.include?(command.method_name) raise "vendor command #{cmd['method']} would override spec command #{domain}.#{command.method_name}" end command end
#build_vendor_override(cmd, type_name, fields, namespace)
The vendor variant's params record and the command that builds it, as [record, command].
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 744
def build_vendor_override(cmd, type_name, fields, namespace) shared = @types[type_name]['fields'] taken = shared.map { |field| field_name(field, false) } type = @types[type_name].merge('fields' => shared + fields.map { |f| vendor_field(f, namespace, taken) }) record = record_class(type_name, type) command = Command.new(wire_name: cmd['method'], method_name: BiDiGenerate.safe_method_name(BiDiGenerate.camel_to_snake(cmd['name'])), params: record_params(type['fields'], false), result_ref: cmd['result'] && structured_ref(cmd['result']['ref']), params_class: record.ruby_name, union_params: false, spec_href: cmd['specHref']) [record, command] end
#checkable_primitive(node) (private)
Reached through alias chains too (resolve_named), so js-uint is checked as an integer.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 981
def checkable_primitive(node) node['primitive'] if CHECKABLE_PRIMITIVES.include?(node['primitive']) end
#command_wrapper_refs(domain)
The domain's command param/result wrapper type names — the classes a command
constructs (params) or parses its result into. They are reachable (so tagged
outbound/inbound) but are the message wrappers a command method already builds,
not data a caller composes, so they are excluded from the type accessors.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 686
def command_wrapper_refs(domain) commands_for(domain).flat_map { |c| [c.dig('params', 'ref'), c.dig('result', 'ref')] }.compact.to_set end
#commands_for(domain)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 678
def commands_for(domain) @commands.select { |c| c['domain'] == domain } end
#default_variant_enum_values(selector, by) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1388
def default_variant_enum_values(selector, by) default = selector['default'] field = default && @types[default]['fields'].find { |f| f['wire'] == by } ref = field && field['type']['ref'] ref && @types[ref] && @types[ref]['kind'] == 'enum' ? @types[ref]['values'] : [] end
#directionality(name) (private)
The type's send/receive tags (schema outbound/inbound) as constructor kwargs,
coerced to plain booleans — shared by every structured-type builder.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1120
def directionality(name) node = @types[name] {outbound: node['outbound'] ? true : false, inbound: node['inbound'] ? true : false} end
#discriminated_variants(selector) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1248
def discriminated_variants(selector) variants = selector['variants'].map do |variant| VariantIR.new(mode: :value, value: variant['value'], ref: ruby_path(variant['ref']), requires: nil) end return variants unless selector['default'] variants << VariantIR.new(mode: :fallback, value: nil, ref: ruby_path(selector['default']), requires: nil) end
#domain_href(domain)
The domain's #module- spec link, or nil when the schema has none.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 642
def domain_href(domain) @domains.dig(domain, 'specHref') end
#domain_path(name) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 897
def domain_path(name) name.include?('.') ? ruby_path(name) : nil end
#domain_relative_path(name)
The class path to a type relative to its domain class (an accessor body resolves in the Domain subclass scope): "ExtensionData", or "AccessibilityLocator::Value" for a synthetic nested under its owner.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 704
def domain_relative_path(name) prefix = "#{BiDiGenerate.snake_to_class_name(BiDiGenerate.camel_to_snake(name.split('.', 2).first))}::" ruby_path(name).sub(/\A#{Regexp.escape(prefix)}/, '') end
#domains
Domains that carry a command or event each become one generated module.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 674
def domains (@commands + @events).map { |entry| entry['domain'] }.uniq end
#enum_const(field_type) (private)
The allowed-values constant path when a field (or a list's element) is an enum type, else nil. Union command-params skip this (their merged superset can blur a discriminator's const vs enum); only flat record params get the outbound check.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1345
def enum_const(field_type) ref = field_type['ref'] || field_type.dig('list', 'ref') return unless ref && @types[ref] && @types[ref]['kind'] == 'enum' BiDiGenerate.enum_const_path(ref) end
#enums_for(domain)
Enum types declared under "
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 791
def enums_for(domain) owned_types(domain).filter_map do |name, type| next unless type['kind'] == 'enum' pairs = type['values'].map { |v| [BiDiGenerate.enum_key(v), v] } Enum.new(constant_name: BiDiGenerate.screaming_snake(name.split('.', 2).last), pairs: pairs, primitive: type['primitive'], spec_href: type['specHref']) end end
#envelope_synthetic?(type) ⇒ Boolean
A synthetic record lifted out as an envelope's params (its owner is an envelope).
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 877
def envelope_synthetic?(type) return false unless type['synthetic'] owner = @types[type['owner']] owner && owner['kind'] == 'record' && (owner) end
#error_codes
The protocol-root ErrorCode enum's wire values (e.g. "no such frame"), in schema order. Used to generate the BiDi-specific Error subclasses. [] when the schema has no ErrorCode.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 843
def error_codes @types.dig('ErrorCode', 'values') || [] end
#events_for(domain)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 770
def events_for(domain) @events.select { |e| e['domain'] == domain } end
#extensible_record?(name) ⇒ Boolean (private)
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1142
def extensible_record?(name) @types.dig(name, 'extensible') ? true : false end
#field_ir(field, extensible) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1158
def field_ir(field, extensible) resolved = resolve(field['type']) ruby_name = field_name(field, extensible) FieldIR.new(ruby_name: ruby_name, wire_key: field['wire'], required: field['required'], nullable: resolved[:nullable], ref: resolved[:ref], list: resolved[:list], enum: enum_const(field['type']), primitive: resolved[:primitive], scalar: resolved[:scalar], const: leaf_const(field['type']), map_of: resolved[:map], bounds: resolved[:bounds] || {}, rbs: resolved[:rbs]) end
#field_name(field, extensible) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1146
def field_name(field, extensible) BiDiGenerate.safe_field_name(BiDiGenerate.camel_to_snake(field['name']), extensible: extensible) end
#fold_vendor_extensions!
A vendor's fields on a spec type are declared fields (ADR 17786), so they are typed and never ride the extensions map. Fields on a spec command's params go to a record of the vendor variant's own (see vendor_modules_for), returned per namespace; fields on any other type join it as optional fields.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 572
def fold_vendor_extensions! @vendor_field_refs = [] @vendor.to_h do |namespace, section| params = {} (section['extends'] || {}).each do |type_name, entry| fields = vendor_extension_fields(namespace, type_name, entry) if params_command(type_name) params[type_name] = fields else optional = fields.map { |field| field.merge('required' => false) } @types[type_name] = @types[type_name].merge('fields' => @types[type_name]['fields'] + optional) end end [namespace, params] end end
#guard_union_dispatch_keys_simple!(selector, ref) (private)
Union.build matches the command's kwargs to the selector's dispatch keys by
symbol, which holds only while each dispatch wire key equals its ruby kwarg.
Every current key is a single lowercase word; fail generation if a new one is
camelCase so the outbound dispatch gets an explicit wire<->ruby mapping then.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1411
def guard_union_dispatch_keys_simple!(selector, ref) keys = selector['by'] ? [selector['by']] : (selector['ordered'] || []).flat_map { |arm| arm['requires'] } camel = keys.reject { |k| BiDiGenerate.camel_to_snake(k) == k } return if camel.empty? raise "union command param #{ref} dispatches on non-snake wire key(s) #{camel.inspect}; " \ 'Union.build matches kwargs to dispatch keys by symbol, so give the outbound ' \ 'dispatch an explicit wire<->ruby mapping before shipping this.' end
#hoist_vendor_enum(type_name, field)
The projector leaves a vendor field's enum inline (moz:scope), so name it after the field in the
extended type's domain (browsingContext.MozScope) to emit and validate it like a spec enum.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 601
def hoist_vendor_enum(type_name, field) values = field['type']['enum'] return field unless values domain = type_name.split('.', 2).first name = "#{domain}.#{BiDiGenerate.snake_to_class_name(BiDiGenerate.camel_to_snake(field['name']))}" enum = {'kind' => 'enum', 'values' => values, 'primitive' => field['type']['primitive']} raise "vendor enum #{name} conflicts with an existing type" if @types.key?(name) && @types[name] != enum @types[name] = enum field.merge('type' => field['type'].slice('nullable').merge('ref' => name)) end
#leaf_const(node, seen = {}) (private)
The literal value of a const field, following alias chains, so the runtime can reject a
value that is neither the literal nor null (a literal / null param such as
emulation.setScriptingEnabled's enabled). Nil for any non-const node — const literals are
never nil, so nil unambiguously means "no const" (a null value is carried by nullable).
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1173
def leaf_const(node, seen = {}) return node['const'] if node.key?('const') return nil unless node.key?('ref') name = node['ref'] type = @types[name] return nil if seen[name] || type.nil? || type['kind'] != 'alias' seen[name] = true leaf_const(type['type'], seen) end
#map_record(type) (private)
A field-less record is either a genuinely empty one (EmptyParams, Extensible — opaque)
or the projector's stand-in for an inline map ({*text => text} is hoisted to a record
with no fields and the value type under map, e.g. script.NodePropertiesAttributes).
A map whose values are a primitive, or a union of primitives (Firefox prefs), becomes a
map descriptor the runtime checks and a typed Hash; a ref-valued one stays opaque for now.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1068
def map_record(type) primitives = map_value_primitives(type['map']) return OPAQUE unless primitives rbs = primitives.map { |primitive| PRIMITIVE_RBS.fetch(primitive) }.join(' | ') {ref: nil, list: false, map: primitives.one? ? primitives.first : primitives, bounds: union_bounds(type['map']['union'] || [type['map']]), rbs: "Hash[String, #{rbs}]"} end
#map_value_primitives(node) (private)
Each arm's checkable primitive, following aliases (js-int is an integer); nil if any arm has none.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1078
def map_value_primitives(node) union_primitives(node['union'] || [node]) if node end
#merged_params(variant_fields, extensible) (private)
Merge variant field lists into one flat param superset. A field is required only when every variant declares it required; variant-specific fields become optional.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1397
def merged_params(variant_fields, extensible) all_fields = variant_fields.flatten all_fields.map { |f| f['wire'] }.uniq.map do |wire| field = all_fields.find { |f| f['wire'] == wire } required = variant_fields.all? { |fields| fields.any? { |f| f['wire'] == wire && f['required'] } } Param.new(ruby_name: field_name(field, extensible), wire_name: wire, required: required, rbs: rbs_type(field['type'])) end end
#message_envelope?(type) ⇒ Boolean
A protocol message envelope is a record with a baked method discriminator
({method: <const>, params: …}) — the wire shape of a command/event message.
No value type carries a const method field, so this is unambiguous.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 872
def (type) type['fields'].any? { |f| f['wire'] == 'method' && f['type'].key?('const') } end
#named_type(name) (private)
A named structured type's serialization ref (nil for a dotless/global type, never emitted as a class) and its absolute RBS class path, derived independently so each output keeps its own treatment of dotless names.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1085
def named_type(name) {ref: domain_path(name), list: false, rbs: rbs_abs(ruby_path(name))} end
#named_union(name) (private)
Like named_type, but a union is typed to its value alias (variant | variant | …), not its class — the class has no instances, so a variant must be assignable where the union is expected. The serialization ref is unchanged (still the union that dispatches inbound).
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1092
def named_union(name) {ref: domain_path(name), list: false, rbs: union_alias_path(name)} end
#narrow_bounds(bounds, narrowing) (private)
A value must satisfy both, so each side keeps the tighter bound, and at an equal value the exclusive one.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1186
def narrow_bounds(bounds, narrowing) result = bounds.dup {'min' => 'exclusiveMin', 'max' => 'exclusiveMax'}.each do |side, flag| next unless narrowing.key?(side) && tighter_bound?(side, flag, narrowing, bounds) result[side] = narrowing[side] result.delete(flag) result[flag] = true if narrowing[flag] end result.slice(*BOUND_KEYS) end
#narrowed(named, node) (private)
A ref's own bounds narrow those of the alias it names (js-uint {.ge} 1 is 1..2**53-1).
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 963
def narrowed(named, node) own = node.slice(*BOUND_KEYS) own.empty? ? named : named.merge(bounds: narrow_bounds(named[:bounds] || {}, own)) end
#nilable(type, flag) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1114
def nilable(type, flag) flag ? BiDiGenerate.rbs_nilable(type) : type end
#ordered_variants(selector) (private)
[ GitHub ]#owned_types(domain)
The types a domain's module declares. A spec domain's are the "moz:debugging) owns the vendor types its commands
and events name (Mozilla prefixes them mozDebugging.), read off their params and result refs.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 805
def owned_types(domain) vendor = vendor_domain?(domain) prefixes = type_prefixes(domain).map { |prefix| "#{prefix}." } added = vendor ? Set.new : vendor_types_reached(domain) @types.select do |name, _| next false unless prefixes.any? { |p| name.start_with?(p) } @vendor_types.key?(name) ? vendor || added.include?(name) : !vendor end end
#params_command(type_name)
The spec command that sends a type as its params, or nil.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 615
def params_command(type_name) @commands.find { |c| c.dig('params', 'ref') == type_name && !vendor_domain?(c['domain']) } end
#params_for(params_ref)
Flat params for a command: the record's fields, or — for a union of records — the merged superset of variant fields. Returns [] for commands with no params, or nil when params can't be flattened (alias, or a union whose variants aren't all records) so the caller forwards verbatim.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 778
def params_for(params_ref) return [] unless params_ref type = @types[params_ref['ref']] return nil unless type case type['kind'] when 'record' then record_params(type['fields'], extensible_record?(params_ref['ref'])) when 'union' then union_params(type, params_ref['ref']) end end
#plain_refs(node) (private)
The refs a node exposes through a NON-arm position: a record's fields and map value, or an alias's target. A named union contributes none — its variants are arm positions (built through the union), so they do not count toward plainly_reached_types.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 904
def plain_refs(node) case node['kind'] when 'record' refs = node['fields'].flat_map { |f| refs_in_type(f['type']) } node['map'] ? refs + refs_in_type(node['map']) : refs when 'alias' then refs_in_type(node['type']) else [] end end
#plainly_reached_types
Type names reached by at least one non-union-arm reference: used as a record field,
list element, map value, or alias target somewhere — not solely as a named union's
variant. A type reached only as a union arm is built through its union (a variant
factory or the command's flattened dispatch), so a nested one needs no accessor; one
reached as a plain field ref (browsingContext.AccessibilityLocator's value) does.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 695
def plainly_reached_types @plainly_reached_types ||= @types.each_value.with_object(Set.new) do |node, reached| plain_refs(node).each { |ref| reached << ref } end end
#promote_command_params_records!
A command written in CDDL map form carries its params as an inline object (rather
than the usual group form referencing a named params type). The projector links the
command to those params, but hoists them into a synthetic record owned by the
command's message envelope. That envelope is suppressed (Transport forms it), so the
synthetic params record would never be emitted even though the command's params ref
points straight at it. Promote it to a top-level domain record so the generator emits
and references it like any other params type. Today this is exactly
userAgentClientHints.setClientHintsOverride.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 654
def promote_command_params_records! @commands.each do |cmd| ref = cmd.dig('params', 'ref') next unless ref type = @types[ref] promote_to_domain_type!(ref) if type && envelope_synthetic?(type) end end
#promote_to_domain_type!(name)
Strip the synthetic/owner/label tags so a lifted-out type emits as a top-level domain record instead of nesting under its (suppressed) envelope.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 666
def promote_to_domain_type!(name) type = @types[name] type&.delete('synthetic') type&.delete('owner') type&.delete('label') end
#rbs_abs(path) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1338
def rbs_abs(path) "::Selenium::WebDriver::BiDi::Protocol::#{path}" end
#rbs_const(value) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1329
def rbs_const(value) case value when true, false then 'bool' when ::String then 'String' when ::Numeric then 'Numeric' else 'untyped' end end
#rbs_type(node) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1299
def rbs_type(node) resolve(node)[:rbs] end
#record_class(name, type) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1125
def record_class(name, type) extensible = extensible_record?(name) const = type['fields'].find { |f| baked_discriminator?(f) } discriminator = const && {ruby_name: field_name(const, extensible), wire: const['wire'], value: const['type']['const'], rbs: rbs_const(const['type']['const'])} fields = type['fields'].reject { |f| baked_discriminator?(f) }.map { |f| field_ir(f, extensible) } # Every extensible type gets the extensions store: an undeclared wire key is preserved # and echoed back on any type the spec marks extensible, whether or not it is re-sendable. # Extensibility alone is the signal; send-reachability does not enter into it. TypeClass.new(ruby_name: BiDiGenerate.type_class_name(name), fields: fields, discriminator: discriminator, extensible: extensible, schema_name: name, synthetic: type['synthetic'] ? true : false, owner: type['owner'], label: type['label'], spec_href: type['specHref'], **directionality(name)) end
#record_params(fields, extensible) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1287
def record_params(fields, extensible) fields.map do |field| Param.new( ruby_name: field_name(field, extensible), wire_name: field['wire'], required: field['required'], enum: enum_const(field['type']), rbs: rbs_type(field['type']) ) end end
#refs_in_type(node) (private)
Every type name a type expression references (mirrors the projector's refsInType), descending list element, map value, inline union arms, and inline record fields. An inline union arm inside a field is a plain position — the field is filled with it.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 917
def refs_in_type(node) return [] unless node return [node['ref']] if node['ref'] return refs_in_type(node['list']) if node['list'] return refs_in_type(node['map']) if node['map'] return node['union'].flat_map { |arm| refs_in_type(arm) } if node['union'] return node['record'].flat_map { |f| refs_in_type(f['type']) } if node['record'] [] end
#resolve(node) (private)
Projects a schema type node to list:, nullable:, primitive:, bounds:, rbs:. Deriving the
serialization facts and the RBS signature from one walk keeps them from drifting
apart when the schema shape changes.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 944
def resolve(node) nullable = node['nullable'] ? true : false if node.key?('list') list = resolve_list(node['list']) return list.merge(nullable: nullable, rbs: nilable(list[:rbs], nullable)) end if node.key?('ref') named = narrowed(resolve_named(node['ref']), node) return {ref: named[:ref], list: named[:list], nullable: nullable, scalar: named[:scalar], map: named[:map], primitive: named[:primitive], bounds: named[:bounds], rbs: nilable(named[:rbs], nullable)} end return resolve_union(node, nullable) if node.key?('union') {ref: nil, list: false, nullable: nullable, primitive: checkable_primitive(node), bounds: node.slice(*BOUND_KEYS), rbs: nilable(scalar_rbs(node), nullable)} end
#resolve_list(element_node) (private)
A list's facts describe its element, so primitive (like ref) is the element's. A nested
list's elements are lists, so no primitive check applies to them.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 970
def resolve_list(element_node) element = resolve(element_node) # Record checks every element as non-null, so a nullable one would reject valid wire data. raise "nullable list element #{element_node.inspect} is not supported by Record" if element[:nullable] primitive, bounds = element.values_at(:primitive, :bounds) unless element[:list] {ref: element[:ref], list: true, scalar: element[:scalar], primitive: primitive, bounds: bounds, rbs: "Array[#{element[:rbs]}]"} end
#resolve_named(name, seen = {}) (private)
Resolves a named ref to the same list:, rbs: facts, transparently
following aliases — including alias-to-list — so an element type behind an alias
(e.g. script.ListLocalValue -> [script.LocalValue]) is preserved. Nullability is a
property of the referencing node (applied by #resolve), so it is not threaded
here. seen guards against cyclic ref-aliases.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1047
def resolve_named(name, seen = {}) return OPAQUE if name.nil? || seen[name] seen[name] = true type = @types[name] return OPAQUE unless type case type['kind'] when 'record' then type['fields'].empty? ? map_record(type) : named_type(name) when 'union' then named_union(name) when 'enum' then {ref: nil, list: false, rbs: 'Symbol'} when 'alias' then resolve_named_alias(name, type['type'], seen) else OPAQUE end end
#resolve_named_alias(name, inner, seen) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1105
def resolve_named_alias(name, inner, seen) return named_union(name) if inner.key?('union') return narrowed(resolve_named(inner['ref'], seen), inner) if inner.key?('ref') return resolve_list(inner['list']) if inner.key?('list') {ref: nil, list: false, primitive: checkable_primitive(inner), bounds: inner.slice(*BOUND_KEYS), rbs: scalar_rbs(inner)} end
#resolve_union(node, nullable) (private)
An inline union of one union-typed arm plus scalars (e.g. a MappingRemoteValue entry,
RemoteValue / string) is carried as that union ref so nested entries are typed. Because
the union is object_only, a bare-scalar sibling would raise there — so the projector's
scalar signal (a bare-scalar arm is present) is forwarded, and the runtime passes a
non-object leaf through instead (the map's string keys). A union of scalars carries their
primitives; any other shape (a record arm, multiple structured arms, a scalar beside an
enum) stays opaque.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 992
def resolve_union(node, nullable) primitives = union_primitives(node['union']) return scalar_union(primitives, nullable).merge(bounds: union_bounds(node['union'])) if primitives refs = node['union'].select { |arm| arm.key?('ref') } opaque = {ref: nil, list: false, nullable: nullable, rbs: nilable('untyped', nullable)} return opaque unless refs.one? && union_ref?(refs.first['ref']) named = resolve_named(refs.first['ref']) {ref: named[:ref], list: named[:list], nullable: nullable, scalar: node['scalar'], rbs: nilable('untyped', nullable)} end
#ruby_path(name) (private)
Class path, nesting a synthetic type under its owner as Owner::Label so a ref
resolves to the same nested constant the type is emitted as.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 930
def ruby_path(name) type = @types[name] return BiDiGenerate.type_ruby_path(name) unless type && type['synthetic'] "#{ruby_path(type['owner'])}::#{type['label']}" end
#ruby_path_for(name)
Public ruby-path resolver (Owner::Label for a synthetic), matching how a variant's
ref is emitted — so a caller can map a variant ref back to its emitted record.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 893
def ruby_path_for(name) = ruby_path(name)
#scalar_rbs(node) (private)
The leaf of #resolve: the bare scalar type, before any nullable wrap. An alias's own nullable is intentionally left off — only the referencing node's is applied.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1322
def scalar_rbs(node) return PRIMITIVE_RBS.fetch(node['primitive']) if node.key?('primitive') return rbs_const(node['const']) if node.key?('const') 'untyped' end
#scalar_union(primitives, nullable) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1025
def scalar_union(primitives, nullable) rbs = primitives.map { |primitive| PRIMITIVE_RBS.fetch(primitive) }.join(' | ') rbs = "(#{rbs})" if nullable && primitives.size > 1 {ref: nil, list: false, nullable: nullable, primitive: primitives.one? ? primitives.first : primitives, rbs: nilable(rbs, nullable)} end
#structured_ref(name)
The Protocol-relative class path a command result parses into, or nil when it is non-structured (or a bare list, returned raw).
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 886
def structured_ref(name) resolved = resolve_named(name) resolved[:list] ? nil : resolved[:ref] end
#suppressed_record?(type) ⇒ Boolean
Records the generator deliberately does not emit: a message envelope, or a synthetic params record lifted out of one. Both are reachable only through the envelope, which Transport replaces — so nothing else references them.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 865
def suppressed_record?(type) (type) || envelope_synthetic?(type) end
#tighter_bound?(side, flag, candidate, current) ⇒ Boolean (private)
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1198
def tighter_bound?(side, flag, candidate, current) return true unless current.key?(side) return candidate[flag] && !current[flag] if candidate[side] == current[side] side == 'min' ? candidate[side] > current[side] : candidate[side] < current[side] end
#type_kind(ref)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 766
def type_kind(ref) @types[ref]&.fetch('kind', nil) end
#type_prefixes(domain)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 833
def type_prefixes(domain) return [domain] unless vendor_domain?(domain) = commands_for(domain) + events_for(domain) refs = .flat_map { |m| [m.dig('params', 'ref'), m.dig('result', 'ref')] } refs.compact.select { |ref| ref.include?('.') }.map { |ref| ref.split('.', 2).first }.uniq end
#types_for(domain)
Structured value classes (records + discriminated unions) declared under
"{method, params} wire wrapper) are
skipped — Transport forms that envelope, so nothing references them.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 852
def types_for(domain) owned_types(domain).filter_map do |name, type| case type['kind'] when 'record' then record_class(name, type) unless type['fields'].empty? || suppressed_record?(type) when 'union' then union_class(name) when 'alias' then union_class(name) if type['type'].key?('union') end end end
#union_alias_path(name) (private)
Absolute RBS path of a union's value alias: its class path with the last segment
snake-cased (WebExtension::ExtensionData -> ...::WebExtension::extension_data), matching
the type alias emitted alongside the class.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1099
def union_alias_path(name) segments = ruby_path(name).split('::') segments[-1] = BiDiGenerate.camel_to_snake(segments[-1]) rbs_abs(segments.join('::')) end
#union_bounds(arms) (private)
The runtime holds every numeric value a field carries to one set of bounds, so numeric arms
that disagree (js-uint / float) would be checked wrongly; fail generation instead.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1017
def union_bounds(arms) numeric = arms.map { |arm| resolve(arm) }.select { |arm| NUMERIC_PRIMITIVES.include?(arm[:primitive]) } bounds = numeric.map { |arm| arm[:bounds] || {} }.uniq raise "numeric union arms #{arms} have different bounds: #{bounds}" if bounds.size > 1 bounds.first end
#union_class(name) (private)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1205
def union_class(name) type = @types[name] # A first-class union carries the schema's authoritative dispatch `selector` # (derived spec-faithfully, including null discriminators and the spec's choice # order); consume it rather than re-deriving and silently depending on emit # order. An alias-to-union (only input.Origin) has no selector — its const-string # arms aren't first-class types — so it keeps the structural re-derivation. klass = type['kind'] == 'union' ? union_from_selector(name, type['selector']) : union_from_alias(name) # A non-object_only union has a bare-scalar arm; only const-literal arms (scalar_values) are # modeled, so the runtime can validate an outbound scalar. A non-object_only union without them # is a shape the generator doesn't yet handle — fail here, at generation, not at a caller's runtime. if !klass.object_only && !klass.scalar_values? raise "non-object_only union #{name} has no scalar_values to validate its bare-scalar arm" end klass end
#union_from_alias(name) (private)
The sole alias-union is input.Origin ("viewport" | "pointer" | ElementOrigin): a scalar-or-object union the object-payload selector model doesn't cover, so the projector leaves it an alias with no selector. Its object arm(s) carry a const discriminator; the bare-string arms need no dispatch (Union.from_json returns a non-Hash payload unchanged). So dispatch the ref arms by their const tag.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1268
def union_from_alias(name) spec = @types[name] consts = spec['type']['union'].filter_map { |arm| arm['ref'] }.to_h do |ref| const = @types[ref]['fields'].find { |f| f['type'].key?('const') } const || raise("alias-union #{name} arm #{ref} has no const discriminator to dispatch on") [ref, const] end variants = consts.map do |ref, const| VariantIR.new(mode: :value, value: const['type']['const'], ref: ruby_path(ref), requires: nil) end # An alias-union carries bare-scalar arms (input.Origin's "viewport"/"pointer"), so it # is never object_only — those arms must still pass a non-Hash payload through, but only # a value the schema pins in scalarValues (so a stray "banana" is still rejected outbound). UnionClass.new(ruby_name: BiDiGenerate.type_class_name(name), discriminator_wire: consts.values.first['wire'], variants: variants, schema_name: name, spec_href: spec['specHref'], object_only: spec['objectOnly'] ? true : false, scalar_values: spec['type']['scalarValues'], **directionality(name)) end
#union_from_selector(name, selector) (private)
Map a union selector to dispatch variants the template renders:
{ by, variants, default? } -> a discriminator table (value => ref), {default}
as the fallback (it may itself be a union, which finishes the dispatch).
{ ordered: [{ ref, requires }] } -> presence rules in the spec's choice order.
{ correlated: true } -> resolved by request id, not the payload, so no payload
dispatch. Unreachable here: every correlated union is a top-level result
grouping, never domain-scoped, so it is never emitted as a class.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1230
def union_from_selector(name, selector) # A correlated union is resolved by request id, never the payload, so it carries # no dispatch — it must never be emitted (every one is a top-level result # grouping). Fail loudly if a future schema makes one domain-scoped rather than # emit a Union whose every parse would raise. if selector['correlated'] raise "correlated union #{name} must not be emitted (resolved by request id, not payload)" end variants = selector['by'] ? discriminated_variants(selector) : ordered_variants(selector) raise "union #{name} selector yielded no dispatch variants" if variants.empty? UnionClass.new(ruby_name: BiDiGenerate.type_class_name(name), discriminator_wire: selector['by'], variants: variants, schema_name: name, spec_href: @types[name]['specHref'], object_only: @types[name]['objectOnly'] ? true : false, **directionality(name)) end
#union_params(type, ref = nil) (private)
Merge a union's record variants into one flat param list for the command
signature. A field is only required when every variant declares it required;
variant-specific fields become optional. The command body dispatches these
kwargs to the matching variant via Union.build, whose typed as_json handles
null-vs-absent — so no nullable allowlist is needed.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1357
def union_params(type, ref = nil) variants = type['variants'].map { |variant_ref| @types[variant_ref] } return nil unless variants.all? { |v| v && v['kind'] == 'record' } selector = type['selector'] guard_union_dispatch_keys_simple!(selector, ref) params = merged_params(variants.map { |v| v['fields'] }, type['variants'].any? { |v| extensible_record?(v) }) annotate_discriminator_enum!(params, selector) params end
#union_primitives(arms) (private)
The primitives a union of scalars admits (pageRanges' js-uint / text), or nil when any arm
is something else (an enum, a const, a ref), which no primitive check can admit.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1007
def union_primitives(arms) primitives = arms.map do |arm| resolved = resolve(arm) resolved[:primitive] unless arm.key?('const') || resolved[:list] || resolved[:nullable] end primitives.flatten.uniq unless primitives.include?(nil) end
#union_ref?(name) ⇒ Boolean (private)
True when a ref (following aliases) is a union — the only arm whose from_json tolerates a scalar sibling. A record arm would raise on one, so it is not carried.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 1034
def union_ref?(name) type = @types[name] return false unless type return union_ref?(type['type']['ref']) if type['kind'] == 'alias' && type['type'].key?('ref') type['kind'] == 'union' end
#vendor_added_commands(domain, namespace)
The commands a vendor defines in a spec domain (webExtension.moz:listExtensions). They are
emitted on that domain's vendor variant, never on the shared class (see vendor_modules_for).
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 635
def vendor_added_commands(domain, namespace) return [] if vendor_domain?(domain) (@vendor.dig(namespace, 'commands') || []).select { |c| c['domain'] == domain } end
#vendor_domain?(domain) ⇒ Boolean
A vendor module's domain keeps its wire prefix (moz:debugging); a spec domain has none.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 567
def vendor_domain?(domain) = domain.include?(':')
#vendor_events
A vendor event in a spec domain would need the same vendor-variant routing as a command; none exists yet, so fail generation rather than leak it into the shared class.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 625
def vendor_events events = ('events') stray = events.reject { |e| vendor_domain?(e['domain']) } raise "vendor event #{stray.first['method']} in spec domain #{stray.first['domain']} is unsupported" if stray.any? events end
#vendor_extension_fields(namespace, type_name, entry)
Also records the vendor types the fields reference, so vendor_types_reached emits them.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 590
def vendor_extension_fields(namespace, type_name, entry) type = @types[type_name] || raise("vendor #{namespace} extends unknown type #{type_name}") raise "vendor #{namespace} extends #{type_name}, which is not a record" unless type['kind'] == 'record' fields = entry['fields'].map { |field| hoist_vendor_enum(type_name, field) } @vendor_field_refs.concat(fields.flat_map { |field| refs_in_type(field['type']) }) fields end
#vendor_field(field, namespace, taken)
A vendor field's ruby name drops its namespace prefix (moz:permanent → permanent): the
module already scopes it, so re-encoding the namespace in every identifier is redundant. The
wire key is untouched. Falls back to the prefixed name only if stripping would collide with a
shared field on the same record.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 761
def vendor_field(field, namespace, taken) stripped = field.merge('name' => field['name'].delete_prefix("#{namespace}:")) taken.include?(field_name(stripped, false)) ? field : stripped end
#vendor_messages(kind)
[ GitHub ]# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 619
def (kind) @vendor.values.flat_map { |section| section[kind] || [] } end
#vendor_modules_for(domain)
The vendor modules a domain carries, one per namespace (moz → module Moz). Each
command whose params a vendor extends is overridden there with a closed params record of
the variant's own (the shared fields plus the vendor's), so the vendor method mirrors the
base command's wire method and result while adding the typed vendor fields. Empty for
any domain (or schema) with no vendor extensions, so non-vendor output is unaffected.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 714
def vendor_modules_for(domain) parent = BiDiGenerate.snake_to_class_name(BiDiGenerate.camel_to_snake(domain)) @vendor_params.filter_map do |namespace, extended| overrides = extended.filter_map do |type_name, fields| cmd = params_command(type_name) build_vendor_override(cmd, type_name, fields, namespace) if cmd['domain'] == domain end added = vendor_added_commands(domain, namespace).map { |cmd| build_added_command(cmd, namespace, domain) } next if overrides.empty? && added.empty? VendorModule.new(name: BiDiGenerate.snake_to_class_name(namespace), namespace: namespace, parent: parent, types: overrides.map(&:first), commands: overrides.map(&:last) + added) end end
#vendor_types_reached(domain)
The vendor types a vendor-added command in this spec domain reaches through its params and result, or a vendor field folded onto a spec type references, so they are emitted in the domain's module beside what uses them.
# File 'rb/lib/selenium/webdriver/bidi/support/bidi_generate.rb', line 819
def vendor_types_reached(domain) commands = @vendor.keys.flat_map { |namespace| vendor_added_commands(domain, namespace) } pending = commands.flat_map { |c| [c.dig('params', 'ref'), c.dig('result', 'ref')] }.compact + @vendor_field_refs reached = Set.new until pending.empty? name = pending.pop next if reached.include?(name) || !@vendor_types.key?(name) reached << name pending.concat(plain_refs(@types[name]) + (@types[name]['variants'] || [])) end reached end