// 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 { setEnumType } from "./enum.js"; import { Message } from "../message.js"; import { scalarEquals } from "./scalars.js"; import { ScalarType } from "../scalar.js"; import { isMessage } from "../is-message.js"; /* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-argument,no-case-declarations */ export function makeUtilCommon() { return { setEnumType, initPartial(source, target) { if (source === undefined) { return; } const type = target.getType(); for (const member of type.fields.byMember()) { const localName = member.localName, t = target, s = source; if (s[localName] == null) { // TODO if source is a Message instance, we should use isFieldSet() here to support future field presence continue; } switch (member.kind) { case "oneof": const sk = s[localName].case; if (sk === undefined) { continue; } const sourceField = member.findField(sk); let val = s[localName].value; if (sourceField && sourceField.kind == "message" && !isMessage(val, sourceField.T)) { val = new sourceField.T(val); } else if (sourceField && sourceField.kind === "scalar" && sourceField.T === ScalarType.BYTES) { val = toU8Arr(val); } t[localName] = { case: sk, value: val }; break; case "scalar": case "enum": let copy = s[localName]; if (member.T === ScalarType.BYTES) { copy = member.repeated ? copy.map(toU8Arr) : toU8Arr(copy); } t[localName] = copy; break; case "map": switch (member.V.kind) { case "scalar": case "enum": if (member.V.T === ScalarType.BYTES) { for (const [k, v] of Object.entries(s[localName])) { t[localName][k] = toU8Arr(v); } } else { Object.assign(t[localName], s[localName]); } break; case "message": const messageType = member.V.T; for (const k of Object.keys(s[localName])) { let val = s[localName][k]; if (!messageType.fieldWrapper) { // We only take partial input for messages that are not a wrapper type. // For those messages, we recursively normalize the partial input. val = new messageType(val); } t[localName][k] = val; } break; } break; case "message": const mt = member.T; if (member.repeated) { t[localName] = s[localName].map((val) => isMessage(val, mt) ? val : new mt(val)); } else { const val = s[localName]; if (mt.fieldWrapper) { if ( // We can't use BytesValue.typeName as that will create a circular import mt.typeName === "google.protobuf.BytesValue") { t[localName] = toU8Arr(val); } else { t[localName] = val; } } else { t[localName] = isMessage(val, mt) ? val : new mt(val); } } break; } } }, // TODO use isFieldSet() here to support future field presence equals(type, a, b) { if (a === b) { return true; } if (!a || !b) { return false; } return type.fields.byMember().every((m) => { const va = a[m.localName]; const vb = b[m.localName]; if (m.repeated) { if (va.length !== vb.length) { return false; } // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- repeated fields are never "map" switch (m.kind) { case "message": return va.every((a, i) => m.T.equals(a, vb[i])); case "scalar": return va.every((a, i) => scalarEquals(m.T, a, vb[i])); case "enum": return va.every((a, i) => scalarEquals(ScalarType.INT32, a, vb[i])); } throw new Error(`repeated cannot contain ${m.kind}`); } switch (m.kind) { case "message": let a = va; let b = vb; if (m.T.fieldWrapper) { if (a !== undefined && !isMessage(a)) { a = m.T.fieldWrapper.wrapField(a); } if (b !== undefined && !isMessage(b)) { b = m.T.fieldWrapper.wrapField(b); } } return m.T.equals(a, b); case "enum": return scalarEquals(ScalarType.INT32, va, vb); case "scalar": return scalarEquals(m.T, va, vb); case "oneof": if (va.case !== vb.case) { return false; } const s = m.findField(va.case); if (s === undefined) { return true; } // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- oneof fields are never "map" switch (s.kind) { case "message": return s.T.equals(va.value, vb.value); case "enum": return scalarEquals(ScalarType.INT32, va.value, vb.value); case "scalar": return scalarEquals(s.T, va.value, vb.value); } throw new Error(`oneof cannot contain ${s.kind}`); case "map": const keys = Object.keys(va).concat(Object.keys(vb)); switch (m.V.kind) { case "message": const messageType = m.V.T; return keys.every((k) => messageType.equals(va[k], vb[k])); case "enum": return keys.every((k) => scalarEquals(ScalarType.INT32, va[k], vb[k])); case "scalar": const scalarType = m.V.T; return keys.every((k) => scalarEquals(scalarType, va[k], vb[k])); } break; } }); }, // TODO use isFieldSet() here to support future field presence clone(message) { const type = message.getType(), target = new type(), any = target; for (const member of type.fields.byMember()) { const source = message[member.localName]; let copy; if (member.repeated) { copy = source.map(cloneSingularField); } else if (member.kind == "map") { copy = any[member.localName]; for (const [key, v] of Object.entries(source)) { copy[key] = cloneSingularField(v); } } else if (member.kind == "oneof") { const f = member.findField(source.case); copy = f ? { case: source.case, value: cloneSingularField(source.value) } : { case: undefined }; } else { copy = cloneSingularField(source); } any[member.localName] = copy; } for (const uf of type.runtime.bin.listUnknownFields(message)) { type.runtime.bin.onUnknownField(any, uf.no, uf.wireType, uf.data); } return target; }, }; } // clone a single field value - i.e. the element type of repeated fields, the value type of maps function cloneSingularField(value) { if (value === undefined) { return value; } if (isMessage(value)) { return value.clone(); } if (value instanceof Uint8Array) { const c = new Uint8Array(value.byteLength); c.set(value); return c; } return value; } // converts any ArrayLike to Uint8Array if necessary. function toU8Arr(input) { return input instanceof Uint8Array ? input : new Uint8Array(input); }