Skip to content

Commit 4cdf7fb

Browse files
committed
parsing service definition to support auto-detect root messages for cleanup
Signed-off-by: xil <fridalu66@gmail.com>
1 parent 3e0e12d commit 4cdf7fb

9 files changed

Lines changed: 356 additions & 38 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
55
## [Unreleased]
66
### Added
77
- Add unit test workflow with coverage reporting ([#346](https://github.com/opensearch-project/opensearch-protobufs/pull/346))
8+
- parsing service definition to support auto-detect root messages for cleanup ([#347](https://github.com/opensearch-project/opensearch-protobufs/pull/347))
89

910
### Changed
1011

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
"scripts": {
88
"preprocessing": "ts-node tools/proto-convert/src/PreProcessing.ts",
99
"backward-compat": "ts-node tools/proto-convert/src/postprocessing/BackwardCompatibleWriter.ts",
10-
"cleanup-common": "ts-node tools/proto-convert/src/postprocessing/CleanupUnusedMessages.ts -i protos/schemas/common.proto",
11-
"postprocessing": "npm run backward-compat && npm run cleanup-common",
10+
"cleanup-unused": "ts-node tools/proto-convert/src/postprocessing/CleanupUnusedMessages.ts -i protos/schemas/common.proto",
11+
"postprocessing": "npm run backward-compat && npm run cleanup-unused",
1212
"test": "npx jest --no-watchman"
1313
},
1414
"dependencies": {

protos/generated/services/default_service.proto

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ option java_multiple_files = true;
1212
option java_outer_classname = "CommonProto";
1313
option java_package = "org.opensearch.protobufs";
1414

15+
service DefaultService {
16+
rpc Bulk (BulkRequest) returns (BulkResponse);
17+
18+
rpc Search (SearchRequest) returns (SearchResponse);
19+
20+
}
21+
1522
message BulkRequest {
1623
repeated BulkRequestBody bulk_request_body = 1;
1724

tools/proto-convert/src/config/protobuf-schema-template/api.mustache

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ import "{{{.}}}.proto";
66
{{/import}}
77
{{/imports}}
88

9+
service {{classname}} {
10+
{{#operations}}
11+
{{#operation}}
12+
{{#description}}
13+
// {{{.}}}
14+
{{/description}}
15+
rpc {{operationId}} ({{#hasParams}}{{operationId}}Request{{/hasParams}}{{^hasParams}}google.protobuf.Empty{{/hasParams}}) returns ({{#vendorExtensions.x-grpc-response}}{{.}}{{/vendorExtensions.x-grpc-response}}{{^vendorExtensions.x-grpc-response}}{{operationId}}Response{{/vendorExtensions.x-grpc-response}});
16+
17+
{{/operation}}
18+
{{/operations}}
19+
}
20+
921
{{#operations}}
1022
{{#operation}}
1123
{{#hasParams}}

tools/proto-convert/src/postprocessing/CleanupUnusedMessages.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,23 +114,42 @@ export function filterEnums(
114114
);
115115
}
116116

117+
/**
118+
* Extract root message names from service definitions (all request/response types).
119+
*/
120+
export function extractRootsFromServices(servicePath: string): string[] {
121+
const parsed = parseProtoFile(servicePath);
122+
const roots = new Set<string>();
123+
124+
for (const service of parsed.services) {
125+
for (const rpc of service.rpcs) {
126+
roots.add(rpc.requestType);
127+
roots.add(rpc.responseType);
128+
}
129+
}
130+
131+
return Array.from(roots);
132+
}
133+
117134
// ==================== CLI ====================
118135

119136
if (require.main === module) {
120137
const command = new Command()
121138
.description('Remove unused messages and enums from a proto file.')
122-
.addOption(new Option('-i, --input <path>', 'input proto file').default('protos/schemas/common.proto'))
139+
.addOption(new Option('-i, --input <path>', 'input proto file').default('protos/generated/models/aggregated_models.proto'))
123140
.addOption(new Option('-o, --output <path>', 'output proto file (defaults to input)'))
124-
.addOption(new Option('-r, --roots <names>', 'root message names (comma-separated)')
125-
.argParser((val: string) => val.split(',').map(s => s.trim()))
126-
.default(['SearchRequest', 'SearchResponse', 'BulkRequest', 'BulkResponse']))
141+
.addOption(new Option('-s, --service <path>', 'service proto file to auto-detect roots')
142+
.default('protos/generated/services/default_service.proto'))
143+
.addOption(new Option('-r, --roots <names>', 'root message names (comma-separated, overrides --service)')
144+
.argParser((val: string) => val.split(',').map(s => s.trim())))
127145
.allowExcessArguments(false)
128146
.parse();
129147

130148
type CleanupOpts = {
131149
input: string;
132150
output?: string;
133-
roots: string[];
151+
service: string;
152+
roots?: string[];
134153
};
135154

136155
const opts = command.opts() as CleanupOpts;
@@ -140,19 +159,32 @@ if (require.main === module) {
140159
process.exit(1);
141160
}
142161

162+
// Get roots: from --roots if provided, otherwise from service file
163+
let roots: string[];
164+
if (opts.roots && opts.roots.length > 0) {
165+
roots = opts.roots;
166+
logger.info(`Using manually specified roots: ${roots.join(', ')}`);
167+
} else if (existsSync(opts.service)) {
168+
roots = extractRootsFromServices(opts.service);
169+
logger.info(`Auto-detected roots from ${opts.service}: ${roots.join(', ')}`);
170+
} else {
171+
logger.error(`Service file not found: ${opts.service}. Specify --roots manually.`);
172+
process.exit(1);
173+
}
174+
143175
const parsed = parseProtoFile(opts.input);
144176

145177
// Verify root messages exist
146178
const messageNames = new Set(parsed.messages.map(m => m.name));
147-
for (const rootMsg of opts.roots) {
179+
for (const rootMsg of roots) {
148180
if (!messageNames.has(rootMsg)) {
149181
logger.error(`Root message not found: ${rootMsg}`);
150182
process.exit(1);
151183
}
152184
}
153185

154186
// Find reachable types
155-
const reachable = findReachableTypes(opts.roots, parsed.messages);
187+
const reachable = findReachableTypes(roots, parsed.messages);
156188

157189
// Filter to keep only reachable
158190
const keptMessages = filterMessages(parsed.messages, reachable);

tools/proto-convert/src/postprocessing/parser.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
*/
44

55
import { readFileSync } from 'fs';
6-
import { parse, Field, Enum, Type, Namespace, NamespaceBase } from 'protobufjs';
6+
import { parse, Field, Enum, Type, Namespace, NamespaceBase, Service, Method } from 'protobufjs';
77
import {
88
ProtoField,
99
ProtoMessage,
1010
ProtoEnum,
1111
ProtoEnumValue,
1212
ProtoOneof,
13+
ProtoService,
14+
ProtoRpc,
1315
ParsedProtoFile,
1416
Annotation
1517
} from './types';
@@ -157,6 +159,28 @@ export function convertMessage(msgDef: Type): ProtoMessage {
157159
};
158160
}
159161

162+
/**
163+
* Convert a protobufjs Service to internal ProtoService type.
164+
*/
165+
export function convertService(serviceDef: Service): ProtoService {
166+
const rpcs: ProtoRpc[] = [];
167+
168+
for (const method of serviceDef.methodsArray) {
169+
rpcs.push({
170+
name: method.name,
171+
requestType: method.requestType,
172+
responseType: method.responseType,
173+
comment: method.comment || undefined
174+
});
175+
}
176+
177+
return {
178+
name: serviceDef.name,
179+
comment: serviceDef.comment || undefined,
180+
rpcs
181+
};
182+
}
183+
160184
/**
161185
* Parse a .proto file and return internal types.
162186
*/
@@ -166,6 +190,7 @@ export function parseProtoFile(filePath: string): ParsedProtoFile {
166190

167191
const messages: ProtoMessage[] = [];
168192
const enums: ProtoEnum[] = [];
193+
const services: ProtoService[] = [];
169194

170195
function traverse(ns: NamespaceBase) {
171196
if (ns.nestedArray) {
@@ -174,6 +199,8 @@ export function parseProtoFile(filePath: string): ParsedProtoFile {
174199
messages.push(convertMessage(nested));
175200
} else if (nested instanceof Enum) {
176201
enums.push(convertEnum(nested));
202+
} else if (nested instanceof Service) {
203+
services.push(convertService(nested));
177204
} else if (nested instanceof Namespace) {
178205
traverse(nested);
179206
}
@@ -183,5 +210,5 @@ export function parseProtoFile(filePath: string): ParsedProtoFile {
183210

184211
traverse(parsed.root);
185212

186-
return { messages, enums };
213+
return { messages, enums, services };
187214
}

tools/proto-convert/src/postprocessing/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,23 @@ export interface ProtoMessage {
4242
oneofs?: ProtoOneof[];
4343
}
4444

45+
export interface ProtoRpc {
46+
name: string;
47+
requestType: string;
48+
responseType: string;
49+
comment?: string;
50+
}
51+
52+
export interface ProtoService {
53+
name: string;
54+
comment?: string;
55+
rpcs: ProtoRpc[];
56+
}
57+
4558
export interface ParsedProtoFile {
4659
messages: ProtoMessage[];
4760
enums: ProtoEnum[];
61+
services: ProtoService[];
4862
}
4963

5064
export class BackwardCompatibilityError extends Error {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
syntax = "proto3";
2+
3+
package test;
4+
5+
// Test service for extractRootsFromServices
6+
service TestService {
7+
rpc Search (SearchRequest) returns (SearchResponse);
8+
rpc GetDocument (GetDocumentRequest) returns (GetDocumentResponse);
9+
}
10+
11+
message SearchRequest {
12+
string query = 1;
13+
}
14+
15+
message SearchResponse {
16+
repeated string results = 1;
17+
}
18+
19+
message GetDocumentRequest {
20+
string id = 1;
21+
}
22+
23+
message GetDocumentResponse {
24+
string content = 1;
25+
}

0 commit comments

Comments
 (0)