diff --git a/protographic/README.md b/protographic/README.md index ec6a507742..589a5a742c 100644 --- a/protographic/README.md +++ b/protographic/README.md @@ -18,6 +18,7 @@ Protographic bridges GraphQL and Protocol Buffers (protobuf) ecosystems through - Robust handling of complex GraphQL features (unions, interfaces, directives) - First-class support for Federation entity mapping - Deterministic field ordering with proto.lock.json for backward compatibility +- Use of Protocol Buffer wrappers for nullable fields to distinguish between semantic nulls and zero values ## Installation diff --git a/protographic/SDL_PROTO_RULES.md b/protographic/SDL_PROTO_RULES.md index c894493317..b7c1da192c 100644 --- a/protographic/SDL_PROTO_RULES.md +++ b/protographic/SDL_PROTO_RULES.md @@ -53,13 +53,13 @@ Rules should follow [Proto Best Practices](https://protobuf.dev/best-practices/d ### Scalar Types -| GraphQL Type | Protocol Buffer Type | -| ------------ | -------------------- | -| ID | string | -| String | string | -| Int | int32 | -| Float | double | -| Boolean | bool | +| GraphQL Type | Protocol Buffer Type (Non-Null) | Protocol Buffer Type (Nullable) | +| ------------ | ------------------------------- | ------------------------------- | +| ID | string | google.protobuf.StringValue | +| String | string | google.protobuf.StringValue | +| Int | int32 | google.protobuf.Int32Value | +| Float | double | google.protobuf.DoubleValue | +| Boolean | bool | google.protobuf.BoolValue | ### Complex Types @@ -286,13 +286,15 @@ type User { Generates: ```protobuf +import "google/protobuf/wrappers.proto"; + message User { string id = 1; string name = 2; string email = 3; - int32 age = 4; - string bio = 5; - bool is_active = 6; + google.protobuf.Int32Value age = 4; + google.protobuf.StringValue bio = 5; + google.protobuf.BoolValue is_active = 6; } ``` @@ -312,11 +314,13 @@ type User { Generates (with range notation for reserved fields): ```protobuf +import "google/protobuf/wrappers.proto"; + message User { reserved 3 to 5; // Efficiently reserves fields 3, 4, and 5 string id = 1; string name = 2; - bool is_active = 6; + google.protobuf.BoolValue is_active = 6; } ``` @@ -335,13 +339,15 @@ type User { Generates: ```protobuf +import "google/protobuf/wrappers.proto"; + message User { reserved 3 to 4; // Fields 3 and 4 remain reserved string id = 1; string name = 2; - string bio = 5; // Restored field keeps its original number - bool is_active = 6; - string created_at = 7; // New field gets next available number + google.protobuf.StringValue bio = 5; // Restored field keeps its original number + google.protobuf.BoolValue is_active = 6; + google.protobuf.StringValue created_at = 7; // New field gets next available number } ``` diff --git a/protographic/src/sdl-to-proto-visitor.ts b/protographic/src/sdl-to-proto-visitor.ts index 2ab6858ffa..e3ecf12b20 100644 --- a/protographic/src/sdl-to-proto-visitor.ts +++ b/protographic/src/sdl-to-proto-visitor.ts @@ -49,6 +49,20 @@ const SCALAR_TYPE_MAP: Record = { Boolean: 'bool', // Direct mapping }; +/** + * Maps GraphQL scalar types to Protocol Buffer wrapper types for nullable fields + * + * These wrapper types allow distinguishing between unset fields and zero values + * in Protocol Buffers, which is important for GraphQL nullable semantics. + */ +const SCALAR_WRAPPER_TYPE_MAP: Record = { + ID: 'google.protobuf.StringValue', + String: 'google.protobuf.StringValue', + Int: 'google.protobuf.Int32Value', + Float: 'google.protobuf.DoubleValue', + Boolean: 'google.protobuf.BoolValue', +}; + /** * Generic structure for returning RPC and message definitions */ @@ -116,6 +130,9 @@ export class GraphQLToProtoTextVisitor { /** Track generated nested list wrapper messages */ private nestedListWrappers = new Map(); + /** Track whether wrapper types are used (for conditional import) */ + private usesWrapperTypes = false; + /** * Map of message names to their field numbers for tracking deleted fields * This maintains field numbers even when fields are removed from the schema @@ -424,6 +441,26 @@ export class GraphQLToProtoTextVisitor { // Third: Process all complex types from the message queue in a single pass this.processMessageQueue(); + // Add wrapper import if needed, at the correct position + if (this.usesWrapperTypes) { + // Find the position after the package declaration + const packageIndex = this.protoText.findIndex((line) => line.startsWith('package ')); + if (packageIndex !== -1) { + // Insert after package line and any existing options, but before service + let insertIndex = packageIndex + 1; + + // Skip over any existing options and empty lines + while ( + insertIndex < this.protoText.length && + (this.protoText[insertIndex].startsWith('option ') || this.protoText[insertIndex].trim() === '') + ) { + insertIndex++; + } + + this.protoText.splice(insertIndex, 0, 'import "google/protobuf/wrappers.proto";', ''); + } + } + // Store the generated lock data for retrieval this.generatedLockData = this.lockManager.getLockData(); @@ -1322,14 +1359,21 @@ Example: * Map GraphQL type to Protocol Buffer type * * Determines the appropriate Protocol Buffer type for a given GraphQL type, - * handling all GraphQL type wrappers (NonNull, List) correctly. + * including the use of wrapper types for nullable scalar fields to distinguish + * between unset fields and zero values. * * @param graphqlType - The GraphQL type to convert + * @param ignoreWrapperTypes - If true, do not use wrapper types for nullable scalar fields * @returns The corresponding Protocol Buffer type name */ - private getProtoTypeFromGraphQL(graphqlType: GraphQLType): string { + private getProtoTypeFromGraphQL(graphqlType: GraphQLType, ignoreWrapperTypes: boolean = false): string { + // For nullable scalar types, use wrapper types if (isScalarType(graphqlType)) { - return SCALAR_TYPE_MAP[graphqlType.name] || 'string'; + if (ignoreWrapperTypes) { + return SCALAR_TYPE_MAP[graphqlType.name] || 'string'; + } + this.usesWrapperTypes = true; // Track that we're using wrapper types + return SCALAR_WRAPPER_TYPE_MAP[graphqlType.name] || 'google.protobuf.StringValue'; } if (isEnumType(graphqlType)) { @@ -1337,6 +1381,11 @@ Example: } if (isNonNullType(graphqlType)) { + // For non-null scalar types, use the base type + if (isScalarType(graphqlType.ofType)) { + return SCALAR_TYPE_MAP[graphqlType.ofType.name] || 'string'; + } + return this.getProtoTypeFromGraphQL(graphqlType.ofType); } @@ -1364,7 +1413,7 @@ Example: return wrapperName; } - return this.getProtoTypeFromGraphQL(innerType); + return this.getProtoTypeFromGraphQL(innerType, true); } // Named types (object, interface, union, input) @@ -1417,7 +1466,7 @@ Example: const fieldNumber = this.getFieldNumber(wrapperName, 'result', 1); // For the inner type, we need to get the proto type for the base type - const protoType = this.getProtoTypeFromGraphQL(baseType); + const protoType = this.getProtoTypeFromGraphQL(baseType, true); messageLines.push(` repeated ${protoType} result = ${fieldNumber};`); messageLines.push('}'); diff --git a/protographic/tests/sdl-to-proto/01-basic-types.test.ts b/protographic/tests/sdl-to-proto/01-basic-types.test.ts index 923678d7e1..f0a32b5900 100644 --- a/protographic/tests/sdl-to-proto/01-basic-types.test.ts +++ b/protographic/tests/sdl-to-proto/01-basic-types.test.ts @@ -24,6 +24,8 @@ describe('SDL to Proto - Basic Types', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc QueryBooleanField(QueryBooleanFieldRequest) returns (QueryBooleanFieldResponse) {} @@ -38,35 +40,35 @@ describe('SDL to Proto - Basic Types', () => { } // Response message for stringField operation. message QueryStringFieldResponse { - string string_field = 1; + google.protobuf.StringValue string_field = 1; } // Request message for intField operation. message QueryIntFieldRequest { } // Response message for intField operation. message QueryIntFieldResponse { - int32 int_field = 1; + google.protobuf.Int32Value int_field = 1; } // Request message for floatField operation. message QueryFloatFieldRequest { } // Response message for floatField operation. message QueryFloatFieldResponse { - double float_field = 1; + google.protobuf.DoubleValue float_field = 1; } // Request message for booleanField operation. message QueryBooleanFieldRequest { } // Response message for booleanField operation. message QueryBooleanFieldResponse { - bool boolean_field = 1; + google.protobuf.BoolValue boolean_field = 1; } // Request message for idField operation. message QueryIdFieldRequest { } // Response message for idField operation. message QueryIdFieldResponse { - string id_field = 1; + google.protobuf.StringValue id_field = 1; }" `); }); @@ -185,6 +187,8 @@ describe('SDL to Proto - Basic Types', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc QueryUser(QueryUserRequest) returns (QueryUserResponse) {} @@ -201,7 +205,7 @@ describe('SDL to Proto - Basic Types', () => { message User { string id = 1; string name = 2; - int32 age = 3; + google.protobuf.Int32Value age = 3; }" `); }); @@ -228,6 +232,8 @@ describe('SDL to Proto - Basic Types', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc QueryFilteredUsers(QueryFilteredUsersRequest) returns (QueryFilteredUsersResponse) {} @@ -245,8 +251,8 @@ describe('SDL to Proto - Basic Types', () => { // Request message for filteredUsers operation. message QueryFilteredUsersRequest { int32 limit = 1; - int32 offset = 2; - string name_filter = 3; + google.protobuf.Int32Value offset = 2; + google.protobuf.StringValue name_filter = 3; } // Response message for filteredUsers operation. message QueryFilteredUsersResponse { @@ -284,6 +290,8 @@ describe('SDL to Proto - Basic Types', () => { option go_package = "github.com/example/mypackage;mypackage"; + import "google/protobuf/wrappers.proto"; + // Service definition for CustomService service CustomService { rpc QueryHello(QueryHelloRequest) returns (QueryHelloResponse) {} @@ -294,7 +302,7 @@ describe('SDL to Proto - Basic Types', () => { } // Response message for hello operation. message QueryHelloResponse { - string hello = 1; + google.protobuf.StringValue hello = 1; }" `); }); @@ -320,6 +328,8 @@ describe('SDL to Proto - Basic Types', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc MutationField2(MutationField2Request) returns (MutationField2Response) {} @@ -331,15 +341,15 @@ describe('SDL to Proto - Basic Types', () => { } // Response message for field1 operation. message QueryField1Response { - string field_1 = 1; + google.protobuf.StringValue field_1 = 1; } // Request message for field2 operation. message MutationField2Request { - string input = 1; + google.protobuf.StringValue input = 1; } // Response message for field2 operation. message MutationField2Response { - int32 field_2 = 1; + google.protobuf.Int32Value field_2 = 1; }" `); }); @@ -506,4 +516,74 @@ describe('SDL to Proto - Basic Types', () => { }" `); }); + + test('should convert nullable scalar types to wrapper types', () => { + const sdl = ` + type Query { + nullableString: String + nullableInt: Int + nullableFloat: Float + nullableBoolean: Boolean + nullableId: ID + } + `; + + const { proto: protoText } = compileGraphQLToProto(sdl); + + // Validate Proto definition + expectValidProto(protoText); + + // Full snapshot to ensure overall structure is correct + expect(protoText).toMatchInlineSnapshot(` + "syntax = "proto3"; + package service.v1; + + import "google/protobuf/wrappers.proto"; + + // Service definition for DefaultService + service DefaultService { + rpc QueryNullableBoolean(QueryNullableBooleanRequest) returns (QueryNullableBooleanResponse) {} + rpc QueryNullableFloat(QueryNullableFloatRequest) returns (QueryNullableFloatResponse) {} + rpc QueryNullableId(QueryNullableIdRequest) returns (QueryNullableIdResponse) {} + rpc QueryNullableInt(QueryNullableIntRequest) returns (QueryNullableIntResponse) {} + rpc QueryNullableString(QueryNullableStringRequest) returns (QueryNullableStringResponse) {} + } + + // Request message for nullableString operation. + message QueryNullableStringRequest { + } + // Response message for nullableString operation. + message QueryNullableStringResponse { + google.protobuf.StringValue nullable_string = 1; + } + // Request message for nullableInt operation. + message QueryNullableIntRequest { + } + // Response message for nullableInt operation. + message QueryNullableIntResponse { + google.protobuf.Int32Value nullable_int = 1; + } + // Request message for nullableFloat operation. + message QueryNullableFloatRequest { + } + // Response message for nullableFloat operation. + message QueryNullableFloatResponse { + google.protobuf.DoubleValue nullable_float = 1; + } + // Request message for nullableBoolean operation. + message QueryNullableBooleanRequest { + } + // Response message for nullableBoolean operation. + message QueryNullableBooleanResponse { + google.protobuf.BoolValue nullable_boolean = 1; + } + // Request message for nullableId operation. + message QueryNullableIdRequest { + } + // Response message for nullableId operation. + message QueryNullableIdResponse { + google.protobuf.StringValue nullable_id = 1; + }" + `); + }); }); diff --git a/protographic/tests/sdl-to-proto/02-complex-types.test.ts b/protographic/tests/sdl-to-proto/02-complex-types.test.ts index b4c6f2479b..58a80ccaa0 100644 --- a/protographic/tests/sdl-to-proto/02-complex-types.test.ts +++ b/protographic/tests/sdl-to-proto/02-complex-types.test.ts @@ -93,6 +93,8 @@ describe('SDL to Proto - Complex Types', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc MutationCreateUser(MutationCreateUserRequest) returns (MutationCreateUserResponse) {} @@ -104,7 +106,7 @@ describe('SDL to Proto - Complex Types', () => { } // Response message for dummy operation. message QueryDummyResponse { - string dummy = 1; + google.protobuf.StringValue dummy = 1; } // Request message for createUser operation. message MutationCreateUserRequest { @@ -118,14 +120,14 @@ describe('SDL to Proto - Complex Types', () => { message UserInput { string name = 1; string email = 2; - int32 age = 3; + google.protobuf.Int32Value age = 3; } message User { string id = 1; string name = 2; string email = 3; - int32 age = 4; + google.protobuf.Int32Value age = 4; }" `); }); @@ -284,6 +286,8 @@ describe('SDL to Proto - Complex Types', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc QueryUsers(QueryUsersRequest) returns (QueryUsersResponse) {} @@ -299,9 +303,9 @@ describe('SDL to Proto - Complex Types', () => { } message UserFilterInput { - string name_contains = 1; - int32 min_age = 2; - int32 max_age = 3; + google.protobuf.StringValue name_contains = 1; + google.protobuf.Int32Value min_age = 2; + google.protobuf.Int32Value max_age = 3; repeated AddressInput addresses = 4; } @@ -315,7 +319,7 @@ describe('SDL to Proto - Complex Types', () => { string street = 1; string city = 2; string country = 3; - string zip_code = 4; + google.protobuf.StringValue zip_code = 4; }" `); }); diff --git a/protographic/tests/sdl-to-proto/04-federation.test.ts b/protographic/tests/sdl-to-proto/04-federation.test.ts index 53efd1f43b..4f2170f719 100644 --- a/protographic/tests/sdl-to-proto/04-federation.test.ts +++ b/protographic/tests/sdl-to-proto/04-federation.test.ts @@ -399,6 +399,8 @@ describe('SDL to Proto - Federation and Special Types', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc QueryEvent(QueryEventRequest) returns (QueryEventResponse) {} @@ -425,9 +427,9 @@ describe('SDL to Proto - Federation and Special Types', () => { string id = 1; string name = 2; string start_time = 3; - string end_time = 4; - string metadata = 5; - string attachment = 6; + google.protobuf.StringValue end_time = 4; + google.protobuf.StringValue metadata = 5; + google.protobuf.StringValue attachment = 6; }" `); }); diff --git a/protographic/tests/sdl-to-proto/05-edge-cases.test.ts b/protographic/tests/sdl-to-proto/05-edge-cases.test.ts index 8e561389e5..de21cd0c2d 100644 --- a/protographic/tests/sdl-to-proto/05-edge-cases.test.ts +++ b/protographic/tests/sdl-to-proto/05-edge-cases.test.ts @@ -3,38 +3,6 @@ import { compileGraphQLToProto } from '../../src'; import { expectValidProto } from '../util'; describe('SDL to Proto - Edge Cases and Error Handling', () => { - test('should handle empty schema correctly', () => { - const sdl = ` - type Query { - dummy: String - } - `; - - const { proto: protoText } = compileGraphQLToProto(sdl); - - // Validate Proto definition - expectValidProto(protoText); - - // Check that all required components are present - expect(protoText).toMatchInlineSnapshot(` - "syntax = "proto3"; - package service.v1; - - // Service definition for DefaultService - service DefaultService { - rpc QueryDummy(QueryDummyRequest) returns (QueryDummyResponse) {} - } - - // Request message for dummy operation. - message QueryDummyRequest { - } - // Response message for dummy operation. - message QueryDummyResponse { - string dummy = 1; - }" - `); - }); - test('should handle schema with only scalar fields correctly', () => { const sdl = ` type Query { @@ -56,6 +24,8 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc QueryBoolean(QueryBooleanRequest) returns (QueryBooleanResponse) {} @@ -70,35 +40,35 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { } // Response message for string operation. message QueryStringResponse { - string string = 1; + google.protobuf.StringValue string = 1; } // Request message for int operation. message QueryIntRequest { } // Response message for int operation. message QueryIntResponse { - int32 int = 1; + google.protobuf.Int32Value int = 1; } // Request message for float operation. message QueryFloatRequest { } // Response message for float operation. message QueryFloatResponse { - double float = 1; + google.protobuf.DoubleValue float = 1; } // Request message for boolean operation. message QueryBooleanRequest { } // Response message for boolean operation. message QueryBooleanResponse { - bool boolean = 1; + google.protobuf.BoolValue boolean = 1; } // Request message for id operation. message QueryIdRequest { } // Response message for id operation. message QueryIdResponse { - string id = 1; + google.protobuf.StringValue id = 1; }" `); }); @@ -137,6 +107,8 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc QueryEnumValue(QueryEnumValueRequest) returns (QueryEnumValueResponse) {} @@ -166,7 +138,7 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { } // Response message for enumValue operation. message QueryEnumValueResponse { - string enum_value = 1; + google.protobuf.StringValue enum_value = 1; } message MessageType { @@ -216,6 +188,8 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc QueryUser(QueryUserRequest) returns (QueryUserResponse) {} @@ -232,14 +206,14 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { message User { string id = 1; - string message = 2; - string service = 3; - string enum = 4; - string syntax = 5; - string package = 6; - string option = 7; - string import = 8; - string reserved = 9; + google.protobuf.StringValue message = 2; + google.protobuf.StringValue service = 3; + google.protobuf.StringValue enum = 4; + google.protobuf.StringValue syntax = 5; + google.protobuf.StringValue package = 6; + google.protobuf.StringValue option = 7; + google.protobuf.StringValue import = 8; + google.protobuf.StringValue reserved = 9; }" `); }); @@ -367,6 +341,8 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { // Lookup Post entity by id @@ -466,8 +442,8 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { } // Request message for users operation. message QueryUsersRequest { - int32 limit = 1; - int32 offset = 2; + google.protobuf.Int32Value limit = 1; + google.protobuf.Int32Value offset = 2; } // Response message for users operation. message QueryUsersResponse { @@ -483,8 +459,8 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { } // Request message for posts operation. message QueryPostsRequest { - int32 limit = 1; - int32 offset = 2; + google.protobuf.Int32Value limit = 1; + google.protobuf.Int32Value offset = 2; PostStatus status = 3; } // Response message for posts operation. @@ -502,8 +478,8 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { // Request message for comments operation. message QueryCommentsRequest { string post_id = 1; - int32 limit = 2; - int32 offset = 3; + google.protobuf.Int32Value limit = 2; + google.protobuf.Int32Value offset = 3; } // Response message for comments operation. message QueryCommentsResponse { @@ -574,7 +550,7 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { string name = 2; string email = 3; string created_at = 4; - string metadata = 5; + google.protobuf.StringValue metadata = 5; UserStatus status = 6; repeated Post posts = 7; UserProfile profile = 8; @@ -587,7 +563,7 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { User author = 4; repeated string tags = 5; string created_at = 6; - string updated_at = 7; + google.protobuf.StringValue updated_at = 7; PostStatus status = 8; repeated Comment comments = 9; } @@ -598,13 +574,13 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { User author = 3; string content = 4; string created_at = 5; - string updated_at = 6; + google.protobuf.StringValue updated_at = 6; } message SearchInput { string query = 1; - int32 limit = 2; - int32 offset = 3; + google.protobuf.Int32Value limit = 2; + google.protobuf.Int32Value offset = 3; repeated string types = 4; } @@ -649,10 +625,10 @@ describe('SDL to Proto - Edge Cases and Error Handling', () => { } message UserProfile { - string bio = 1; - string avatar_url = 2; - string location = 3; - string website = 4; + google.protobuf.StringValue bio = 1; + google.protobuf.StringValue avatar_url = 2; + google.protobuf.StringValue location = 3; + google.protobuf.StringValue website = 4; } enum PostStatus { diff --git a/protographic/tests/sdl-to-proto/09-comments.test.ts b/protographic/tests/sdl-to-proto/09-comments.test.ts index 8b9359fd87..b6323d1e28 100644 --- a/protographic/tests/sdl-to-proto/09-comments.test.ts +++ b/protographic/tests/sdl-to-proto/09-comments.test.ts @@ -82,6 +82,8 @@ describe('SDL to Proto Comments', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { // Get a user by ID @@ -112,9 +114,9 @@ describe('SDL to Proto Comments', () => { * Number of items to skip. * Use for pagination. */ - int32 offset = 1; + google.protobuf.Int32Value offset = 1; // Maximum number of items to return - int32 limit = 2; + google.protobuf.Int32Value limit = 2; } /* * Response message for users operation: List all users with pagination. @@ -139,8 +141,8 @@ describe('SDL to Proto Comments', () => { * Multi-line description for the name field. * Second line of the description. */ - string name = 2; - int32 age = 3; + google.protobuf.StringValue name = 2; + google.protobuf.Int32Value age = 3; } // Single line description for the Role enum @@ -231,6 +233,8 @@ describe('SDL to Proto Comments', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { // Get node by ID @@ -307,7 +311,7 @@ describe('SDL to Proto Comments', () => { // Organization name string name = 2; // Organization description - string description = 3; + google.protobuf.StringValue description = 3; }" `); }); @@ -372,6 +376,8 @@ describe('SDL to Proto Comments', () => { "syntax = "proto3"; package service.v1; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { /* @@ -502,7 +508,7 @@ describe('SDL to Proto Comments', () => { // Product price in cents int32 price = 3; // Optional product description - string description = 4; + google.protobuf.StringValue description = 4; }" `); }); diff --git a/protographic/tests/sdl-to-proto/10-options.test.ts b/protographic/tests/sdl-to-proto/10-options.test.ts index c0c4eb51de..1cfd23d5f1 100644 --- a/protographic/tests/sdl-to-proto/10-options.test.ts +++ b/protographic/tests/sdl-to-proto/10-options.test.ts @@ -26,6 +26,8 @@ describe('SDL to Proto Options', () => { option go_package = "github.com/wundergraph/cosmo/protographic"; + import "google/protobuf/wrappers.proto"; + // Service definition for DefaultService service DefaultService { rpc QueryBooleanField(QueryBooleanFieldRequest) returns (QueryBooleanFieldResponse) {} @@ -40,35 +42,35 @@ describe('SDL to Proto Options', () => { } // Response message for stringField operation. message QueryStringFieldResponse { - string string_field = 1; + google.protobuf.StringValue string_field = 1; } // Request message for intField operation. message QueryIntFieldRequest { } // Response message for intField operation. message QueryIntFieldResponse { - int32 int_field = 1; + google.protobuf.Int32Value int_field = 1; } // Request message for floatField operation. message QueryFloatFieldRequest { } // Response message for floatField operation. message QueryFloatFieldResponse { - double float_field = 1; + google.protobuf.DoubleValue float_field = 1; } // Request message for booleanField operation. message QueryBooleanFieldRequest { } // Response message for booleanField operation. message QueryBooleanFieldResponse { - bool boolean_field = 1; + google.protobuf.BoolValue boolean_field = 1; } // Request message for idField operation. message QueryIdFieldRequest { } // Response message for idField operation. message QueryIdFieldResponse { - string id_field = 1; + google.protobuf.StringValue id_field = 1; }" `); }); diff --git a/protographic/tests/util.ts b/protographic/tests/util.ts index 3bad0f64d7..43266fc7dc 100644 --- a/protographic/tests/util.ts +++ b/protographic/tests/util.ts @@ -8,11 +8,17 @@ import { expect } from 'vitest'; * @throws Error if the protocol buffer definition is invalid */ export function validateProtoDefinition(protoText: string): void { + // Create a root instance + const root = new protobufjs.Root(); + + // Load the common wrappers into the root + root.loadSync('google/protobuf/wrappers.proto'); + // Use protobufjs to parse the text without writing to a file - const root = protobufjs.parse(protoText).root; + const parsedRoot = protobufjs.parse(protoText, root).root; // Verify the root is loaded by forcing resolution - root.resolveAll(); + parsedRoot.resolveAll(); } /**