diff --git a/packages/plugin/src/message-type-extensions/well-known-types.ts b/packages/plugin/src/message-type-extensions/well-known-types.ts index 673b921e..007e046a 100644 --- a/packages/plugin/src/message-type-extensions/well-known-types.ts +++ b/packages/plugin/src/message-type-extensions/well-known-types.ts @@ -358,8 +358,9 @@ export class WellKnownTypes implements CustomMethodGenerator { * Encode \`${descriptor.name}\` to JSON object. */ function internalJsonWrite(message: ${FieldMask}, options: ${JsonWriteOptions}): ${JsonValue} { + const invalidFieldMaskJsonRegex = /[A-Z]|(_([.0-9_]|$))/g; return message.paths.map(p => { - if (p.match(/_[0-9]?_/g) || p.match(/[A-Z]/g)) + if (invalidFieldMaskJsonRegex.test(p)) throw new Error("Unable to encode FieldMask to JSON. lowerCamelCase of path name \\""+p+"\\" is irreversible."); return ${lowerCamelCase}(p); }).join(","); @@ -379,7 +380,7 @@ export class WellKnownTypes implements CustomMethodGenerator { if (str.includes('_')) throw new Error("Unable to parse FieldMask from JSON. Path names must be lowerCamelCase."); let sc = str.replace(/[A-Z]/g, letter => "_" + letter.toLowerCase()); - return (sc[0] === "_") ? sc.substring(1) : sc; + return sc; }; target.paths = json.split(",").map(camelToSnake); return target; diff --git a/packages/test-generated/spec/google.protobuf.field_mask.spec.ts b/packages/test-generated/spec/google.protobuf.field_mask.spec.ts index b6450f4f..cc5ef84b 100644 --- a/packages/test-generated/spec/google.protobuf.field_mask.spec.ts +++ b/packages/test-generated/spec/google.protobuf.field_mask.spec.ts @@ -2,8 +2,27 @@ import {FieldMask} from "../ts-out/google/protobuf/field_mask"; describe('google.protobuf.FieldMask', function () { - describe('toJson()', function () { + // Based on https://github.com/protocolbuffers/protobuf/blob/e8ae137c96444ea313485ed1118c5e43b2099cf1/src/google/protobuf/util/field_mask_util_test.cc#L69-L82 + it('converts snake_case to camelCase', function () { + let mask: FieldMask = { paths: [] }; + function snakeToCamel(s: string) { + mask.paths[0] = s; + return FieldMask.toJson(mask); + } + expect(snakeToCamel('foo_bar')).toBe('fooBar'); + expect(snakeToCamel('_foo_bar')).toBe('FooBar'); + expect(snakeToCamel('foo3_bar')).toBe('foo3Bar'); + // No uppercase letter is allowed. + expect(() => snakeToCamel('Foo')).toThrowError(); + // Any character after a "_" must be a lowercase letter. + // 1. "_" cannot be followed by another "_". + // 2. "_" cannot be followed by a digit. + // 3. "_" cannot appear as the last character. + expect(() => snakeToCamel('foo__bar')).toThrowError(); + expect(() => snakeToCamel('foo_3bar')).toThrowError(); + expect(() => snakeToCamel('foo_bar_')).toThrowError(); + }); it('returns expected JSON', function () { let mask: FieldMask = { paths: [ @@ -29,6 +48,17 @@ describe('google.protobuf.FieldMask', function () { }); describe('fromJson()', function () { + // Based on https://github.com/protocolbuffers/protobuf/blob/e8ae137c96444ea313485ed1118c5e43b2099cf1/src/google/protobuf/util/field_mask_util_test.cc#L84-L90 + it('converts camelCase to snake_case', function () { + function camelToSnake(s: string) { + return FieldMask.fromJson(s).paths[0]; + } + expect(camelToSnake('fooBar')).toBe('foo_bar'); + expect(camelToSnake('FooBar')).toBe('_foo_bar'); + expect(camelToSnake('foo3Bar')).toBe('foo3_bar'); + // "_"s are not allowed. + expect(() => camelToSnake('foo_bar')).toThrowError(); + }); it('parses JSON as expected', function () { let expected: FieldMask = { paths: [