Comparing version 1.1.4 to 1.2.0
@@ -39,2 +39,4 @@ "use strict"; | ||
.addHashEntry(generateDecode(typeMap, fullName, message)) | ||
.addHashEntry(generateFromJson(typeMap, fullName, message)) | ||
.addHashEntry(generateToJson(typeMap, fullName, message)) | ||
.endHash() | ||
@@ -44,2 +46,9 @@ .add(';') | ||
file = file.addCode(staticMethods); | ||
}, (fullName, enumDesc) => { | ||
let staticMethods = ts_poet_1.CodeBlock.empty() | ||
.beginControlFlow('export namespace %L', fullName) | ||
.addFunction(generateEnumFromJson(fullName, enumDesc)) | ||
.addFunction(generateEnumToJson(fullName, enumDesc)) | ||
.endControlFlow(); | ||
file = file.addCode(staticMethods); | ||
}); | ||
@@ -54,2 +63,9 @@ visitServices(fileDesc, serviceDesc => { | ||
file = addLongUtilityMethod(file); | ||
let hasAnyTimestamps = false; | ||
visit(fileDesc, (_, messageType) => { | ||
hasAnyTimestamps = hasAnyTimestamps || sequency_1.asSequence(messageType.field).any(types_1.isTimestamp); | ||
}); | ||
if (hasAnyTimestamps) { | ||
file = addTimestampMethods(file); | ||
} | ||
return file; | ||
@@ -62,7 +78,36 @@ } | ||
.addCodeBlock(ts_poet_1.CodeBlock.empty() | ||
.beginControlFlow('if (long.gt(Number.MAX_VALUE))') | ||
.addStatement('throw new Error("Value is larger than Number.MAX_VALUE");') | ||
.beginControlFlow('if (long.gt(Number.MAX_SAFE_INTEGER))') | ||
.addStatement('throw new Error("Value is larger than Number.MAX_SAFE_INTEGER")') | ||
.endControlFlow() | ||
.addStatement('return long.toNumber()'))); | ||
} | ||
function addTimestampMethods(file) { | ||
const timestampType = 'Timestamp@./google/protobuf/timestamp'; | ||
return file | ||
.addFunction(ts_poet_1.FunctionSpec.create('toTimestamp') | ||
.addParameter('date', 'Date') | ||
.returns(timestampType) | ||
.addCodeBlock(ts_poet_1.CodeBlock.empty() | ||
.addStatement('const seconds = date.getTime() / 1_000') | ||
.addStatement('const nanos = (date.getTime() %% 1_000) * 1_000_000') | ||
.addStatement('return { seconds, nanos }'))) | ||
.addFunction(ts_poet_1.FunctionSpec.create('fromTimestamp') | ||
.addParameter('t', timestampType) | ||
.returns('Date') | ||
.addCodeBlock(ts_poet_1.CodeBlock.empty() | ||
.addStatement('let millis = t.seconds * 1_000') | ||
.addStatement('millis += t.nanos / 1_000_000') | ||
.addStatement('return new Date(millis)'))) | ||
.addFunction(ts_poet_1.FunctionSpec.create('fromJsonTimestamp') | ||
.addParameter('o', 'any') | ||
.returns('Date') | ||
.addCodeBlock(ts_poet_1.CodeBlock.empty() | ||
.beginControlFlow('if (o instanceof Date)') | ||
.addStatement('return o') | ||
.nextControlFlow('else if (typeof o === "string")') | ||
.addStatement('return new Date(o)') | ||
.nextControlFlow('else') | ||
.addStatement('return fromTimestamp(Timestamp.fromJSON(o))') | ||
.endControlFlow())); | ||
} | ||
function generateEnum(fullName, enumDesc) { | ||
@@ -75,2 +120,35 @@ let spec = ts_poet_1.EnumSpec.create(fullName).addModifiers(ts_poet_1.Modifier.EXPORT); | ||
} | ||
function generateEnumFromJson(fullName, enumDesc) { | ||
let func = ts_poet_1.FunctionSpec.create('fromJSON') | ||
.addParameter('object', 'any') | ||
.addModifiers(ts_poet_1.Modifier.EXPORT) | ||
.returns(fullName); | ||
let body = ts_poet_1.CodeBlock.empty().beginControlFlow('switch (object)'); | ||
for (const valueDesc of enumDesc.value) { | ||
body = body | ||
.add('case %L:\n', valueDesc.number) | ||
.add('case %S:%>\n', valueDesc.name) | ||
.addStatement('return %L.%L%<', fullName, valueDesc.name); | ||
} | ||
body = body | ||
.add('default:%>\n') | ||
.addStatement('throw new Error(`Invalid value ${object}`)%<') | ||
.endControlFlow(); | ||
return func.addCodeBlock(body); | ||
} | ||
function generateEnumToJson(fullName, enumDesc) { | ||
let func = ts_poet_1.FunctionSpec.create('toJSON') | ||
.addParameter('object', fullName) | ||
.addModifiers(ts_poet_1.Modifier.EXPORT) | ||
.returns('string'); | ||
let body = ts_poet_1.CodeBlock.empty().beginControlFlow('switch (object)'); | ||
for (const valueDesc of enumDesc.value) { | ||
body = body.add('case %L.%L:%>\n', fullName, valueDesc.name).addStatement('return %S%<', valueDesc.name); | ||
} | ||
body = body | ||
.add('default:%>\n') | ||
.addStatement('return "UNKNOWN"%<') | ||
.endControlFlow(); | ||
return func.addCodeBlock(body); | ||
} | ||
// Create the interface with properties | ||
@@ -149,2 +227,5 @@ function generateInterfaceDeclaration(typeMap, fullName, messageDesc) { | ||
} | ||
else if (types_1.isTimestamp(field)) { | ||
readSnippet = ts_poet_1.CodeBlock.of('fromTimestamp(%T.decode(reader, reader.uint32()))', types_1.basicTypeName(typeMap, field, true)); | ||
} | ||
else if (types_1.isMessage(field)) { | ||
@@ -212,2 +293,6 @@ readSnippet = ts_poet_1.CodeBlock.of('%T.decode(reader, reader.uint32())', types_1.basicTypeName(typeMap, field)); | ||
} | ||
else if (types_1.isTimestamp(field)) { | ||
const tag = ((field.number << 3) | 2) >>> 0; | ||
writeSnippet = place => ts_poet_1.CodeBlock.of('%T.encode(toTimestamp(%L), writer.uint32(%L).fork()).ldelim()', types_1.basicTypeName(typeMap, field, true), place, tag); | ||
} | ||
else if (types_1.isValueType(field)) { | ||
@@ -259,2 +344,118 @@ const tag = ((field.number << 3) | 2) >>> 0; | ||
} | ||
/** | ||
* Creates a function to decode a message from JSON. | ||
* | ||
* This is very similar to decode, we loop through looking for properties, with | ||
* a few special cases for https://developers.google.com/protocol-buffers/docs/proto3#json. | ||
* */ | ||
function generateFromJson(typeMap, fullName, messageDesc) { | ||
// create the basic function declaration | ||
let func = ts_poet_1.FunctionSpec.create('fromJSON') | ||
.addParameter('object', 'any') | ||
.returns(fullName); | ||
// add the message | ||
func = func.addStatement('const message = Object.create(base%L) as %L', fullName, fullName); | ||
// initialize all lists | ||
messageDesc.field.filter(types_1.isRepeated).forEach(field => { | ||
const value = types_1.isMapType(typeMap, messageDesc, field) ? '{}' : '[]'; | ||
func = func.addStatement('message.%L = %L', snakeToCamel(field.name), value); | ||
}); | ||
// add a check for each incoming field | ||
messageDesc.field.forEach(field => { | ||
const fieldName = snakeToCamel(field.name); | ||
// get a generic 'reader.doSomething' bit that is specific to the basic type | ||
const readSnippet = (from) => { | ||
if (types_1.isEnum(field)) { | ||
return ts_poet_1.CodeBlock.of('%T.fromJSON(%L)', types_1.basicTypeName(typeMap, field), from); | ||
} | ||
else if (types_1.isPrimitive(field)) { | ||
// Convert primitives using the String(value)/Number(value) cstr, except for bytes | ||
if (types_1.isBytes(field)) { | ||
return ts_poet_1.CodeBlock.of('%L', from); | ||
} | ||
else { | ||
const cstr = capitalize(types_1.basicTypeName(typeMap, field, true).toString()); | ||
return ts_poet_1.CodeBlock.of('%L(%L)', cstr, from); | ||
} | ||
// if (basicLongWireType(field.type) !== undefined) { | ||
// readSnippet = CodeBlock.of('longToNumber(%L as Long)', readSnippet); | ||
// } | ||
} | ||
else if (types_1.isTimestamp(field)) { | ||
return ts_poet_1.CodeBlock.of('fromJsonTimestamp(%L)', from); | ||
} | ||
else if (types_1.isValueType(field)) { | ||
const cstr = capitalize(types_1.basicTypeName(typeMap, field, false).typeChoices[0].toString()); | ||
return ts_poet_1.CodeBlock.of('%L(%L)', cstr, from); | ||
} | ||
else if (types_1.isMessage(field)) { | ||
return ts_poet_1.CodeBlock.of('%T.fromJSON(%L)', types_1.basicTypeName(typeMap, field), from); | ||
} | ||
else { | ||
throw new Error(`Unhandled field ${field}`); | ||
} | ||
}; | ||
// and then use the snippet to handle repeated fields if necessary | ||
func = func.beginControlFlow('if (object.%L)', fieldName); | ||
if (types_1.isRepeated(field)) { | ||
if (types_1.isMapType(typeMap, messageDesc, field)) { | ||
func = func | ||
.addStatement(`const entry = %L`, readSnippet(`object.${fieldName}`)) | ||
.beginControlFlow('if (entry.value)') | ||
.addStatement('message.%L[entry.key] = entry.value', fieldName) | ||
.endControlFlow(); | ||
} | ||
else { | ||
func = func | ||
.beginControlFlow('for (const e of object.%L)', fieldName) | ||
.addStatement(`message.%L.push(%L)`, fieldName, readSnippet('e')) | ||
.endControlFlow(); | ||
} | ||
} | ||
else { | ||
func = func.addStatement(`message.%L = %L`, fieldName, readSnippet(`object.${fieldName}`)); | ||
} | ||
func = func.endControlFlow(); | ||
}); | ||
// and then wrap up the switch/while/return | ||
func = func.addStatement('return message'); | ||
return func; | ||
} | ||
function generateToJson(typeMap, fullName, messageDesc) { | ||
// create the basic function declaration | ||
let func = ts_poet_1.FunctionSpec.create('toJSON') | ||
.addParameter('message', fullName) | ||
.returns('unknown'); | ||
func = func.addCodeBlock(ts_poet_1.CodeBlock.empty().addStatement('const obj: any = {}')); | ||
// then add a case for each field | ||
messageDesc.field.forEach(field => { | ||
const fieldName = snakeToCamel(field.name); | ||
const readSnippet = (from) => { | ||
if (types_1.isEnum(field)) { | ||
return ts_poet_1.CodeBlock.of('%T.toJSON(%L)', types_1.basicTypeName(typeMap, field), from); | ||
} | ||
else if (types_1.isTimestamp(field)) { | ||
return ts_poet_1.CodeBlock.of('%L !== undefined ? %L.toISOString() : null', from, from); | ||
} | ||
else if (types_1.isMessage(field) && !types_1.isValueType(field) && !types_1.isMapType(typeMap, messageDesc, field)) { | ||
return ts_poet_1.CodeBlock.of('%L ? %T.toJSON(%L) : %L', from, types_1.basicTypeName(typeMap, field, true), from, types_1.defaultValue(field.type)); | ||
} | ||
else { | ||
return ts_poet_1.CodeBlock.of('%L || %L', from, types_1.defaultValue(field.type)); | ||
} | ||
}; | ||
if (types_1.isRepeated(field) && !types_1.isMapType(typeMap, messageDesc, field)) { | ||
func = func | ||
.beginControlFlow('if (message.%L)', fieldName) | ||
.addStatement('obj.%L = message.%L.map(e => %L)', fieldName, fieldName, readSnippet('e')) | ||
.nextControlFlow('else') | ||
.addStatement('obj.%L = []', fieldName) | ||
.endControlFlow(); | ||
} | ||
else { | ||
func = func.addStatement('obj.%L = %L', fieldName, readSnippet(`message.${fieldName}`)); | ||
} | ||
}); | ||
return func.addStatement('return obj'); | ||
} | ||
function generateService(typeMap, fileDesc, serviceDesc) { | ||
@@ -394,1 +595,4 @@ let service = ts_poet_1.InterfaceSpec.create(serviceDesc.name).addModifiers(ts_poet_1.Modifier.EXPORT); | ||
} | ||
function capitalize(s) { | ||
return s.substring(0, 1).toUpperCase() + s.substring(1); | ||
} |
@@ -201,6 +201,14 @@ "use strict"; | ||
exports.isPrimitive = isPrimitive; | ||
function isBytes(field) { | ||
return field.type === FieldDescriptorProto.Type.TYPE_BYTES; | ||
} | ||
exports.isBytes = isBytes; | ||
function isMessage(field) { | ||
return field.type == FieldDescriptorProto.Type.TYPE_MESSAGE; | ||
return field.type === FieldDescriptorProto.Type.TYPE_MESSAGE; | ||
} | ||
exports.isMessage = isMessage; | ||
function isEnum(field) { | ||
return field.type === FieldDescriptorProto.Type.TYPE_ENUM; | ||
} | ||
exports.isEnum = isEnum; | ||
function isWithinOneOf(field) { | ||
@@ -223,2 +231,9 @@ return field.hasOwnProperty('oneofIndex'); | ||
}; | ||
const mappedTypes = { | ||
'.google.protobuf.Timestamp': ts_poet_1.TypeNames.unionType(ts_poet_1.TypeNames.DATE, ts_poet_1.TypeNames.UNDEFINED) | ||
}; | ||
function isTimestamp(field) { | ||
return field.typeName === '.google.protobuf.Timestamp'; | ||
} | ||
exports.isTimestamp = isTimestamp; | ||
function isValueType(field) { | ||
@@ -230,6 +245,10 @@ return field.typeName in valueTypes; | ||
function messageToTypeName(typeMap, protoType, keepValueType = false) { | ||
// turn .google.protobuf.StringValue --> string | undefined | ||
// Watch for the wrapper types `.google.protobuf.StringValue` and map to `string | undefined` | ||
if (!keepValueType && protoType in valueTypes) { | ||
return valueTypes[protoType]; | ||
} | ||
// Look for other special prototypes like Timestamp that aren't technically wrapper types | ||
if (!keepValueType && protoType in mappedTypes) { | ||
return mappedTypes[protoType]; | ||
} | ||
const [module, type] = toModuleAndType(typeMap, protoType); | ||
@@ -245,3 +264,3 @@ return ts_poet_1.TypeNames.importedType(`${type}@./${module}`); | ||
function toTypeName(typeMap, messageDesc, field) { | ||
let type = basicTypeName(typeMap, field); | ||
let type = basicTypeName(typeMap, field, false); | ||
if (isRepeated(field)) { | ||
@@ -268,3 +287,4 @@ const mapType = detectMapType(typeMap, messageDesc, field); | ||
.filter(t => t.name === `${utils_1.upperFirst(fieldDesc.name)}Entry` && | ||
(t.options !== undefined && t.options != null && t.options.mapEntry)).map(mapType => { | ||
(t.options !== undefined && t.options != null && t.options.mapEntry)) | ||
.map(mapType => { | ||
const keyType = toTypeName(typeMap, messageDesc, mapType.field[0]); | ||
@@ -274,3 +294,4 @@ // use basicTypeName because we don't need the '| undefined' | ||
return { messageDesc: mapType, keyType, valueType }; | ||
}).find(_ => true); | ||
}) | ||
.find(_ => true); | ||
} | ||
@@ -277,0 +298,0 @@ return undefined; |
{ | ||
"name": "ts-proto", | ||
"version": "1.1.4", | ||
"version": "1.2.0", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "build/plugin.js", |
269
src/main.ts
@@ -11,3 +11,4 @@ import { | ||
TypeName, | ||
TypeNames | ||
TypeNames, | ||
Union | ||
} from 'ts-poet'; | ||
@@ -21,2 +22,4 @@ import { google } from '../build/pbjs'; | ||
detectMapType, | ||
isBytes, | ||
isEnum, | ||
isMapType, | ||
@@ -26,2 +29,3 @@ isMessage, | ||
isRepeated, | ||
isTimestamp, | ||
isValueType, | ||
@@ -73,14 +77,27 @@ isWithinOneOf, | ||
// then add the encoder/decoder/base instance | ||
visit(fileDesc, (fullName, message) => { | ||
file = file.addProperty(generateBaseInstance(fullName, message)); | ||
let staticMethods = CodeBlock.empty() | ||
.add('export const %L = ', fullName) | ||
.beginHash() | ||
.addHashEntry(generateEncode(typeMap, fullName, message)) | ||
.addHashEntry(generateDecode(typeMap, fullName, message)) | ||
.endHash() | ||
.add(';') | ||
.newLine(); | ||
file = file.addCode(staticMethods); | ||
}); | ||
visit( | ||
fileDesc, | ||
(fullName, message) => { | ||
file = file.addProperty(generateBaseInstance(fullName, message)); | ||
let staticMethods = CodeBlock.empty() | ||
.add('export const %L = ', fullName) | ||
.beginHash() | ||
.addHashEntry(generateEncode(typeMap, fullName, message)) | ||
.addHashEntry(generateDecode(typeMap, fullName, message)) | ||
.addHashEntry(generateFromJson(typeMap, fullName, message)) | ||
.addHashEntry(generateToJson(typeMap, fullName, message)) | ||
.endHash() | ||
.add(';') | ||
.newLine(); | ||
file = file.addCode(staticMethods); | ||
}, | ||
(fullName, enumDesc) => { | ||
let staticMethods = CodeBlock.empty() | ||
.beginControlFlow('export namespace %L', fullName) | ||
.addFunction(generateEnumFromJson(fullName, enumDesc)) | ||
.addFunction(generateEnumToJson(fullName, enumDesc)) | ||
.endControlFlow(); | ||
file = file.addCode(staticMethods); | ||
} | ||
); | ||
@@ -98,2 +115,10 @@ visitServices(fileDesc, serviceDesc => { | ||
let hasAnyTimestamps = false; | ||
visit(fileDesc, (_, messageType) => { | ||
hasAnyTimestamps = hasAnyTimestamps || asSequence(messageType.field).any(isTimestamp); | ||
}); | ||
if (hasAnyTimestamps) { | ||
file = addTimestampMethods(file); | ||
} | ||
return file; | ||
@@ -108,4 +133,4 @@ } | ||
CodeBlock.empty() | ||
.beginControlFlow('if (long.gt(Number.MAX_VALUE))') | ||
.addStatement('throw new Error("Value is larger than Number.MAX_VALUE");') | ||
.beginControlFlow('if (long.gt(Number.MAX_SAFE_INTEGER))') | ||
.addStatement('throw new Error("Value is larger than Number.MAX_SAFE_INTEGER")') | ||
.endControlFlow() | ||
@@ -117,2 +142,44 @@ .addStatement('return long.toNumber()') | ||
function addTimestampMethods(file: FileSpec): FileSpec { | ||
const timestampType = 'Timestamp@./google/protobuf/timestamp'; | ||
return file | ||
.addFunction( | ||
FunctionSpec.create('toTimestamp') | ||
.addParameter('date', 'Date') | ||
.returns(timestampType) | ||
.addCodeBlock( | ||
CodeBlock.empty() | ||
.addStatement('const seconds = date.getTime() / 1_000') | ||
.addStatement('const nanos = (date.getTime() %% 1_000) * 1_000_000') | ||
.addStatement('return { seconds, nanos }') | ||
) | ||
) | ||
.addFunction( | ||
FunctionSpec.create('fromTimestamp') | ||
.addParameter('t', timestampType) | ||
.returns('Date') | ||
.addCodeBlock( | ||
CodeBlock.empty() | ||
.addStatement('let millis = t.seconds * 1_000') | ||
.addStatement('millis += t.nanos / 1_000_000') | ||
.addStatement('return new Date(millis)') | ||
) | ||
) | ||
.addFunction( | ||
FunctionSpec.create('fromJsonTimestamp') | ||
.addParameter('o', 'any') | ||
.returns('Date') | ||
.addCodeBlock( | ||
CodeBlock.empty() | ||
.beginControlFlow('if (o instanceof Date)') | ||
.addStatement('return o') | ||
.nextControlFlow('else if (typeof o === "string")') | ||
.addStatement('return new Date(o)') | ||
.nextControlFlow('else') | ||
.addStatement('return fromTimestamp(Timestamp.fromJSON(o))') | ||
.endControlFlow() | ||
) | ||
); | ||
} | ||
function generateEnum(fullName: string, enumDesc: EnumDescriptorProto): EnumSpec { | ||
@@ -126,2 +193,37 @@ let spec = EnumSpec.create(fullName).addModifiers(Modifier.EXPORT); | ||
function generateEnumFromJson(fullName: string, enumDesc: EnumDescriptorProto): FunctionSpec { | ||
let func = FunctionSpec.create('fromJSON') | ||
.addParameter('object', 'any') | ||
.addModifiers(Modifier.EXPORT) | ||
.returns(fullName); | ||
let body = CodeBlock.empty().beginControlFlow('switch (object)'); | ||
for (const valueDesc of enumDesc.value) { | ||
body = body | ||
.add('case %L:\n', valueDesc.number) | ||
.add('case %S:%>\n', valueDesc.name) | ||
.addStatement('return %L.%L%<', fullName, valueDesc.name); | ||
} | ||
body = body | ||
.add('default:%>\n') | ||
.addStatement('throw new Error(`Invalid value ${object}`)%<') | ||
.endControlFlow(); | ||
return func.addCodeBlock(body); | ||
} | ||
function generateEnumToJson(fullName: string, enumDesc: EnumDescriptorProto): FunctionSpec { | ||
let func = FunctionSpec.create('toJSON') | ||
.addParameter('object', fullName) | ||
.addModifiers(Modifier.EXPORT) | ||
.returns('string'); | ||
let body = CodeBlock.empty().beginControlFlow('switch (object)'); | ||
for (const valueDesc of enumDesc.value) { | ||
body = body.add('case %L.%L:%>\n', fullName, valueDesc.name).addStatement('return %S%<', valueDesc.name); | ||
} | ||
body = body | ||
.add('default:%>\n') | ||
.addStatement('return "UNKNOWN"%<') | ||
.endControlFlow(); | ||
return func.addCodeBlock(body); | ||
} | ||
// Create the interface with properties | ||
@@ -215,2 +317,7 @@ function generateInterfaceDeclaration(typeMap: TypeMap, fullName: string, messageDesc: DescriptorProto) { | ||
readSnippet = CodeBlock.of('%T.decode(reader, reader.uint32()).value', basicTypeName(typeMap, field, true)); | ||
} else if (isTimestamp(field)) { | ||
readSnippet = CodeBlock.of( | ||
'fromTimestamp(%T.decode(reader, reader.uint32()))', | ||
basicTypeName(typeMap, field, true) | ||
); | ||
} else if (isMessage(field)) { | ||
@@ -276,2 +383,11 @@ readSnippet = CodeBlock.of('%T.decode(reader, reader.uint32())', basicTypeName(typeMap, field)); | ||
writeSnippet = place => CodeBlock.of('writer.uint32(%L).%L(%L)', tag, toReaderCall(field), place); | ||
} else if (isTimestamp(field)) { | ||
const tag = ((field.number << 3) | 2) >>> 0; | ||
writeSnippet = place => | ||
CodeBlock.of( | ||
'%T.encode(toTimestamp(%L), writer.uint32(%L).fork()).ldelim()', | ||
basicTypeName(typeMap, field, true), | ||
place, | ||
tag | ||
); | ||
} else if (isValueType(field)) { | ||
@@ -331,2 +447,121 @@ const tag = ((field.number << 3) | 2) >>> 0; | ||
/** | ||
* Creates a function to decode a message from JSON. | ||
* | ||
* This is very similar to decode, we loop through looking for properties, with | ||
* a few special cases for https://developers.google.com/protocol-buffers/docs/proto3#json. | ||
* */ | ||
function generateFromJson(typeMap: TypeMap, fullName: string, messageDesc: DescriptorProto): FunctionSpec { | ||
// create the basic function declaration | ||
let func = FunctionSpec.create('fromJSON') | ||
.addParameter('object', 'any') | ||
.returns(fullName); | ||
// add the message | ||
func = func.addStatement('const message = Object.create(base%L) as %L', fullName, fullName); | ||
// initialize all lists | ||
messageDesc.field.filter(isRepeated).forEach(field => { | ||
const value = isMapType(typeMap, messageDesc, field) ? '{}' : '[]'; | ||
func = func.addStatement('message.%L = %L', snakeToCamel(field.name), value); | ||
}); | ||
// add a check for each incoming field | ||
messageDesc.field.forEach(field => { | ||
const fieldName = snakeToCamel(field.name); | ||
// get a generic 'reader.doSomething' bit that is specific to the basic type | ||
const readSnippet = (from: string): CodeBlock => { | ||
if (isEnum(field)) { | ||
return CodeBlock.of('%T.fromJSON(%L)', basicTypeName(typeMap, field), from); | ||
} else if (isPrimitive(field)) { | ||
// Convert primitives using the String(value)/Number(value) cstr, except for bytes | ||
if (isBytes(field)) { | ||
return CodeBlock.of('%L', from); | ||
} else { | ||
const cstr = capitalize(basicTypeName(typeMap, field, true).toString()); | ||
return CodeBlock.of('%L(%L)', cstr, from); | ||
} | ||
// if (basicLongWireType(field.type) !== undefined) { | ||
// readSnippet = CodeBlock.of('longToNumber(%L as Long)', readSnippet); | ||
// } | ||
} else if (isTimestamp(field)) { | ||
return CodeBlock.of('fromJsonTimestamp(%L)', from); | ||
} else if (isValueType(field)) { | ||
const cstr = capitalize((basicTypeName(typeMap, field, false) as Union).typeChoices[0].toString()); | ||
return CodeBlock.of('%L(%L)', cstr, from); | ||
} else if (isMessage(field)) { | ||
return CodeBlock.of('%T.fromJSON(%L)', basicTypeName(typeMap, field), from); | ||
} else { | ||
throw new Error(`Unhandled field ${field}`); | ||
} | ||
}; | ||
// and then use the snippet to handle repeated fields if necessary | ||
func = func.beginControlFlow('if (object.%L)', fieldName); | ||
if (isRepeated(field)) { | ||
if (isMapType(typeMap, messageDesc, field)) { | ||
func = func | ||
.addStatement(`const entry = %L`, readSnippet(`object.${fieldName}`)) | ||
.beginControlFlow('if (entry.value)') | ||
.addStatement('message.%L[entry.key] = entry.value', fieldName) | ||
.endControlFlow(); | ||
} else { | ||
func = func | ||
.beginControlFlow('for (const e of object.%L)', fieldName) | ||
.addStatement(`message.%L.push(%L)`, fieldName, readSnippet('e')) | ||
.endControlFlow(); | ||
} | ||
} else { | ||
func = func.addStatement(`message.%L = %L`, fieldName, readSnippet(`object.${fieldName}`)); | ||
} | ||
func = func.endControlFlow(); | ||
}); | ||
// and then wrap up the switch/while/return | ||
func = func.addStatement('return message'); | ||
return func; | ||
} | ||
function generateToJson(typeMap: TypeMap, fullName: string, messageDesc: DescriptorProto): FunctionSpec { | ||
// create the basic function declaration | ||
let func = FunctionSpec.create('toJSON') | ||
.addParameter('message', fullName) | ||
.returns('unknown'); | ||
func = func.addCodeBlock(CodeBlock.empty().addStatement('const obj: any = {}')); | ||
// then add a case for each field | ||
messageDesc.field.forEach(field => { | ||
const fieldName = snakeToCamel(field.name); | ||
const readSnippet = (from: string): CodeBlock => { | ||
if (isEnum(field)) { | ||
return CodeBlock.of('%T.toJSON(%L)', basicTypeName(typeMap, field), from); | ||
} else if (isTimestamp(field)) { | ||
return CodeBlock.of('%L !== undefined ? %L.toISOString() : null', from, from); | ||
} else if (isMessage(field) && !isValueType(field) && !isMapType(typeMap, messageDesc, field)) { | ||
return CodeBlock.of( | ||
'%L ? %T.toJSON(%L) : %L', | ||
from, | ||
basicTypeName(typeMap, field, true), | ||
from, | ||
defaultValue(field.type) | ||
); | ||
} else { | ||
return CodeBlock.of('%L || %L', from, defaultValue(field.type)); | ||
} | ||
}; | ||
if (isRepeated(field) && !isMapType(typeMap, messageDesc, field)) { | ||
func = func | ||
.beginControlFlow('if (message.%L)', fieldName) | ||
.addStatement('obj.%L = message.%L.map(e => %L)', fieldName, fieldName, readSnippet('e')) | ||
.nextControlFlow('else') | ||
.addStatement('obj.%L = []', fieldName) | ||
.endControlFlow(); | ||
} else { | ||
func = func.addStatement('obj.%L = %L', fieldName, readSnippet(`message.${fieldName}`)); | ||
} | ||
}); | ||
return func.addStatement('return obj'); | ||
} | ||
function generateService( | ||
@@ -521,1 +756,5 @@ typeMap: TypeMap, | ||
} | ||
function capitalize(s: string): string { | ||
return s.substring(0, 1).toUpperCase() + s.substring(1); | ||
} |
@@ -205,6 +205,14 @@ import { google } from '../build/pbjs'; | ||
export function isBytes(field: FieldDescriptorProto): boolean { | ||
return field.type === FieldDescriptorProto.Type.TYPE_BYTES; | ||
} | ||
export function isMessage(field: FieldDescriptorProto): boolean { | ||
return field.type == FieldDescriptorProto.Type.TYPE_MESSAGE; | ||
return field.type === FieldDescriptorProto.Type.TYPE_MESSAGE; | ||
} | ||
export function isEnum(field: FieldDescriptorProto): boolean { | ||
return field.type === FieldDescriptorProto.Type.TYPE_ENUM; | ||
} | ||
export function isWithinOneOf(field: FieldDescriptorProto): boolean { | ||
@@ -228,2 +236,10 @@ return field.hasOwnProperty('oneofIndex'); | ||
const mappedTypes: { [key: string]: TypeName } = { | ||
'.google.protobuf.Timestamp': TypeNames.unionType(TypeNames.DATE, TypeNames.UNDEFINED) | ||
}; | ||
export function isTimestamp(field: FieldDescriptorProto): boolean { | ||
return field.typeName === '.google.protobuf.Timestamp'; | ||
} | ||
export function isValueType(field: FieldDescriptorProto): boolean { | ||
@@ -235,6 +251,10 @@ return field.typeName in valueTypes; | ||
export function messageToTypeName(typeMap: TypeMap, protoType: string, keepValueType: boolean = false): TypeName { | ||
// turn .google.protobuf.StringValue --> string | undefined | ||
// Watch for the wrapper types `.google.protobuf.StringValue` and map to `string | undefined` | ||
if (!keepValueType && protoType in valueTypes) { | ||
return valueTypes[protoType]; | ||
} | ||
// Look for other special prototypes like Timestamp that aren't technically wrapper types | ||
if (!keepValueType && protoType in mappedTypes) { | ||
return mappedTypes[protoType]; | ||
} | ||
const [module, type] = toModuleAndType(typeMap, protoType); | ||
@@ -251,3 +271,3 @@ return TypeNames.importedType(`${type}@./${module}`); | ||
export function toTypeName(typeMap: TypeMap, messageDesc: DescriptorProto, field: FieldDescriptorProto): TypeName { | ||
let type = basicTypeName(typeMap, field); | ||
let type = basicTypeName(typeMap, field, false); | ||
if (isRepeated(field)) { | ||
@@ -271,3 +291,3 @@ const mapType = detectMapType(typeMap, messageDesc, field); | ||
fieldDesc: FieldDescriptorProto | ||
): { messageDesc: DescriptorProto, keyType: TypeName, valueType: TypeName } | undefined { | ||
): { messageDesc: DescriptorProto; keyType: TypeName; valueType: TypeName } | undefined { | ||
if ( | ||
@@ -282,3 +302,4 @@ fieldDesc.label === FieldDescriptorProto.Label.LABEL_REPEATED && | ||
(t.options !== undefined && t.options != null && t.options.mapEntry) | ||
).map(mapType => { | ||
) | ||
.map(mapType => { | ||
const keyType = toTypeName(typeMap, messageDesc, mapType.field[0]); | ||
@@ -288,3 +309,4 @@ // use basicTypeName because we don't need the '| undefined' | ||
return { messageDesc: mapType, keyType, valueType }; | ||
}).find(_ => true); | ||
}) | ||
.find(_ => true); | ||
} | ||
@@ -291,0 +313,0 @@ return undefined; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
823750
11782
95