Skip to content

Commit 5eeb996

Browse files
Peter HaugeCopilot
andcommitted
feat: accept legacy *Names filter keys as backward-compat aliases
The config loader now accepts both Toolkit-style keys (e.g., apis, backends, versionSets) and legacy *Names keys (e.g., apiNames, backendNames, versionSetNames). Legacy keys emit a deprecation warning. Using both forms for the same field is an error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f058d6b commit 5eeb996

2 files changed

Lines changed: 103 additions & 48 deletions

File tree

src/lib/config-loader.ts

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -34,61 +34,59 @@ function assertStringArray(value: unknown, fieldName: string): string[] {
3434
* Load and parse a filter configuration YAML file.
3535
* Returns undefined if file doesn't exist.
3636
*/
37+
/**
38+
* Mapping from FilterConfig field to its legacy alias (the old *Names key).
39+
* Both the Toolkit-style key and the legacy alias are accepted during parsing.
40+
*/
41+
const FILTER_KEY_ALIASES: Record<keyof FilterConfig, string> = {
42+
apis: 'apiNames',
43+
backends: 'backendNames',
44+
products: 'productNames',
45+
namedValues: 'namedValueNames',
46+
loggers: 'loggerNames',
47+
diagnostics: 'diagnosticNames',
48+
tags: 'tagNames',
49+
policyFragments: 'policyFragmentNames',
50+
gateways: 'gatewayNames',
51+
versionSets: 'versionSetNames',
52+
groups: 'groupNames',
53+
subscriptions: 'subscriptionNames',
54+
schemas: 'schemaNames',
55+
policyRestrictions: 'policyRestrictionNames',
56+
documentations: 'documentationNames',
57+
workspaces: 'workspaceNames',
58+
};
59+
3760
export async function loadFilterConfig(filePath: string): Promise<FilterConfig | undefined> {
3861
try {
3962
const content = await fs.readFile(filePath, 'utf-8');
4063
const parsed = (yaml.load(content) ?? {}) as Record<string, unknown>;
4164

42-
// Validate structure — each field must be an array of strings
65+
// Validate structure — each field must be an array of strings.
66+
// Accept both Toolkit-style keys (e.g. "apis") and legacy aliases (e.g. "apiNames").
4367
const config: FilterConfig = {};
4468

45-
if (parsed.apis !== undefined) {
46-
config.apis = assertStringArray(parsed.apis, 'apis');
47-
}
48-
if (parsed.backends !== undefined) {
49-
config.backends = assertStringArray(parsed.backends, 'backends');
50-
}
51-
if (parsed.products !== undefined) {
52-
config.products = assertStringArray(parsed.products, 'products');
53-
}
54-
if (parsed.namedValues !== undefined) {
55-
config.namedValues = assertStringArray(parsed.namedValues, 'namedValues');
56-
}
57-
if (parsed.loggers !== undefined) {
58-
config.loggers = assertStringArray(parsed.loggers, 'loggers');
59-
}
60-
if (parsed.diagnostics !== undefined) {
61-
config.diagnostics = assertStringArray(parsed.diagnostics, 'diagnostics');
62-
}
63-
if (parsed.tags !== undefined) {
64-
config.tags = assertStringArray(parsed.tags, 'tags');
65-
}
66-
if (parsed.policyFragments !== undefined) {
67-
config.policyFragments = assertStringArray(parsed.policyFragments, 'policyFragments');
68-
}
69-
if (parsed.gateways !== undefined) {
70-
config.gateways = assertStringArray(parsed.gateways, 'gateways');
71-
}
72-
if (parsed.versionSets !== undefined) {
73-
config.versionSets = assertStringArray(parsed.versionSets, 'versionSets');
74-
}
75-
if (parsed.groups !== undefined) {
76-
config.groups = assertStringArray(parsed.groups, 'groups');
77-
}
78-
if (parsed.subscriptions !== undefined) {
79-
config.subscriptions = assertStringArray(parsed.subscriptions, 'subscriptions');
80-
}
81-
if (parsed.schemas !== undefined) {
82-
config.schemas = assertStringArray(parsed.schemas, 'schemas');
83-
}
84-
if (parsed.policyRestrictions !== undefined) {
85-
config.policyRestrictions = assertStringArray(parsed.policyRestrictions, 'policyRestrictions');
86-
}
87-
if (parsed.documentations !== undefined) {
88-
config.documentations = assertStringArray(parsed.documentations, 'documentations');
89-
}
90-
if (parsed.workspaces !== undefined) {
91-
config.workspaces = assertStringArray(parsed.workspaces, 'workspaces');
69+
for (const [field, legacyAlias] of Object.entries(FILTER_KEY_ALIASES)) {
70+
const key = field as keyof FilterConfig;
71+
const toolkitValue = parsed[field];
72+
const legacyValue = parsed[legacyAlias];
73+
74+
if (toolkitValue !== undefined && legacyValue !== undefined) {
75+
throw new Error(
76+
`Filter config contains both '${field}' and '${legacyAlias}'. ` +
77+
`Use '${field}' (the APIOps Toolkit format).`
78+
);
79+
}
80+
81+
if (toolkitValue !== undefined) {
82+
config[key] = assertStringArray(toolkitValue, field);
83+
} else if (legacyValue !== undefined) {
84+
logger.warn(
85+
`Filter key '${legacyAlias}' is deprecated; use '${field}' instead ` +
86+
`(APIOps Toolkit format).`
87+
);
88+
config[key] = assertStringArray(legacyValue, legacyAlias);
89+
}
9290
}
9391

9492
logger.debug(`Loaded filter config from ${filePath}`);

tests/unit/lib/config-loader.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,63 @@ workspaces: [p]
118118
expect(config!.apis).toEqual(['a']);
119119
expect(config!.workspaces).toEqual(['p']);
120120
});
121+
122+
it('should accept legacy *Names keys as aliases', async () => {
123+
const content = `
124+
apiNames:
125+
- api1
126+
- api2
127+
productNames:
128+
- starter
129+
backendNames:
130+
- backend1
131+
versionSetNames:
132+
- vs1
133+
`;
134+
const filePath = path.join(tmpDir, 'legacy.yaml');
135+
await fs.writeFile(filePath, content, 'utf-8');
136+
137+
const config = await loadFilterConfig(filePath);
138+
expect(config).toBeDefined();
139+
expect(config!.apis).toEqual(['api1', 'api2']);
140+
expect(config!.products).toEqual(['starter']);
141+
expect(config!.backends).toEqual(['backend1']);
142+
expect(config!.versionSets).toEqual(['vs1']);
143+
});
144+
145+
it('should throw when both Toolkit and legacy keys are used for the same field', async () => {
146+
const content = `
147+
apis:
148+
- api1
149+
apiNames:
150+
- api2
151+
`;
152+
const filePath = path.join(tmpDir, 'conflict.yaml');
153+
await fs.writeFile(filePath, content, 'utf-8');
154+
155+
await expect(loadFilterConfig(filePath)).rejects.toThrow(
156+
"contains both 'apis' and 'apiNames'"
157+
);
158+
});
159+
160+
it('should accept a mix of Toolkit and legacy keys for different fields', async () => {
161+
const content = `
162+
apis:
163+
- api1
164+
backendNames:
165+
- backend1
166+
versionSets:
167+
- vs1
168+
`;
169+
const filePath = path.join(tmpDir, 'mixed.yaml');
170+
await fs.writeFile(filePath, content, 'utf-8');
171+
172+
const config = await loadFilterConfig(filePath);
173+
expect(config).toBeDefined();
174+
expect(config!.apis).toEqual(['api1']);
175+
expect(config!.backends).toEqual(['backend1']);
176+
expect(config!.versionSets).toEqual(['vs1']);
177+
});
121178
});
122179

123180
describe('loadOverrideConfig', () => {

0 commit comments

Comments
 (0)