forked from fdzcxy212206413/gsl_grs
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.
259 lines
9.1 KiB
259 lines
9.1 KiB
2 months ago
|
// 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 { isMessage } from "./is-message.js";
|
||
|
import { ScalarType, } from "./descriptors.js";
|
||
|
import { scalarZeroValue } from "./reflect/scalar.js";
|
||
|
import { FieldError } from "./reflect/error.js";
|
||
|
import { isObject } from "./reflect/guard.js";
|
||
|
import { unsafeGet, unsafeOneofCase, unsafeSet } from "./reflect/unsafe.js";
|
||
|
import { isWrapperDesc } from "./wkt/wrappers.js";
|
||
|
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO3: const $name: Edition.$localName = $number;
|
||
|
const EDITION_PROTO3 = 999;
|
||
|
// bootstrap-inject google.protobuf.Edition.EDITION_PROTO2: const $name: Edition.$localName = $number;
|
||
|
const EDITION_PROTO2 = 998;
|
||
|
// bootstrap-inject google.protobuf.FeatureSet.FieldPresence.IMPLICIT: const $name: FeatureSet_FieldPresence.$localName = $number;
|
||
|
const IMPLICIT = 2;
|
||
|
/**
|
||
|
* Create a new message instance.
|
||
|
*
|
||
|
* The second argument is an optional initializer object, where all fields are
|
||
|
* optional.
|
||
|
*/
|
||
|
export function create(schema, init) {
|
||
|
if (isMessage(init, schema)) {
|
||
|
return init;
|
||
|
}
|
||
|
const message = createZeroMessage(schema);
|
||
|
if (init !== undefined) {
|
||
|
initMessage(schema, message, init);
|
||
|
}
|
||
|
return message;
|
||
|
}
|
||
|
/**
|
||
|
* Sets field values from a MessageInitShape on a zero message.
|
||
|
*/
|
||
|
function initMessage(messageDesc, message, init) {
|
||
|
for (const member of messageDesc.members) {
|
||
|
let value = init[member.localName];
|
||
|
if (value == null) {
|
||
|
// intentionally ignore undefined and null
|
||
|
continue;
|
||
|
}
|
||
|
let field;
|
||
|
if (member.kind == "oneof") {
|
||
|
const oneofField = unsafeOneofCase(init, member);
|
||
|
if (!oneofField) {
|
||
|
continue;
|
||
|
}
|
||
|
field = oneofField;
|
||
|
value = unsafeGet(init, oneofField);
|
||
|
}
|
||
|
else {
|
||
|
field = member;
|
||
|
}
|
||
|
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- no need to convert enum
|
||
|
switch (field.fieldKind) {
|
||
|
case "message":
|
||
|
value = toMessage(field, value);
|
||
|
break;
|
||
|
case "scalar":
|
||
|
value = initScalar(field, value);
|
||
|
break;
|
||
|
case "list":
|
||
|
value = initList(field, value);
|
||
|
break;
|
||
|
case "map":
|
||
|
value = initMap(field, value);
|
||
|
break;
|
||
|
}
|
||
|
unsafeSet(message, field, value);
|
||
|
}
|
||
|
return message;
|
||
|
}
|
||
|
function initScalar(field, value) {
|
||
|
if (field.scalar == ScalarType.BYTES) {
|
||
|
return toU8Arr(value);
|
||
|
}
|
||
|
return value;
|
||
|
}
|
||
|
function initMap(field, value) {
|
||
|
if (isObject(value)) {
|
||
|
if (field.scalar == ScalarType.BYTES) {
|
||
|
return convertObjectValues(value, toU8Arr);
|
||
|
}
|
||
|
if (field.mapKind == "message") {
|
||
|
return convertObjectValues(value, (val) => toMessage(field, val));
|
||
|
}
|
||
|
}
|
||
|
return value;
|
||
|
}
|
||
|
function initList(field, value) {
|
||
|
if (Array.isArray(value)) {
|
||
|
if (field.scalar == ScalarType.BYTES) {
|
||
|
return value.map(toU8Arr);
|
||
|
}
|
||
|
if (field.listKind == "message") {
|
||
|
return value.map((item) => toMessage(field, item));
|
||
|
}
|
||
|
}
|
||
|
return value;
|
||
|
}
|
||
|
function toMessage(field, value) {
|
||
|
if (field.fieldKind == "message" &&
|
||
|
!field.oneof &&
|
||
|
isWrapperDesc(field.message)) {
|
||
|
// Types from google/protobuf/wrappers.proto are unwrapped when used in
|
||
|
// a singular field that is not part of a oneof group.
|
||
|
return initScalar(field.message.fields[0], value);
|
||
|
}
|
||
|
if (isObject(value)) {
|
||
|
if (field.message.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 value;
|
||
|
}
|
||
|
if (!isMessage(value, field.message)) {
|
||
|
return create(field.message, value);
|
||
|
}
|
||
|
}
|
||
|
return value;
|
||
|
}
|
||
|
// converts any ArrayLike<number> to Uint8Array if necessary.
|
||
|
function toU8Arr(value) {
|
||
|
return Array.isArray(value) ? new Uint8Array(value) : value;
|
||
|
}
|
||
|
function convertObjectValues(obj, fn) {
|
||
|
const ret = {};
|
||
|
for (const entry of Object.entries(obj)) {
|
||
|
ret[entry[0]] = fn(entry[1]);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
const tokenZeroMessageField = Symbol();
|
||
|
const messagePrototypes = new WeakMap();
|
||
|
/**
|
||
|
* Create a zero message.
|
||
|
*/
|
||
|
function createZeroMessage(desc) {
|
||
|
let msg;
|
||
|
if (!needsPrototypeChain(desc)) {
|
||
|
msg = {
|
||
|
$typeName: desc.typeName,
|
||
|
};
|
||
|
for (const member of desc.members) {
|
||
|
if (member.kind == "oneof" || member.presence == IMPLICIT) {
|
||
|
msg[member.localName] = createZeroField(member);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// Support default values and track presence via the prototype chain
|
||
|
const cached = messagePrototypes.get(desc);
|
||
|
let prototype;
|
||
|
let members;
|
||
|
if (cached) {
|
||
|
({ prototype, members } = cached);
|
||
|
}
|
||
|
else {
|
||
|
prototype = {};
|
||
|
members = new Set();
|
||
|
for (const member of desc.members) {
|
||
|
if (member.kind == "oneof") {
|
||
|
// we can only put immutable values on the prototype,
|
||
|
// oneof ADTs are mutable
|
||
|
continue;
|
||
|
}
|
||
|
if (member.fieldKind != "scalar" && member.fieldKind != "enum") {
|
||
|
// only scalar and enum values are immutable, map, list, and message
|
||
|
// are not
|
||
|
continue;
|
||
|
}
|
||
|
if (member.presence == IMPLICIT) {
|
||
|
// implicit presence tracks field presence by zero values - e.g. 0, false, "", are unset, 1, true, "x" are set.
|
||
|
// message, map, list fields are mutable, and also have IMPLICIT presence.
|
||
|
continue;
|
||
|
}
|
||
|
members.add(member);
|
||
|
prototype[member.localName] = createZeroField(member);
|
||
|
}
|
||
|
messagePrototypes.set(desc, { prototype, members });
|
||
|
}
|
||
|
msg = Object.create(prototype);
|
||
|
msg.$typeName = desc.typeName;
|
||
|
for (const member of desc.members) {
|
||
|
if (members.has(member)) {
|
||
|
continue;
|
||
|
}
|
||
|
if (member.kind == "field") {
|
||
|
if (member.fieldKind == "message") {
|
||
|
continue;
|
||
|
}
|
||
|
if (member.fieldKind == "scalar" || member.fieldKind == "enum") {
|
||
|
if (member.presence != IMPLICIT) {
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
msg[member.localName] = createZeroField(member);
|
||
|
}
|
||
|
}
|
||
|
return msg;
|
||
|
}
|
||
|
/**
|
||
|
* Do we need the prototype chain to track field presence?
|
||
|
*/
|
||
|
function needsPrototypeChain(desc) {
|
||
|
switch (desc.file.edition) {
|
||
|
case EDITION_PROTO3:
|
||
|
// proto3 always uses implicit presence, we never need the prototype chain.
|
||
|
return false;
|
||
|
case EDITION_PROTO2:
|
||
|
// proto2 never uses implicit presence, we always need the prototype chain.
|
||
|
return true;
|
||
|
default:
|
||
|
// If a message uses scalar or enum fields with explicit presence, we need
|
||
|
// the prototype chain to track presence. This rule does not apply to fields
|
||
|
// in a oneof group - they use a different mechanism to track presence.
|
||
|
return desc.fields.some((f) => f.presence != IMPLICIT && f.fieldKind != "message" && !f.oneof);
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Returns a zero value for oneof groups, and for every field kind except
|
||
|
* messages. Scalar and enum fields can have default values.
|
||
|
*/
|
||
|
function createZeroField(field) {
|
||
|
if (field.kind == "oneof") {
|
||
|
return { case: undefined };
|
||
|
}
|
||
|
if (field.fieldKind == "list") {
|
||
|
return [];
|
||
|
}
|
||
|
if (field.fieldKind == "map") {
|
||
|
return {}; // Object.create(null) would be desirable here, but is unsupported by react https://react.dev/reference/react/use-server#serializable-parameters-and-return-values
|
||
|
}
|
||
|
if (field.fieldKind == "message") {
|
||
|
return tokenZeroMessageField;
|
||
|
}
|
||
|
const defaultValue = field.getDefaultValue();
|
||
|
if (defaultValue !== undefined) {
|
||
|
return field.fieldKind == "scalar" && field.longAsString
|
||
|
? defaultValue.toString()
|
||
|
: defaultValue;
|
||
|
}
|
||
|
return field.fieldKind == "scalar"
|
||
|
? scalarZeroValue(field.scalar, field.longAsString)
|
||
|
: field.enum.values[0].number;
|
||
|
}
|