You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Wildcard support in filters (#48) lets users concisely include large sets of resources by naming convention:
apiNames:
- 'prod-*'
But there's no way to say "include everything matching this pattern except these specific ones." Users have hit this in practice — e.g. "include all prod-* APIs but not prod-legacy-billing," or "extract all backends except the shared infra ones."
The current workarounds are all bad:
Enumerate every name explicitly (defeats the purpose of wildcards).
Split into multiple runs with different filter files and stitch results.
Post-process artifacts to remove unwanted ones (same problem the filter was meant to solve).
Proposed solution
Add a negation operator! as the first character of any filter entry. A leading ! marks the entry as an exclusion rather than an inclusion. Exclusions are always evaluated after inclusions for the same resource type: a resource is included iff at least one inclusion matches it and no exclusion matches it.
! must be the first character of the entry to be interpreted as negation. foo!bar is a literal name.
The remainder of the string is a normal filter entry: exact name or wildcard pattern (*, ?), case-insensitive, same as today.
Evaluation order: for each candidate resource, check inclusions first; if none match, exclude. If any inclusion matches, then check exclusions; if any exclusion matches, exclude. Otherwise include.
A filter list containing only exclusions is treated as "include-all, then subtract" — equivalent to prepending an implicit *. This is a convenience so users can write apiNames: ['!prod-legacy-billing'] to mean "everything except that one."
Applies uniformly to all filter fields that already accept string entries (apiNames, productNames, backendNames, namedValueNames, etc.) and to sub-filter fields inside apiSubFilters and workspaceSubFilters.
Case-insensitive matching for both inclusion and exclusion, matching current filter behavior.
No escape hatch needed. Verified against APIM resource name constraints: every filterable resource type (API, Backend, Product, NamedValue, PolicyFragment, Gateway, Workspace, Subscription, Tag, VersionSet, Logger, Diagnostic, Schema, Documentation) requires a name matching a pattern that starts with (and only contains) alphanumeric characters, hyphens, and — for policy fragments — underscores. ! cannot appear anywhere in a valid APIM resource name, so a leading ! in a filter entry is unambiguous by construction.
Interaction with existing features:
API revision-suffix handling: exclusions match the same way inclusions do (root name matched with revision suffix stripped, per current filter-service.ts behavior).
Parent → child inheritance: if a parent (Api, Product, Gateway, Workspace) is excluded, its children are excluded too, matching how inclusions inherit today.
Object-form entries (nested sub-filters): the object form uses the resource name as a key, so ! can be a leading character of that key. Same rules apply.
Localize the change to src/services/filter-service.ts. The public shouldIncludeResource / filterResources API stays the same; only matchesFilter and its wildcard/exact-match helpers need to understand negation.
Suggested internal shape: partition each allowlist once at load time (or lazily on first use) into { includes: string[]; excludes: string[] }, then decide inclusion as (includes.length === 0 || any(includes match)) && !any(excludes match).
No config-schema change needed — !-prefixed strings are still valid string entries. The loader in src/lib/config-loader.ts doesn't need to know about the operator.
More expressive expression languages (regex, boolean AND/OR, grouping). If users want that later, it's a separate design conversation.
Changing the shape of FilterConfig or introducing a new field for exclusions. The ! prefix is intentionally minimal and stays within the current schema.
Acceptance criteria
Filter entries whose first character is ! are treated as exclusions.
! supports both exact names and wildcard patterns (!foo, !prod-*).
A resource is included iff at least one inclusion matches (or the list contains only exclusions) AND no exclusion matches.
Unit tests in filter-service cover: pure-exclusion list, mixed inclusion + exclusion, wildcard exclusion, exclusion of API root vs. revision, parent exclusion cascading to children.
Docs updated with negation examples alongside the existing wildcard docs.
Problem or use case
Wildcard support in filters (#48) lets users concisely include large sets of resources by naming convention:
But there's no way to say "include everything matching this pattern except these specific ones." Users have hit this in practice — e.g. "include all
prod-*APIs but notprod-legacy-billing," or "extract all backends except the shared infra ones."The current workarounds are all bad:
Proposed solution
Add a negation operator
!as the first character of any filter entry. A leading!marks the entry as an exclusion rather than an inclusion. Exclusions are always evaluated after inclusions for the same resource type: a resource is included iff at least one inclusion matches it and no exclusion matches it.Examples:
Semantics:
!must be the first character of the entry to be interpreted as negation.foo!baris a literal name.*,?), case-insensitive, same as today.*. This is a convenience so users can writeapiNames: ['!prod-legacy-billing']to mean "everything except that one."apiNames,productNames,backendNames,namedValueNames, etc.) and to sub-filter fields insideapiSubFiltersandworkspaceSubFilters.!cannot appear anywhere in a valid APIM resource name, so a leading!in a filter entry is unambiguous by construction.Interaction with existing features:
filter-service.tsbehavior).!can be a leading character of that key. Same rules apply.Applies to both:
apiops extract --filterapiops publish --filter(once Add--filterflag to subset resources during publish #215 lands — exclusions "just work" iffilter-serviceis the single source of truth, which it is)Implementation notes (non-binding)
src/services/filter-service.ts. The publicshouldIncludeResource/filterResourcesAPI stays the same; onlymatchesFilterand its wildcard/exact-match helpers need to understand negation.{ includes: string[]; excludes: string[] }, then decide inclusion as(includes.length === 0 || any(includes match)) && !any(excludes match).!-prefixed strings are still valid string entries. The loader insrc/lib/config-loader.tsdoesn't need to know about the operator.Out of scope
FilterConfigor introducing a new field for exclusions. The!prefix is intentionally minimal and stays within the current schema.Acceptance criteria
!are treated as exclusions.!supports both exact names and wildcard patterns (!foo,!prod-*).apiNames,backendNames, etc.) and inapiSubFilters/workspaceSubFiltersstring lists.apiops extract --filtertoday and forapiops publish --filteronce Add--filterflag to subset resources during publish #215 lands (no publish-specific changes required).filter-servicecover: pure-exclusion list, mixed inclusion + exclusion, wildcard exclusion, exclusion of API root vs. revision, parent exclusion cascading to children.Affected command
apiops extract,apiops publish(once #215 lands)