Skip to content

Commit 471bc9c

Browse files
authored
Preprocessing - Add schema exclusion list to filter out schemas and their dependencies (#328)
Signed-off-by: xil <fridalu66@gmail.com>
1 parent 09e79bb commit 471bc9c

7 files changed

Lines changed: 107 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
88
- Preprocessing - Handling spec added/deprecated versioning.([#309](https://github.com/opensearch-project/opensearch-protobufs/pull/309))
99
- preprocessing - Support maxProperties=1 constraints by marking them as `oneof` for protobuf generation ([#317](https://github.com/opensearch-project/opensearch-protobufs/pull/317))
1010
- Preprocessing - Support x-protobuf-required to enforce required protobuf field and convert oneOf properties pattern to min/max Properties = 1 ([#318](https://github.com/opensearch-project/opensearch-protobufs/pull/318))
11+
- Preprocessing - Add schema exclusion list to filter out schemas and their dependencies ([#328](https://github.com/opensearch-project/opensearch-protobufs/pull/328))
1112

1213
### Changed
1314
- Backward incompatible change for unimplemented query types `ScriptScoreQuery`, `SimpleQueryStringQuery`, `DisMaxQuery`, `IntervalsQuery`, `QueryStringQuery` and `TermsAggregation` ([#324](https://github.com/opensearch-project/opensearch-protobufs/pull/324))

DEVELOPER_GUIDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ dependencies {
7575
```
7676

7777
## Python
78-
### Generate and install Python Code
78+
### Generate and install Python Code
7979

8080
Generate the wheel file with bazel and install the packag with pip:
8181
```
@@ -93,7 +93,7 @@ pip install bazel-bin/opensearch_protos-*-py3-none-any.whl
9393
The [Spec Preprocessing](tools/proto-convert/src/PreProcessing.ts) includes two steps:
9494

9595
1. **Filter**
96-
- Filters only the target APIs defined in [target_api.yaml](tools/src/config/target_api.yaml).
96+
- Filters only the target APIs defined in [spec-filter.yaml](tools/proto-convert/src/config/spec-filter.yaml).
9797
- Extract a single API per group from the OpenSearch spec.
9898

9999
2. **Sanitizer**

tools/proto-convert/src/Filter.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { type OpenAPIV3 } from 'openapi-types'
22
import _ from "lodash";
33
import Logger from "./utils/logger"
4+
import { getSchemaNames } from "./utils/helper"
5+
46
/**
57
* Recursively traverses a node and for every $ref that starts with "#/components/",
6-
* enqueues the reference string if it hasn’t been visited yet.
8+
* enqueues the reference string if it hasn't been visited yet.
9+
* Skips schemas in the exclusion list.
710
*/
8-
function traverse_and_enqueue(node: any, queue: string[], visited: Set<string>): void {
11+
function traverse_and_enqueue(node: any, queue: string[], visited: Set<string>, excluded: Set<string>): void {
912
for (const key in node) {
1013
var item = node[key]
1114

@@ -14,22 +17,40 @@ function traverse_and_enqueue(node: any, queue: string[], visited: Set<string>)
1417
if (ref == null || ref == "" && _.isString(item)){
1518
ref = item as string;
1619
}
20+
21+
// Check exclusion list - if schema is excluded, don't push ref
22+
const names = getSchemaNames(ref);
23+
if (names && (excluded.has(names.full) || excluded.has(names.short))) {
24+
continue;
25+
}
26+
1727
queue.push(ref);
1828
visited.add(ref);
1929
}
2030
if (_.isObject(item) || _.isArray(item) || (_.isString(item) && item.startsWith('#/components/'))) {
21-
traverse_and_enqueue(item, queue, visited)
31+
traverse_and_enqueue(item, queue, visited, excluded)
2232
}
2333
}
2434
}
2535

26-
//Filter an OpenAPI spec so that only the specified path and all its referenced components (via $ref) are included.
36+
/**
37+
* Filters an OpenAPI spec to include only specified paths and their referenced components.
38+
* Schemas in the excluded set are skipped.
39+
*/
2740
export default class Filter {
2841
logger: Logger
2942
protected _spec: Record<string, any>
43+
protected targetPaths: string[]
44+
protected excludedSchemas: Set<string>
3045
paths: Record<string, Record<string, OpenAPIV3.PathItemObject>> = {} // namespace -> path -> path_item_object
31-
constructor(logger: Logger = new Logger()) {
46+
47+
constructor(logger: Logger, targetPaths: string[], excludedSchemas: Set<string> = new Set()) {
3248
this.logger = logger
49+
this.targetPaths = targetPaths;
50+
this.excludedSchemas = excludedSchemas;
51+
if (this.excludedSchemas.size > 0) {
52+
this.logger.info(`Loaded ${this.excludedSchemas.size} excluded schemas: ${Array.from(this.excludedSchemas).join(', ')}`);
53+
}
3354
this._spec = {
3455
openapi: '3.1.0',
3556
info: {},
@@ -44,9 +65,9 @@ export default class Filter {
4465
}
4566

4667

47-
filter_spec(spec: Record<string, any>, paths_to_keep: string[]): any {
68+
filter_spec(spec: Record<string, any>): any {
4869
this._spec.info = spec.info;
49-
for (const p of paths_to_keep) {
70+
for (const p of this.targetPaths) {
5071
if (spec.paths[p] === undefined) {
5172
this.logger.error(`Path not found in spec: ${p}`);
5273
continue;
@@ -58,7 +79,7 @@ export default class Filter {
5879
const visited: Set<string> = new Set();
5980

6081
// collect all components that are referenced by the paths
61-
traverse_and_enqueue(this._spec.paths , queue, visited);
82+
traverse_and_enqueue(this._spec.paths, queue, visited, this.excludedSchemas);
6283
while (queue.length > 0) {
6384
const ref_str = queue.shift();
6485
if (ref_str == null || ref_str == "") continue;
@@ -73,7 +94,7 @@ export default class Filter {
7394
if (this._spec.components[sub_component][key] == null) {
7495
if (spec.components != null && spec.components[sub_component] != null && spec.components[sub_component][key] != null) {
7596
this._spec.components[sub_component][key] = spec.components[sub_component][key];
76-
traverse_and_enqueue(this._spec.components[sub_component][key], queue, visited);
97+
traverse_and_enqueue(this._spec.components[sub_component][key], queue, visited, this.excludedSchemas);
7798
}
7899
}
79100
}
@@ -141,4 +162,4 @@ export default class Filter {
141162
}
142163
this._spec.paths = new_paths;
143164
}
144-
}
165+
}

tools/proto-convert/src/PreProcessing.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,13 @@ import {SchemaModifier} from "./SchemaModifier";
88
import {VendorExtensionProcessor} from "./VendorExtensionProcessor";
99
import {GlobalParameterConsolidator} from "./GlobalParamWrapper";
1010
import {OpenSearchVersionExtractor} from "./OpenSearchVersionExtractor";
11-
import type {OpenAPIV3} from "openapi-types";
1211

13-
let config_filtered_path: string[] | undefined;
14-
try {
15-
const config = read_yaml(path.join(__dirname, 'config','target_api.yaml'));
16-
config_filtered_path = config.paths;
17-
} catch (e) {
18-
console.error(e);
19-
config_filtered_path = undefined;
20-
}
21-
const default_api_to_proto = config_filtered_path ?? ['/_search'];
22-
const default_api_to_proto_str = default_api_to_proto.join(',');
12+
// Load config from spec-filter.yaml
13+
const config = read_yaml<{ paths?: string[]; excluded_schemas?: string[] }>(
14+
path.join(__dirname, 'config', 'spec-filter.yaml')
15+
);
16+
const target_paths = config.paths ?? ['/_search'];
17+
const excluded_schemas = new Set(config.excluded_schemas ?? []);
2318

2419
const command = new Command()
2520
.description('Preprocess an OpenAPI spec by filtering for specific paths and then sanitizing it.')
@@ -28,7 +23,7 @@ const command = new Command()
2823
.addOption(
2924
new Option('-p, --filtered_path <paths>', 'the paths to keep (comma-separated, e.g., /_search,)')
3025
.argParser((val: string) => val.split(',').map(s => s.trim()))
31-
.default(default_api_to_proto)
26+
.default(target_paths)
3227
)
3328
.addOption(new Option('--verbose', 'show merge details').default(false))
3429
.addOption(new Option('--opensearch-version <version>', 'current OpenSearch version for deprecation removal').default('3.4'))
@@ -51,7 +46,7 @@ const logger = new Logger();
5146
try {
5247
logger.info(`PreProcessing ${opts.filtered_path.join(', ')} into ${opts.output} ...`)
5348
const original_spec = read_yaml(opts.input)
54-
const filtered_spec = new Filter().filter_spec(original_spec, opts.filtered_path);
49+
const filtered_spec = new Filter(logger, opts.filtered_path, excluded_schemas).filter_spec(original_spec);
5550
const version_processed_spec = new OpenSearchVersionExtractor(filtered_spec, logger).process(opts.opensearchVersion);
5651
const sanitized_spec = new Sanitizer().sanitize(version_processed_spec);
5752
const consolidated_spec = new GlobalParameterConsolidator(sanitized_spec).consolidate();
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Target API paths to include in proto generation
2+
paths:
3+
- /{index}/_bulk
4+
- /{index}/_search
5+
6+
# Schemas to exclude from proto generation
7+
# These schemas and their nested dependencies will not be included
8+
excluded_schemas:
9+
- AggregationContainer
10+
- Aggregate
11+
- Suggester
12+
- Suggest
13+
- CommonTermsQuery
14+
- CombinedFieldsQuery
15+
- DistanceFeatureQuery
16+
- GeoPolygonQuery
17+
- GeoShapeQuery
18+
- HasChildQuery
19+
- HasParentQuery
20+
- MoreLikeThisQuery
21+
- NeuralQuery
22+
- ParentIdQuery
23+
- PercolateQuery
24+
- RankFeatureQuery
25+
- SpanContainingQuery
26+
- SpanFieldMaskingQuery
27+
- SpanFirstQuery
28+
- SpanMultiTermQuery
29+
- SpanNearQuery
30+
- SpanNotQuery
31+
- SpanOrQuery
32+
- SpanTermQuery
33+
- SpanWithinQuery
34+
- ObjectMap
35+
- TypeQuery
36+
- WrapperQuery
37+
- XyShapeQuery

tools/proto-convert/src/config/target_api.yaml

Lines changed: 0 additions & 5 deletions
This file was deleted.

tools/proto-convert/src/utils/helper.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ import {dirname} from "path";
44
import {OpenAPIV3} from "openapi-types";
55
import _ from 'lodash';
66

7+
/**
8+
* Extracts schema names from a $ref string.
9+
*/
10+
export function getSchemaNames(ref: string): { full: string; short: string } | null {
11+
if (!ref.startsWith('#/components/schemas/')) return null;
12+
const full = ref.split('/').pop() || '';
13+
const short = full.includes('___') ? full.split('___').pop() || full : full;
14+
return { full, short };
15+
}
16+
717
export function read_yaml<T = Record<string, any>> (file_path: string, exclude_schema: boolean = false): T {
818
const doc = parse(readFileSync(file_path, 'utf8'))
919
if (typeof doc === 'object' && exclude_schema) delete doc.$schema
@@ -172,9 +182,24 @@ export function remove_unused(spec: OpenAPIV3.Document): void {
172182
(key) => _.keys((spec?.components as any)?.[key]).map((ref) => `#/components/${key}/${ref}`)
173183
);
174184

175-
deleteMatchingKeys(spec, (obj: any) =>
176-
obj.$ref !== undefined && !_.includes(remaining, obj.$ref)
177-
);
185+
// Remove properties where $ref is broken (direct or nested in additionalProperties)
186+
deleteMatchingKeys(spec, (obj: any) => {
187+
// Case 1: Direct broken $ref
188+
if (obj.$ref !== undefined && !_.includes(remaining, obj.$ref)) {
189+
return true;
190+
}
191+
// Case 2: additionalProperties.$ref is broken
192+
if (obj.additionalProperties?.$ref !== undefined &&
193+
!_.includes(remaining, obj.additionalProperties.$ref)) {
194+
return true;
195+
}
196+
// Case 3: additionalProperties.items.$ref is broken (for array types)
197+
if (obj.additionalProperties?.items?.$ref !== undefined &&
198+
!_.includes(remaining, obj.additionalProperties.items.$ref)) {
199+
return true;
200+
}
201+
return false;
202+
});
178203
}
179204

180205
/**

0 commit comments

Comments
 (0)