lib/selenium/webdriver/bidi/serialization/union.rb
# frozen_string_literal: true # Licensed to the Software Freedom Conservancy (SFC) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The SFC licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. module Selenium module WebDriver class BiDi module Serialization # Resolves a wire payload to the right Data variant: a shared discriminator gives # table dispatch; presence rules and a no-tag fallback cover unions without one. # Subclassed (never instantiated) — each union holds its own dispatch table. # # class Locator < Serialization::Union # discriminator 'type' # variants('css' => 'BrowsingContext::CssLocator') # end # # @api private class Union class << self # values maps each variant's discriminator symbol to its wire token, so an # inbound payload tag (a wire string) can be matched to the symbol-keyed table. def discriminator(wire_key, values = {}) @discriminator = wire_key @discriminator_values = values end def variants(table) = @variants = table def presence(rules) = @presence = rules def fallback(path) = @fallback = path # A non-Hash payload is a bare scalar arm (e.g. input.Origin's "viewport") with # no object to dispatch on, so it is returned unchanged. def from_json(json_payload) return json_payload unless json_payload.is_a?(::Hash) variant = select(json_payload) unless variant raise Error::WebDriverError, "#{name} received a variant not in this Selenium's BiDi schema: #{json_payload.inspect}" end Protocol.const_get(variant).from_json(json_payload) end # Outbound mirror of from_json: build the variant the command's kwargs describe # so its typed as_json drives null-vs-absent per field (a flat hash through # Transport cannot). Dispatch keys are wire names equal to their ruby kwarg # (asserted at generation), so they match the kwargs by symbol. A mismatch here # is a caller error (unlike an unknown inbound value), so it fails loudly. def build(**kwargs) variant = outbound_variant(kwargs) || raise(::ArgumentError, "no #{name} variant matches #{kwargs.inspect}") klass = Protocol.const_get(variant) # An omitted optional arrives as UNSET; forward only what was provided. A provided # key that isn't a field of the chosen variant is an invalid combination for this union. provided = kwargs.reject { |_, value| UNSET.equal?(value) } invalid = provided.keys - klass.fields.map(&:name) return klass.new(**provided) if invalid.empty? raise ::ArgumentError, "invalid combination for #{name}: #{invalid.join(', ')}" end private # The discriminator value may legitimately be null (e.g. script.NullValue's # "null" tag), so it is matched by key presence. def select(json_payload) variant_for(payload_tag(json_payload)) { |k| json_payload.key?(k) } end # An explicit nil kwarg still counts as supplied; a non-nullable field set to nil is # rejected at construction (Data.new), not here. def outbound_variant(kwargs) tag = @discriminator ? kwargs.fetch(@discriminator.to_sym, UNSET) : UNSET variant_for(tag) { |k| kwargs.key?(k.to_sym) && !UNSET.equal?(kwargs[k.to_sym]) } end # The matching variant's ref, or nil when none matches (the fallback if declared). def variant_for(tag, &supplied) return @variants[tag] if !UNSET.equal?(tag) && @variants&.key?(tag) @presence&.each { |path, keys| return path if keys.all?(&supplied) } @fallback end # The wire tag mapped back to its variant symbol (the table's key); an # unrecognized tag falls through as-is so select misses and from_json raises. def payload_tag(json_payload) return UNSET unless @discriminator && json_payload.key?(@discriminator) wire = json_payload[@discriminator] @discriminator_values.key(wire) || wire end end end end end # BiDi end # WebDriver end # Selenium