You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

535 lines
17 KiB

// Copyright 2021-2024 Buf Technologies, Inc.
//
// Licensed 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.
import { ScalarType, } from "../descriptors.js";
import { checkField, checkListItem, checkMapEntry } from "./reflect-check.js";
import { FieldError } from "./error.js";
import { unsafeClear, unsafeGet, unsafeIsSet, unsafeLocal, unsafeOneofCase, unsafeSet, } from "./unsafe.js";
import { create } from "../create.js";
import { isWrapper, isWrapperDesc } from "../wkt/wrappers.js";
import { scalarZeroValue } from "./scalar.js";
import { protoInt64 } from "../proto-int64.js";
import { isObject, isReflectList, isReflectMap, isReflectMessage, } from "./guard.js";
/**
* Create a ReflectMessage.
*/
export function reflect(messageDesc, message,
/**
* By default, field values are validated when setting them. For example,
* a value for an uint32 field must be a ECMAScript Number >= 0.
*
* When field values are trusted, performance can be improved by disabling
* checks.
*/
check = true) {
return new ReflectMessageImpl(messageDesc, message, check);
}
class ReflectMessageImpl {
get sortedFields() {
var _a;
return ((_a = this._sortedFields) !== null && _a !== void 0 ? _a : (this._sortedFields = this.desc.fields
.concat()
.sort((a, b) => a.number - b.number)));
}
constructor(messageDesc, message, check = true) {
this.lists = new Map();
this.maps = new Map();
this.check = check;
this.desc = messageDesc;
this.message = this[unsafeLocal] = message !== null && message !== void 0 ? message : create(messageDesc);
this.fields = messageDesc.fields;
this.oneofs = messageDesc.oneofs;
this.members = messageDesc.members;
}
findNumber(number) {
if (!this._fieldsByNumber) {
this._fieldsByNumber = new Map(this.desc.fields.map((f) => [f.number, f]));
}
return this._fieldsByNumber.get(number);
}
oneofCase(oneof) {
assertOwn(this.message, oneof);
return unsafeOneofCase(this.message, oneof);
}
isSet(field) {
assertOwn(this.message, field);
return unsafeIsSet(this.message, field);
}
clear(field) {
assertOwn(this.message, field);
unsafeClear(this.message, field);
}
get(field) {
assertOwn(this.message, field);
const value = unsafeGet(this.message, field);
switch (field.fieldKind) {
case "list":
// eslint-disable-next-line no-case-declarations
let list = this.lists.get(field);
if (!list || list[unsafeLocal] !== value) {
this.lists.set(field, (list = new ReflectListImpl(field, value, this.check)));
}
return list;
case "map":
// eslint-disable-next-line no-case-declarations
let map = this.maps.get(field);
if (!map || map[unsafeLocal] !== value) {
this.maps.set(field, (map = new ReflectMapImpl(field, value, this.check)));
}
return map;
case "message":
return messageToReflect(field, value, this.check);
case "scalar":
return (value === undefined
? scalarZeroValue(field.scalar, false)
: longToReflect(field, value));
case "enum":
return (value !== null && value !== void 0 ? value : field.enum.values[0].number);
}
}
set(field, value) {
assertOwn(this.message, field);
if (this.check) {
const err = checkField(field, value);
if (err) {
throw err;
}
}
let local;
if (field.fieldKind == "message") {
local = messageToLocal(field, value);
}
else if (isReflectMap(value) || isReflectList(value)) {
local = value[unsafeLocal];
}
else {
local = longToLocal(field, value);
}
unsafeSet(this.message, field, local);
}
getUnknown() {
return this.message.$unknown;
}
setUnknown(value) {
this.message.$unknown = value;
}
}
function assertOwn(owner, member) {
if (member.parent.typeName !== owner.$typeName) {
throw new FieldError(member, `cannot use ${member.toString()} with message ${owner.$typeName}`, "ForeignFieldError");
}
}
/**
* Create a ReflectList.
*/
export function reflectList(field, unsafeInput,
/**
* By default, field values are validated when setting them. For example,
* a value for an uint32 field must be a ECMAScript Number >= 0.
*
* When field values are trusted, performance can be improved by disabling
* checks.
*/
check = true) {
return new ReflectListImpl(field, unsafeInput !== null && unsafeInput !== void 0 ? unsafeInput : [], check);
}
class ReflectListImpl {
field() {
return this._field;
}
get size() {
return this._arr.length;
}
constructor(field, unsafeInput, check) {
this._field = field;
this._arr = this[unsafeLocal] = unsafeInput;
this.check = check;
}
get(index) {
const item = this._arr[index];
return item === undefined
? undefined
: listItemToReflect(this._field, item, this.check);
}
set(index, item) {
if (index < 0 || index >= this._arr.length) {
throw new FieldError(this._field, `list item #${index + 1}: out of range`);
}
if (this.check) {
const err = checkListItem(this._field, index, item);
if (err) {
throw err;
}
}
this._arr[index] = listItemToLocal(this._field, item);
}
add(item) {
if (this.check) {
const err = checkListItem(this._field, this._arr.length, item);
if (err) {
throw err;
}
}
this._arr.push(listItemToLocal(this._field, item));
return undefined;
}
clear() {
this._arr.splice(0, this._arr.length);
}
[Symbol.iterator]() {
return this.values();
}
keys() {
return this._arr.keys();
}
*values() {
for (const item of this._arr) {
yield listItemToReflect(this._field, item, this.check);
}
}
*entries() {
for (let i = 0; i < this._arr.length; i++) {
yield [i, listItemToReflect(this._field, this._arr[i], this.check)];
}
}
}
/**
* Create a ReflectMap.
*/
export function reflectMap(field, unsafeInput,
/**
* By default, field values are validated when setting them. For example,
* a value for an uint32 field must be a ECMAScript Number >= 0.
*
* When field values are trusted, performance can be improved by disabling
* checks.
*/
check = true) {
return new ReflectMapImpl(field, unsafeInput, check);
}
class ReflectMapImpl {
constructor(field, unsafeInput, check = true) {
this.obj = this[unsafeLocal] = unsafeInput !== null && unsafeInput !== void 0 ? unsafeInput : {};
this.check = check;
this._field = field;
}
field() {
return this._field;
}
set(key, value) {
if (this.check) {
const err = checkMapEntry(this._field, key, value);
if (err) {
throw err;
}
}
this.obj[mapKeyToLocal(key)] = mapValueToLocal(this._field, value);
return this;
}
delete(key) {
const k = mapKeyToLocal(key);
const has = Object.prototype.hasOwnProperty.call(this.obj, k);
if (has) {
delete this.obj[k];
}
return has;
}
clear() {
for (const key of Object.keys(this.obj)) {
delete this.obj[key];
}
}
get(key) {
let val = this.obj[mapKeyToLocal(key)];
if (val !== undefined) {
val = mapValueToReflect(this._field, val, this.check);
}
return val;
}
has(key) {
return Object.prototype.hasOwnProperty.call(this.obj, mapKeyToLocal(key));
}
*keys() {
for (const objKey of Object.keys(this.obj)) {
yield mapKeyToReflect(objKey, this._field.mapKey);
}
}
*entries() {
for (const objEntry of Object.entries(this.obj)) {
yield [
mapKeyToReflect(objEntry[0], this._field.mapKey),
mapValueToReflect(this._field, objEntry[1], this.check),
];
}
}
[Symbol.iterator]() {
return this.entries();
}
get size() {
return Object.keys(this.obj).length;
}
*values() {
for (const val of Object.values(this.obj)) {
yield mapValueToReflect(this._field, val, this.check);
}
}
forEach(callbackfn, thisArg) {
for (const mapEntry of this.entries()) {
callbackfn.call(thisArg, mapEntry[1], mapEntry[0], this);
}
}
}
function messageToLocal(field, value) {
if (!isReflectMessage(value)) {
return value;
}
if (isWrapper(value.message) &&
!field.oneof &&
field.fieldKind == "message") {
// Types from google/protobuf/wrappers.proto are unwrapped when used in
// a singular field that is not part of a oneof group.
return value.message.value;
}
if (value.desc.typeName == "google.protobuf.Struct" &&
field.parent.typeName != "google.protobuf.Value") {
// google.protobuf.Struct is represented with JsonObject when used in a
// field, except when used in google.protobuf.Value.
return wktStructToLocal(value.message);
}
return value.message;
}
function messageToReflect(field, value, check) {
if (value !== undefined) {
if (isWrapperDesc(field.message) &&
!field.oneof &&
field.fieldKind == "message") {
// Types from google/protobuf/wrappers.proto are unwrapped when used in
// a singular field that is not part of a oneof group.
value = {
$typeName: field.message.typeName,
value: longToReflect(field.message.fields[0], value),
};
}
else if (field.message.typeName == "google.protobuf.Struct" &&
field.parent.typeName != "google.protobuf.Value" &&
isObject(value)) {
// google.protobuf.Struct is represented with JsonObject when used in a
// field, except when used in google.protobuf.Value.
value = wktStructToReflect(value);
}
}
return new ReflectMessageImpl(field.message, value, check);
}
function listItemToLocal(field, value) {
if (field.listKind == "message") {
return messageToLocal(field, value);
}
return longToLocal(field, value);
}
function listItemToReflect(field, value, check) {
if (field.listKind == "message") {
return messageToReflect(field, value, check);
}
return longToReflect(field, value);
}
function mapValueToLocal(field, value) {
if (field.mapKind == "message") {
return messageToLocal(field, value);
}
return longToLocal(field, value);
}
function mapValueToReflect(field, value, check) {
if (field.mapKind == "message") {
return messageToReflect(field, value, check);
}
return value;
}
function mapKeyToLocal(key) {
return typeof key == "string" || typeof key == "number" ? key : String(key);
}
/**
* Converts a map key (any scalar value except float, double, or bytes) from its
* representation in a message (string or number, the only possible object key
* types) to the closest possible type in ECMAScript.
*/
function mapKeyToReflect(key, type) {
switch (type) {
case ScalarType.STRING:
return key;
case ScalarType.INT32:
case ScalarType.FIXED32:
case ScalarType.UINT32:
case ScalarType.SFIXED32:
case ScalarType.SINT32: {
const n = Number.parseInt(key);
if (Number.isFinite(n)) {
return n;
}
break;
}
case ScalarType.BOOL:
switch (key) {
case "true":
return true;
case "false":
return false;
}
break;
case ScalarType.UINT64:
case ScalarType.FIXED64:
try {
return protoInt64.uParse(key);
}
catch (_a) {
//
}
break;
default:
// INT64, SFIXED64, SINT64
try {
return protoInt64.parse(key);
}
catch (_b) {
//
}
break;
}
return key;
}
function longToReflect(field, value) {
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (field.scalar) {
case ScalarType.INT64:
case ScalarType.SFIXED64:
case ScalarType.SINT64:
if ("longAsString" in field &&
field.longAsString &&
typeof value == "string") {
value = protoInt64.parse(value);
}
break;
case ScalarType.FIXED64:
case ScalarType.UINT64:
if ("longAsString" in field &&
field.longAsString &&
typeof value == "string") {
value = protoInt64.uParse(value);
}
break;
}
return value;
}
function longToLocal(field, value) {
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (field.scalar) {
case ScalarType.INT64:
case ScalarType.SFIXED64:
case ScalarType.SINT64:
if ("longAsString" in field && field.longAsString) {
value = String(value);
}
else if (typeof value == "string" || typeof value == "number") {
value = protoInt64.parse(value);
}
break;
case ScalarType.FIXED64:
case ScalarType.UINT64:
if ("longAsString" in field && field.longAsString) {
value = String(value);
}
else if (typeof value == "string" || typeof value == "number") {
value = protoInt64.uParse(value);
}
break;
}
return value;
}
function wktStructToReflect(json) {
const struct = {
$typeName: "google.protobuf.Struct",
fields: {},
};
if (isObject(json)) {
for (const [k, v] of Object.entries(json)) {
struct.fields[k] = wktValueToReflect(v);
}
}
return struct;
}
function wktStructToLocal(val) {
const json = {};
for (const [k, v] of Object.entries(val.fields)) {
json[k] = wktValueToLocal(v);
}
return json;
}
function wktValueToLocal(val) {
switch (val.kind.case) {
case "structValue":
return wktStructToLocal(val.kind.value);
case "listValue":
return val.kind.value.values.map(wktValueToLocal);
case "nullValue":
case undefined:
return null;
default:
return val.kind.value;
}
}
function wktValueToReflect(json) {
const value = {
$typeName: "google.protobuf.Value",
kind: { case: undefined },
};
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- invalid input is unselected kind
switch (typeof json) {
case "number":
value.kind = { case: "numberValue", value: json };
break;
case "string":
value.kind = { case: "stringValue", value: json };
break;
case "boolean":
value.kind = { case: "boolValue", value: json };
break;
case "object":
if (json === null) {
const nullValue = 0;
value.kind = { case: "nullValue", value: nullValue };
}
else if (Array.isArray(json)) {
const listValue = {
$typeName: "google.protobuf.ListValue",
values: [],
};
if (Array.isArray(json)) {
for (const e of json) {
listValue.values.push(wktValueToReflect(e));
}
}
value.kind = {
case: "listValue",
value: listValue,
};
}
else {
value.kind = {
case: "structValue",
value: wktStructToReflect(json),
};
}
break;
}
return value;
}