Comparing version
@@ -40,2 +40,3 @@ "use strict"; | ||
.addHashEntry(generateFromJson(typeMap, fullName, message)) | ||
.addHashEntry(generateFromPartial(typeMap, fullName, message)) | ||
.addHashEntry(generateToJson(typeMap, fullName, message)) | ||
@@ -62,2 +63,3 @@ .endHash() | ||
file = addLongUtilityMethod(file); | ||
file = addDeepPartialType(file); | ||
let hasAnyTimestamps = false; | ||
@@ -82,2 +84,18 @@ visit(fileDesc, (_, messageType) => { | ||
} | ||
function addDeepPartialType(file) { | ||
return file.addCode(ts_poet_1.CodeBlock.empty() | ||
.add('export type DeepPartial<T> = {%>\n') | ||
.add('[P in keyof T]?: T[P] extends Array<infer U>\n') | ||
.add('? Array<DeepPartial<U>>\n') | ||
.add(': T[P] extends ReadonlyArray<infer U>\n') | ||
.add('? ReadonlyArray<DeepPartial<U>>\n') | ||
.add(': T[P] extends Date | Function | Uint8Array | undefined\n') | ||
.add('? T[P]\n') | ||
.add(': T[P] extends infer U | undefined\n') | ||
.add('? DeepPartial<U>\n') | ||
.add(': T[P] extends object\n') | ||
.add('? DeepPartial<T[P]>\n') | ||
.add(': T[P]\n%<') | ||
.add('};')); | ||
} | ||
function addTimestampMethods(file) { | ||
@@ -456,2 +474,52 @@ const timestampType = 'Timestamp@./google/protobuf/timestamp'; | ||
} | ||
function generateFromPartial(typeMap, fullName, messageDesc) { | ||
// create the basic function declaration | ||
let func = ts_poet_1.FunctionSpec.create('fromPartial') | ||
.addParameter('object', `DeepPartial<${fullName}>`) | ||
.returns(fullName); | ||
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); | ||
const readSnippet = (from) => { | ||
if (types_1.isEnum(field) || types_1.isPrimitive(field) || types_1.isTimestamp(field) || types_1.isValueType(field)) { | ||
return ts_poet_1.CodeBlock.of(from); | ||
} | ||
else if (types_1.isMessage(field)) { | ||
return ts_poet_1.CodeBlock.of('%T.fromPartial(%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 | ||
return func.addStatement('return message'); | ||
} | ||
function generateService(typeMap, fileDesc, serviceDesc) { | ||
@@ -458,0 +526,0 @@ let service = ts_poet_1.InterfaceSpec.create(serviceDesc.name).addModifiers(ts_poet_1.Modifier.EXPORT); |
@@ -231,3 +231,3 @@ "use strict"; | ||
const mappedTypes = { | ||
'.google.protobuf.Timestamp': ts_poet_1.TypeNames.unionType(ts_poet_1.TypeNames.DATE, ts_poet_1.TypeNames.UNDEFINED) | ||
'.google.protobuf.Timestamp': ts_poet_1.TypeNames.DATE | ||
}; | ||
@@ -234,0 +234,0 @@ function isTimestamp(field) { |
{ | ||
"name": "ts-proto", | ||
"version": "1.2.2", | ||
"version": "1.3.0", | ||
"description": "", | ||
@@ -25,5 +25,5 @@ "main": "build/plugin.js", | ||
"prettier": "^1.16.4", | ||
"ts-jest": "^24.0.1", | ||
"ts-node": "^8.0.3", | ||
"typescript": "^3.4.1" | ||
"ts-jest": "^24.0.2", | ||
"ts-node": "^8.3.0", | ||
"typescript": "^3.5.2" | ||
}, | ||
@@ -30,0 +30,0 @@ "dependencies": { |
@@ -84,2 +84,3 @@ import { | ||
.addHashEntry(generateFromJson(typeMap, fullName, message)) | ||
.addHashEntry(generateFromPartial(typeMap, fullName, message)) | ||
.addHashEntry(generateToJson(typeMap, fullName, message)) | ||
@@ -111,2 +112,3 @@ .endHash() | ||
file = addLongUtilityMethod(file); | ||
file = addDeepPartialType(file); | ||
@@ -138,2 +140,21 @@ let hasAnyTimestamps = false; | ||
function addDeepPartialType(file: FileSpec): FileSpec { | ||
return file.addCode( | ||
CodeBlock.empty() | ||
.add('export type DeepPartial<T> = {%>\n') | ||
.add('[P in keyof T]?: T[P] extends Array<infer U>\n') | ||
.add('? Array<DeepPartial<U>>\n') | ||
.add(': T[P] extends ReadonlyArray<infer U>\n') | ||
.add('? ReadonlyArray<DeepPartial<U>>\n') | ||
.add(': T[P] extends Date | Function | Uint8Array | undefined\n') | ||
.add('? T[P]\n') | ||
.add(': T[P] extends infer U | undefined\n') | ||
.add('? DeepPartial<U>\n') | ||
.add(': T[P] extends object\n') | ||
.add('? DeepPartial<T[P]>\n') | ||
.add(': T[P]\n%<') | ||
.add('};') | ||
); | ||
} | ||
function addTimestampMethods(file: FileSpec): FileSpec { | ||
@@ -559,2 +580,54 @@ const timestampType = 'Timestamp@./google/protobuf/timestamp'; | ||
function generateFromPartial(typeMap: TypeMap, fullName: string, messageDesc: DescriptorProto): FunctionSpec { | ||
// create the basic function declaration | ||
let func = FunctionSpec.create('fromPartial') | ||
.addParameter('object', `DeepPartial<${fullName}>`) | ||
.returns(fullName); | ||
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); | ||
const readSnippet = (from: string): CodeBlock => { | ||
if (isEnum(field) || isPrimitive(field) || isTimestamp(field) || isValueType(field)) { | ||
return CodeBlock.of(from); | ||
} else if (isMessage(field)) { | ||
return CodeBlock.of('%T.fromPartial(%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 | ||
return func.addStatement('return message'); | ||
} | ||
function generateService( | ||
@@ -651,3 +724,3 @@ typeMap: TypeMap, | ||
const inputTypeDesc = inputType[2] as DescriptorProto; | ||
const outputTypeDesc= outputType[2] as DescriptorProto; | ||
const outputTypeDesc = outputType[2] as DescriptorProto; | ||
if (hasSingleRepeatedField(inputTypeDesc) && hasSingleRepeatedField(outputTypeDesc)) { | ||
@@ -654,0 +727,0 @@ const singleMethodName = methodDesc.name.replace('Batch', 'Get'); |
@@ -237,3 +237,3 @@ import { google } from '../build/pbjs'; | ||
const mappedTypes: { [key: string]: TypeName } = { | ||
'.google.protobuf.Timestamp': TypeNames.unionType(TypeNames.DATE, TypeNames.UNDEFINED) | ||
'.google.protobuf.Timestamp': TypeNames.DATE | ||
}; | ||
@@ -240,0 +240,0 @@ |
@@ -0,4 +1,8 @@ | ||
import { google } from '../build/pbjs'; | ||
import { messageToTypeName } from '../src/types'; | ||
import { TypeNames } from 'ts-poet'; | ||
import DescriptorProto = google.protobuf.DescriptorProto; | ||
const fakeProto = (undefined as any) as DescriptorProto; | ||
describe('main', () => { | ||
@@ -8,4 +12,4 @@ describe('messageToTypeName', () => { | ||
// these are not very useful tests now that this is just a map lookup | ||
const typeMap = new Map<string, [string, string]>(); | ||
typeMap.set('namespace.Message', ['namespace', 'Message']); | ||
const typeMap = new Map<string, [string, string, DescriptorProto]>(); | ||
typeMap.set('namespace.Message', ['namespace', 'Message', fakeProto]); | ||
expect(messageToTypeName(typeMap, '.namespace.Message')).toEqual(TypeNames.anyType('Message@./namespace')); | ||
@@ -15,4 +19,4 @@ }); | ||
it('handles nested messages', () => { | ||
const typeMap = new Map<string, [string, string]>(); | ||
typeMap.set('namespace.Message.Inner', ['namespace', 'Message_Inner']); | ||
const typeMap = new Map<string, [string, string, DescriptorProto]>(); | ||
typeMap.set('namespace.Message.Inner', ['namespace', 'Message_Inner', fakeProto]); | ||
expect(messageToTypeName(typeMap, '.namespace.Message.Inner')).toEqual( | ||
@@ -24,3 +28,3 @@ TypeNames.anyType('Message_Inner@./namespace') | ||
it('handles value types', () => { | ||
const typeMap = new Map<string, [string, string]>(); | ||
const typeMap = new Map<string, [string, string, DescriptorProto]>(); | ||
expect(messageToTypeName(typeMap, '.google.protobuf.StringValue')).toEqual( | ||
@@ -27,0 +31,0 @@ TypeNames.unionType(TypeNames.STRING, TypeNames.UNDEFINED) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
836822
1.53%11926
1.16%