ts-proto
Version:
[](https://www.npmjs.com/package/ts-proto) [](https://github.com/stephenh/ts-proto/actions)
362 lines (361 loc) • 18.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isWrapperType = isWrapperType;
exports.generateWrapDeep = generateWrapDeep;
exports.generateUnwrapDeep = generateUnwrapDeep;
exports.generateWrapShallow = generateWrapShallow;
exports.generateUnwrapShallow = generateUnwrapShallow;
const ts_poet_1 = require("ts-poet");
const utils_1 = require("./utils");
const types_1 = require("./types");
const options_1 = require("./options");
/** Whether we need to generate `.wrap` and `.unwrap` methods for the given type. */
function isWrapperType(fullProtoTypeName) {
return ((0, types_1.isStructTypeName)(fullProtoTypeName) ||
(0, types_1.isAnyValueTypeName)(fullProtoTypeName) ||
(0, types_1.isListValueTypeName)(fullProtoTypeName) ||
(0, types_1.isFieldMaskTypeName)(fullProtoTypeName));
}
/**
* Converts ts-proto's idiomatic Struct/Value/ListValue representation to the proto messages.
*
* We do this deeply b/c NestJS does not invoke wrappers recursively.
*/
function generateWrapDeep(ctx, fullProtoTypeName, fieldNames) {
const chunks = [];
if ((0, types_1.isStructTypeName)(fullProtoTypeName)) {
let setStatement = `struct.fields[key] = ${(0, utils_1.wrapTypeName)(ctx.options, "Value")}.wrap(object[key]);`;
let defaultFields = "struct.fields ??= {};";
if (ctx.options.useMapType) {
setStatement = `struct.fields.set(key, ${(0, utils_1.wrapTypeName)(ctx.options, "Value")}.wrap(object[key]));`;
defaultFields = "struct.fields ??= new Map<string, any | undefined>();";
}
if (ctx.options.useOptionals !== "all")
defaultFields = "";
chunks.push((0, ts_poet_1.code) `wrap(object: {[key: string]: any} | undefined): ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")} {
const struct = createBase${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}();
${defaultFields}
if (object !== undefined) {
for (const key of ${ctx.utils.globalThis}.Object.keys(object)) {
${setStatement}
}
}
return struct;
}`);
}
if ((0, types_1.isAnyValueTypeName)(fullProtoTypeName)) {
// Turn ts-proto representation --> proto representation
chunks.push((0, ts_poet_1.code) `wrap(value: any): ${(0, utils_1.wrapTypeName)(ctx.options, "Value")} {
const result = {} as any;
if (value === null) {
result.${fieldNames.nullValue} = ${(0, utils_1.wrapTypeName)(ctx.options, "NullValue")}.NULL_VALUE;
} else if (typeof value === 'boolean') {
result.${fieldNames.boolValue} = value;
} else if (typeof value === 'number') {
result.${fieldNames.numberValue} = value;
} else if (typeof value === 'string') {
result.${fieldNames.stringValue} = value;
} else if (${ctx.utils.globalThis}.Array.isArray(value)) {
result.${fieldNames.listValue} = ${(0, utils_1.wrapTypeName)(ctx.options, "ListValue")}.wrap(value);
} else if (typeof value === 'object') {
result.${fieldNames.structValue} = ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}.wrap(value);
} else if (typeof value !== 'undefined') {
throw new ${ctx.utils.globalThis}.Error('Unsupported any value type: ' + typeof value);
}
return result;
}`);
}
if ((0, types_1.isListValueTypeName)(fullProtoTypeName)) {
const maybeReadyOnly = ctx.options.useReadonlyTypes ? "Readonly" : "";
chunks.push((0, ts_poet_1.code) `wrap(array: ${maybeReadyOnly}Array<any> | undefined): ${(0, utils_1.wrapTypeName)(ctx.options, "ListValue")} {
const result = createBase${(0, utils_1.wrapTypeName)(ctx.options, "ListValue")}()${maybeAsAny(ctx.options)};
result.values = (array ?? []).map(${(0, utils_1.wrapTypeName)(ctx.options, "Value")}.wrap);
return result;
}`);
}
if ((0, types_1.isFieldMaskTypeName)(fullProtoTypeName)) {
chunks.push((0, ts_poet_1.code) `wrap(paths: ${maybeReadonly(ctx.options)} string[]): ${(0, utils_1.wrapTypeName)(ctx.options, "FieldMask")} {
const result = createBase${(0, utils_1.wrapTypeName)(ctx.options, "FieldMask")}()${maybeAsAny(ctx.options)};
result.paths = paths;
return result;
}`);
}
return chunks;
}
/**
* Converts proto's Struct/Value?listValue messages to ts-proto's idiomatic representation.
*
* We do this deeply b/c NestJS does not invoke wrappers recursively.
*/
function generateUnwrapDeep(ctx, fullProtoTypeName, fieldNames) {
const chunks = [];
if ((0, types_1.isStructTypeName)(fullProtoTypeName)) {
if (ctx.options.useMapType) {
chunks.push((0, ts_poet_1.code) `unwrap(message: ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}: {[key: string]: any} {
const object: { [key: string]: any } = {};
if (message.fields) {
for (const key of message.fields.keys()) {
object[key] = Value.unwrap(message.fields.get(key));
}
}
return object;
}`);
}
else {
chunks.push((0, ts_poet_1.code) `unwrap(message: ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}): {[key: string]: any} {
const object: { [key: string]: any } = {};
if (message.fields) {
for (const key of ${ctx.utils.globalThis}.Object.keys(message.fields)) {
object[key] = Value.unwrap(message.fields[key]);
}
}
return object;
}`);
}
}
if ((0, types_1.isAnyValueTypeName)(fullProtoTypeName)) {
// We check hasOwnProperty because the incoming `message` has been serde-ing
// by the NestJS/protobufjs runtime, and so has a base class with default values
// that throw off the simpler checks we do in generateUnwrapShallow
chunks.push((0, ts_poet_1.code) `unwrap(message: any): string | number | boolean | Object | null | Array<any> | undefined {
if (message?.hasOwnProperty('${fieldNames.stringValue}') && message.${fieldNames.stringValue} !== undefined) {
return message.${fieldNames.stringValue};
} else if (message?.hasOwnProperty('${fieldNames.numberValue}') && message?.${fieldNames.numberValue} !== undefined) {
return message.${fieldNames.numberValue};
} else if (message?.hasOwnProperty('${fieldNames.boolValue}') && message?.${fieldNames.boolValue} !== undefined) {
return message.${fieldNames.boolValue};
} else if (message?.hasOwnProperty('${fieldNames.structValue}') && message?.${fieldNames.structValue} !== undefined) {
return ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}.unwrap(message.${fieldNames.structValue} as any);
} else if (message?.hasOwnProperty('${fieldNames.listValue}') && message?.${fieldNames.listValue} !== undefined) {
return ${(0, utils_1.wrapTypeName)(ctx.options, "ListValue")}.unwrap(message.${fieldNames.listValue});
} else if (message?.hasOwnProperty('${fieldNames.nullValue}') && message?.${fieldNames.nullValue} !== undefined) {
return null;
}
return undefined;
}`);
}
if ((0, types_1.isListValueTypeName)(fullProtoTypeName)) {
chunks.push((0, ts_poet_1.code) `unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : (0, utils_1.wrapTypeName)(ctx.options, "ListValue")}): Array<any> {
if (message?.hasOwnProperty('values') && ${ctx.utils.globalThis}.Array.isArray(message.values)) {
return message.values.map(Value.unwrap);
} else {
return message as any;
}
}`);
}
if ((0, types_1.isFieldMaskTypeName)(fullProtoTypeName)) {
chunks.push(generateFieldMaskUnwrap(ctx));
}
return chunks;
}
/**
* Converts ts-proto's idiomatic Struct/Value/ListValue representation to the proto messages.
*
* We do this shallow's b/c ts-proto's encode methods handle the recursion.
*/
function generateWrapShallow(ctx, fullProtoTypeName, fieldNames) {
const chunks = [];
if ((0, types_1.isStructTypeName)(fullProtoTypeName)) {
let setStatement = "struct.fields[key] = object[key];";
let defaultFields = "struct.fields ??= {};";
if (ctx.options.useMapType) {
setStatement = "struct.fields.set(key, object[key]);";
defaultFields = "struct.fields ??= new Map<string, any | undefined>();";
}
if (ctx.options.useOptionals !== "all")
defaultFields = "";
chunks.push((0, ts_poet_1.code) `wrap(object: {[key: string]: any} | undefined): ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")} {
const struct = createBase${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}();
${defaultFields}
if (object !== undefined) {
for (const key of ${ctx.utils.globalThis}.Object.keys(object)) {
${setStatement}
}
}
return struct;
}`);
}
if ((0, types_1.isAnyValueTypeName)(fullProtoTypeName)) {
if (ctx.options.oneof === options_1.OneofOption.UNIONS) {
chunks.push((0, ts_poet_1.code) `wrap(value: any): ${(0, utils_1.wrapTypeName)(ctx.options, "Value")} {
const result = createBase${(0, utils_1.wrapTypeName)(ctx.options, "Value")}()${maybeAsAny(ctx.options)};
if (value === null) {
result.kind = {$case: '${fieldNames.nullValue}', ${fieldNames.nullValue}: ${(0, utils_1.wrapTypeName)(ctx.options, "NullValue")}.NULL_VALUE};
} else if (typeof value === 'boolean') {
result.kind = {$case: '${fieldNames.boolValue}', ${fieldNames.boolValue}: value};
} else if (typeof value === 'number') {
result.kind = {$case: '${fieldNames.numberValue}', ${fieldNames.numberValue}: value};
} else if (typeof value === 'string') {
result.kind = {$case: '${fieldNames.stringValue}', ${fieldNames.stringValue}: value};
} else if (${ctx.utils.globalThis}.Array.isArray(value)) {
result.kind = {$case: '${fieldNames.listValue}', ${fieldNames.listValue}: value};
} else if (typeof value === 'object') {
result.kind = {$case: '${fieldNames.structValue}', ${fieldNames.structValue}: value};
} else if (typeof value !== 'undefined') {
throw new ${ctx.utils.globalThis}.Error('Unsupported any value type: ' + typeof value);
}
return result;
}`);
}
else if (ctx.options.oneof === options_1.OneofOption.UNIONS_VALUE) {
chunks.push((0, ts_poet_1.code) `wrap(value: any): ${(0, utils_1.wrapTypeName)(ctx.options, "Value")} {
const result = createBase${(0, utils_1.wrapTypeName)(ctx.options, "Value")}()${maybeAsAny(ctx.options)};
if (value === null) {
result.kind = {$case: '${fieldNames.nullValue}', value: ${(0, utils_1.wrapTypeName)(ctx.options, "NullValue")}.NULL_VALUE};
} else if (typeof value === 'boolean') {
result.kind = {$case: '${fieldNames.boolValue}', value };
} else if (typeof value === 'number') {
result.kind = {$case: '${fieldNames.numberValue}', value };
} else if (typeof value === 'string') {
result.kind = {$case: '${fieldNames.stringValue}', value };
} else if (${ctx.utils.globalThis}.Array.isArray(value)) {
result.kind = {$case: '${fieldNames.listValue}', value };
} else if (typeof value === 'object') {
result.kind = {$case: '${fieldNames.structValue}', value };
} else if (typeof value !== 'undefined') {
throw new ${ctx.utils.globalThis}.Error('Unsupported any value type: ' + typeof value);
}
return result;
}`);
}
else {
chunks.push((0, ts_poet_1.code) `wrap(value: any): ${(0, utils_1.wrapTypeName)(ctx.options, "Value")} {
const result = createBase${(0, utils_1.wrapTypeName)(ctx.options, "Value")}()${maybeAsAny(ctx.options)};
if (value === null) {
result.${fieldNames.nullValue} = ${(0, utils_1.wrapTypeName)(ctx.options, "NullValue")}.NULL_VALUE;
} else if (typeof value === 'boolean') {
result.${fieldNames.boolValue} = value;
} else if (typeof value === 'number') {
result.${fieldNames.numberValue} = value;
} else if (typeof value === 'string') {
result.${fieldNames.stringValue} = value;
} else if (${ctx.utils.globalThis}.Array.isArray(value)) {
result.${fieldNames.listValue} = value;
} else if (typeof value === 'object') {
result.${fieldNames.structValue} = value;
} else if (typeof value !== 'undefined') {
throw new ${ctx.utils.globalThis}.Error('Unsupported any value type: ' + typeof value);
}
return result;
}`);
}
}
if ((0, types_1.isListValueTypeName)(fullProtoTypeName)) {
const maybeReadyOnly = ctx.options.useReadonlyTypes ? "Readonly" : "";
chunks.push((0, ts_poet_1.code) `wrap(array: ${maybeReadyOnly}Array<any> | undefined): ${(0, utils_1.wrapTypeName)(ctx.options, "ListValue")} {
const result = createBase${(0, utils_1.wrapTypeName)(ctx.options, "ListValue")}()${maybeAsAny(ctx.options)};
result.values = array ?? [];
return result;
}`);
}
if ((0, types_1.isFieldMaskTypeName)(fullProtoTypeName)) {
chunks.push((0, ts_poet_1.code) `wrap(paths: ${maybeReadonly(ctx.options)} string[]): ${(0, utils_1.wrapTypeName)(ctx.options, "FieldMask")} {
const result = createBase${(0, utils_1.wrapTypeName)(ctx.options, "FieldMask")}()${maybeAsAny(ctx.options)};
result.paths = paths;
return result;
}`);
}
return chunks;
}
/**
* Converts proto's Struct/Value?listValue messages to ts-proto's idiomatic representation.
*
* We do this shallowly b/c ts-proto's decode methods handle recursion.
*/
function generateUnwrapShallow(ctx, fullProtoTypeName, fieldNames) {
const chunks = [];
if ((0, types_1.isStructTypeName)(fullProtoTypeName)) {
if (ctx.options.useMapType) {
chunks.push((0, ts_poet_1.code) `unwrap(message: ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}): {[key: string]: any} {
const object: { [key: string]: any } = {};
if (message.fields) {
for (const key of message.fields.keys()) {
object[key] = message.fields.get(key);
}
}
return object;
}`);
}
else {
chunks.push((0, ts_poet_1.code) `unwrap(message: ${(0, utils_1.wrapTypeName)(ctx.options, "Struct")}): {[key: string]: any} {
const object: { [key: string]: any } = {};
if (message.fields) {
for (const key of ${ctx.utils.globalThis}.Object.keys(message.fields)) {
object[key] = message.fields[key];
}
}
return object;
}`);
}
}
if ((0, types_1.isAnyValueTypeName)(fullProtoTypeName)) {
if (ctx.options.oneof === options_1.OneofOption.UNIONS) {
chunks.push((0, ts_poet_1.code) `unwrap(message: ${(0, utils_1.wrapTypeName)(ctx.options, "Value")}): string | number | boolean | Object | null | Array<any> | undefined {
if (message.kind?.$case === '${fieldNames.nullValue}') {
return null;
} else if (message.kind?.$case === '${fieldNames.numberValue}') {
return message.kind?.${fieldNames.numberValue};
} else if (message.kind?.$case === '${fieldNames.stringValue}') {
return message.kind?.${fieldNames.stringValue};
} else if (message.kind?.$case === '${fieldNames.boolValue}') {
return message.kind?.${fieldNames.boolValue};
} else if (message.kind?.$case === '${fieldNames.structValue}') {
return message.kind?.${fieldNames.structValue};
} else if (message.kind?.$case === '${fieldNames.listValue}') {
return message.kind?.${fieldNames.listValue};
} else {
return undefined;
}
}`);
}
else if (ctx.options.oneof === options_1.OneofOption.UNIONS_VALUE) {
chunks.push((0, ts_poet_1.code) `unwrap(message: ${(0, utils_1.wrapTypeName)(ctx.options, "Value")}): string | number | boolean | Object | null | Array<any> | undefined {
return (message.kind?.$case === '${fieldNames.nullValue}') ? null : message.kind?.value;
}`);
}
else {
chunks.push((0, ts_poet_1.code) `unwrap(message: any): string | number | boolean | Object | null | Array<any> | undefined {
if (message.${fieldNames.stringValue} !== undefined) {
return message.${fieldNames.stringValue};
} else if (message?.${fieldNames.numberValue} !== undefined) {
return message.${fieldNames.numberValue};
} else if (message?.${fieldNames.boolValue} !== undefined) {
return message.${fieldNames.boolValue};
} else if (message?.${fieldNames.structValue} !== undefined) {
return message.${fieldNames.structValue} as any;
} else if (message?.${fieldNames.listValue} !== undefined) {
return message.${fieldNames.listValue};
} else if (message?.${fieldNames.nullValue} !== undefined) {
return null;
}
return undefined;
}`);
}
}
if ((0, types_1.isListValueTypeName)(fullProtoTypeName)) {
chunks.push((0, ts_poet_1.code) `unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : (0, utils_1.wrapTypeName)(ctx.options, "ListValue")}): Array<any> {
if (message?.hasOwnProperty('values') && ${ctx.utils.globalThis}.Array.isArray(message.values)) {
return message.values;
} else {
return message as any;
}
}`);
}
if ((0, types_1.isFieldMaskTypeName)(fullProtoTypeName)) {
chunks.push(generateFieldMaskUnwrap(ctx));
}
return chunks;
}
function generateFieldMaskUnwrap(ctx) {
const returnType = ctx.options.useOptionals === "all" ? "string[] | undefined" : "string[]";
const pathModifier = ctx.options.useOptionals === "all" ? "?" : "";
return (0, ts_poet_1.code) `unwrap(message: ${ctx.options.useReadonlyTypes ? "any" : (0, utils_1.wrapTypeName)(ctx.options, "FieldMask")}): ${returnType} {
return message${pathModifier}.paths;
}`;
}
function maybeReadonly(options) {
return options.useReadonlyTypes ? "readonly " : "";
}
function maybeAsAny(options) {
return options.useReadonlyTypes ? " as any" : "";
}