selenium-webdriver
Version:
The official WebDriver JavaScript bindings from the Selenium project
103 lines (94 loc) • 4.53 kB
JavaScript
// 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.
const { register, resolve } = require('./registry')
const { ValidationError } = require('./record')
// Resolves the variant ref a value/payload matches, per the schema's selector shape:
// { by, variants: [{value, ref}], default? } - discriminated: match `data[by]` against
// each variant's value.
// { ordered: [{ref, requires}] } - structural: first variant whose `requires`
// keys are all present in `data`, in spec order.
function selectVariant(selector, data, hasKey) {
if (selector.by) {
const tag = hasKey(data, selector.by) ? data[selector.by] : undefined
const match = selector.variants.find((v) => v.value === tag)
if (match) return match.ref
return selector.default
}
if (selector.ordered) {
for (const variant of selector.ordered) {
if (variant.requires.every((key) => hasKey(data, key))) return variant.ref
}
return undefined
}
return undefined // correlated: resolved by request id elsewhere, not from the payload
}
/**
* Registers a schema `union` — a value that may be any one of several variant
* record types, resolved by a discriminator field or by structural shape.
* @param {string} name Schema type name, e.g. 'session.ProxyConfiguration'.
* @param {object} selector The schema's `selector` node for this union.
* @param {{objectOnly?: boolean}} [options]
* @returns {{build: function(unknown): object, fromWire: function(unknown): object}}
* The registered union — `build(data)` resolves and constructs the matching
* variant outbound, `fromWire(payload)` resolves and parses it inbound.
*/
function defineUnion(name, selector, options = {}) {
const { objectOnly = false } = options
const union = {
kind: 'union',
// Outbound: resolve which variant `data` describes, then delegate to that
// variant's own (strict) constructor. A discriminated selector's `default`
// catch-all can itself resolve to another union — not just a record — e.g.
// LocalValue's untyped RemoteReference arm (see unionSelector() in
// project_bidi_schema.mjs) — so recurse through that union's own dispatch
// rather than assuming every resolved ref is a record.
build(data) {
if (objectOnly && (typeof data !== 'object' || data === null || Array.isArray(data))) {
throw new ValidationError(`${name}: expected an object`)
}
const ref = selectVariant(selector, data, (d, key) => Object.hasOwn(d, key))
if (ref === undefined) {
throw new ValidationError(`${name}: value does not match any known variant`)
}
const variant = resolve(ref)
if (variant.kind === 'union') {
return variant.build(data)
}
return new variant.RecordClass(data)
},
// Inbound: resolve which variant `payload` matches. An unresolvable payload is a
// closed-vocabulary miss — always an error, never a warning, since there is no
// valid typed object to fall back to. Same nested-union case as build() above.
fromWire(payload) {
if (objectOnly && (typeof payload !== 'object' || payload === null || Array.isArray(payload))) {
throw new ValidationError(`${name}: expected an object on the wire, got ${typeof payload}`)
}
const ref = selectVariant(selector, payload, (d, key) => Object.hasOwn(d, key))
if (ref === undefined) {
throw new ValidationError(`${name}: received a variant not in this binding's BiDi schema`)
}
const variant = resolve(ref)
if (variant.kind === 'union') {
return variant.fromWire(payload)
}
return variant.RecordClass.fromWire(payload)
},
}
register(name, union)
return union
}
module.exports = { defineUnion }