Skip to content

Commit df5e753

Browse files
authored
Merge pull request #4 from zguydev/dev
feat: collect refs on components filter
2 parents 945838d + 60d9a99 commit df5e753

9 files changed

Lines changed: 256 additions & 157 deletions

File tree

README.md

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ The filter configuration file (e.g., `.openapi-filter.yaml`) specifies what part
3737
# Tool-specific configurations (optional)
3838
x-openapi-filter:
3939
logger:
40-
level: info # e.g., debug, info, warn, error
40+
level: info # Log level (e.g., "debug", "info", "warn", "error")
4141
loader:
42-
external_refs_allowed: false # Controls resolution of external $refs
42+
external_refs_allowed: false # Whether to allow external references
4343

4444
# Keep or discard server information (default: false)
4545
servers: true
@@ -53,13 +53,9 @@ externalDocs: true
5353
# Specify paths and methods to keep.
5454
# If a path is listed, only the specified methods are kept.
5555
paths:
56-
/pets:
57-
- post
58-
- put
59-
/pet/{petId}/uploadImage:
60-
- post
61-
/user/login:
62-
- get
56+
/pets: [ post, put ]
57+
/pet/{petId}/uploadImage: [ post ]
58+
/user/login: [ get ]
6359
# Paths not listed here will be removed.
6460

6561
# Specify components to keep.
@@ -70,8 +66,8 @@ components:
7066
- User
7167
- Error
7268
securitySchemes:
73-
- bearerAuth
74-
# Components not listed (and not referenced) will be removed.
69+
- petstore_auth
70+
# Components not listed (that are not referenced from kept paths) will be removed.
7571
```
7672

7773
## Examples

example.openapi-filter.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ external_refs_allowed = false
1111

1212
[paths]
1313
"/api/example" = [ "get", "post" ]
14-
"/api/some-another" = [ "put" ]
14+
"/api/some-another-example" = [ "put" ]
1515

1616
[components]
1717
securitySchemes = [ "ApiKeyAuth" ]

example.openapi-filter.yaml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ tags: true
1010
externalDocs: false
1111

1212
paths:
13-
/api/example:
14-
- get
15-
- post
16-
/api/some-another:
17-
- put
13+
/api/example: [ get, post ]
14+
/api/some-another-example: [ put ]
1815

1916
components:
2017
securitySchemes:

examples/OpenAI/.openapi-filter.yaml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@ x-openapi-filter:
33
level: debug
44

55
servers: true
6+
security: true
7+
tags: true
68

79
paths:
8-
/chat/completions:
9-
- post
10+
/chat/completions: [ post ]
1011

1112
components:
1213
securitySchemes:
1314
- ApiKeyAuth
14-
15-
security: true
16-
tags: true

examples/petstore/.openapi-filter.yaml

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,9 @@ tags: true
88
externalDocs: true
99

1010
paths:
11-
/pet:
12-
- put
13-
- post
14-
/pet/{petId}/uploadImage:
15-
- post
16-
/user/login:
17-
- get
11+
/pet: [ post, put ]
12+
/pet/{petId}/uploadImage: [ post ]
13+
/user/login: [ get ]
1814

1915
components:
2016
securitySchemes:

internal/config/config.go

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,52 @@
1+
// Package config provides configuration structures for the OpenAPI filter tool.
2+
// It defines the configuration format for filtering OpenAPI specs and
3+
// tool-specific settings.
14
package config
25

6+
// Config represents the root configuration structure for the OpenAPI filter tool.
7+
// It combines tool-specific settings with filter configuration.
38
type Config struct {
49
Tool ToolConfig `koanf:"x-openapi-filter"`
510
FilterConfig `koanf:",squash"`
611
}
712

13+
// FilterConfig defines the configuration for filtering an OpenAPI spec.
14+
// It specifies which parts of the spec should be included in the output.
815
type FilterConfig struct {
9-
Servers bool `koanf:"servers"`
10-
Paths map[string][]string `koanf:"paths"`
11-
Components *FilterComponentsConfig `koanf:"components"`
12-
Security bool `koanf:"security"`
13-
Tags bool `koanf:"tags"`
14-
ExternalDocs bool `koanf:"externalDocs"`
16+
Servers bool `koanf:"servers"` // Include servers section
17+
Paths map[string][]string `koanf:"paths"` // Map of paths to allowed HTTP methods
18+
Components *FilterComponentsConfig `koanf:"components"` // Component filtering configuration
19+
Security bool `koanf:"security"` // Include security requirements
20+
Tags bool `koanf:"tags"` // Include tags
21+
ExternalDocs bool `koanf:"externalDocs"` // Include external documentation
1522
}
1623

24+
// FilterComponentsConfig specifies which components should be included in the
25+
// filtered OpenAPI spec. Each field is a list of component names to include.
1726
type FilterComponentsConfig struct {
18-
Schemas []string `koanf:"schemas"`
19-
Parameters []string `koanf:"parameters"`
20-
SecuritySchemes []string `koanf:"securitySchemes"`
21-
RequestBodies []string `koanf:"requestBodies"`
22-
Responses []string `koanf:"responses"`
23-
Headers []string `koanf:"headers"`
24-
Examples []string `koanf:"examples"`
25-
Links []string `koanf:"links"`
26-
Callbacks []string `koanf:"callbacks"`
27+
Schemas []string `koanf:"schemas"` // List of schema names to include
28+
Parameters []string `koanf:"parameters"` // List of parameter names to include
29+
SecuritySchemes []string `koanf:"securitySchemes"` // List of security scheme names to include
30+
RequestBodies []string `koanf:"requestBodies"` // List of request body names to include
31+
Responses []string `koanf:"responses"` // List of response names to include
32+
Headers []string `koanf:"headers"` // List of header names to include
33+
Examples []string `koanf:"examples"` // List of example names to include
34+
Links []string `koanf:"links"` // List of link names to include
35+
Callbacks []string `koanf:"callbacks"` // List of callback names to include
2736
}
2837

38+
// ToolConfig contains tool-specific configuration settings.
2939
type ToolConfig struct {
30-
Logger *LoggerConfig `koanf:"logger"`
31-
Loader *LoaderConfig `koanf:"loader"`
40+
Logger *LoggerConfig `koanf:"logger"` // Logger configuration
41+
Loader *LoaderConfig `koanf:"loader"` // OpenAPI loader configuration
3242
}
3343

44+
// LoggerConfig defines the logging configuration for the tool.
3445
type LoggerConfig struct {
35-
Level string `koanf:"level"`
46+
Level string `koanf:"level"` // Log level (e.g., "debug", "info", "warn", "error")
3647
}
3748

49+
// LoaderConfig defines configuration for the OpenAPI spec loader.
3850
type LoaderConfig struct {
39-
IsExternalRefsAllowed bool `koanf:"external_refs_allowed"`
51+
IsExternalRefsAllowed bool `koanf:"external_refs_allowed"` // Whether to allow external references
4052
}

internal/filter/filter.go

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Package filter provides functionality to filter OpenAPI specs based
2+
// on config. It allows filtering of paths, methods, components,
3+
// and other OpenAPI elements while maintaining the integrity of the spec.
14
package filter
25

36
import (
@@ -10,14 +13,18 @@ import (
1013
"github.com/zguydev/openapi-filter/internal/config"
1114
)
1215

16+
// OpenAPISpecFilter is the main type that handles filtering of OpenAPI specs.
1317
type OpenAPISpecFilter struct {
14-
cfg *config.FilterConfig
15-
logger *zap.Logger
16-
loader *openapi3.Loader
18+
cfg *config.FilterConfig
19+
logger *zap.Logger
20+
loader *openapi3.Loader
21+
collector *RefsCollector
1722

18-
filtered, doc *openapi3.T
23+
doc, filtered *openapi3.T
1924
}
2025

26+
// NewOpenAPISpecFilter creates a new OpenAPISpecFilter instance with the
27+
// provided configuration and logger. It initializes the OpenAPI loader.
2128
func NewOpenAPISpecFilter(
2229
cfg *config.Config,
2330
logger *zap.Logger,
@@ -28,12 +35,23 @@ func NewOpenAPISpecFilter(
2835
}
2936

3037
return &OpenAPISpecFilter{
31-
cfg: &cfg.FilterConfig,
32-
logger: logger,
33-
loader: loader,
38+
cfg: &cfg.FilterConfig,
39+
logger: logger,
40+
loader: loader,
41+
collector: NewRefsCollector(),
3442
}
3543
}
3644

45+
// Filter processes an OpenAPI spec file according to the configured
46+
// filters and writes the filtered result to the specified output path.
47+
// It handles loading, filtering, and writing of the spec while
48+
// maintaining all necessary references and components.
49+
//
50+
// Parameters:
51+
// - inputSpecPath: Path to the input OpenAPI spec file
52+
// - outSpecPath: Path where the filtered spec will be written
53+
//
54+
// Returns an error if any step of the filtering process fails.
3755
func (oaf *OpenAPISpecFilter) Filter(inputSpecPath, outSpecPath string) error {
3856
var err error
3957
oaf.doc, err = loadSpecFromFile(oaf.loader, inputSpecPath)
@@ -49,11 +67,10 @@ func (oaf *OpenAPISpecFilter) Filter(inputSpecPath, outSpecPath string) error {
4967
Paths: &openapi3.Paths{},
5068
}
5169

52-
collector := NewRefsCollector()
53-
54-
oaf.filterPaths(collector)
70+
oaf.filterPaths()
5571
oaf.filterComponents()
5672
oaf.filterOther()
73+
oaf.filterRefs()
5774
if isEmptyComponents(oaf.filtered.Components) {
5875
oaf.filtered.Components = nil
5976
}
@@ -67,7 +84,10 @@ func (oaf *OpenAPISpecFilter) Filter(inputSpecPath, outSpecPath string) error {
6784
return nil
6885
}
6986

70-
func (oaf *OpenAPISpecFilter) filterPaths(collector *RefsCollector) {
87+
// filterPaths processes the paths specified in the configuration and filters them
88+
// according to the allowed methods. It also collects all references used in the
89+
// filtered paths.
90+
func (oaf *OpenAPISpecFilter) filterPaths() {
7191
for path, methods := range oaf.cfg.Paths {
7292
pathItem := oaf.doc.Paths.Find(path)
7393
if pathItem == nil {
@@ -80,37 +100,41 @@ func (oaf *OpenAPISpecFilter) filterPaths(collector *RefsCollector) {
80100
op := oaf.getOperation(pathItem, method, path)
81101
if op == nil {
82102
oaf.logger.Warn("method not exists for specified path",
83-
zap.String("path", path),
84-
zap.String("method", method))
103+
zap.String("method", method),
104+
zap.String("path", path))
85105
continue
86106
}
87107
if !oaf.setOperation(newPathItem, method, path, op) {
88108
continue
89109
}
90-
collector.CollectOperation(op)
110+
oaf.collector.CollectOperation(op)
91111
}
92112

93113
oaf.filtered.Paths.Set(path, newPathItem)
94114
}
95-
96-
oaf.filterRefs(collector.Refs())
97115
}
98116

117+
// getOperation safely retrieves an operation from a PathItem for the specified
118+
// method. It handles unknown HTTP methods gracefully and returns nil if the
119+
// method is invalid.
99120
func (oaf *OpenAPISpecFilter) getOperation(
100121
p *openapi3.PathItem,
101122
method, path string,
102123
) (op *openapi3.Operation) {
103124
defer func() {
104125
if r := recover(); r != nil {
105126
oaf.logger.Warn("unknown HTTP method in filter config",
106-
zap.String("path", path),
107-
zap.String("method", method))
127+
zap.String("method", method),
128+
zap.String("path", path))
108129
op = nil
109130
}
110131
}()
111132
return p.GetOperation(strings.ToUpper(method))
112133
}
113134

135+
// setOperation safely sets an operation in a PathItem for the specified method.
136+
// It handles unknown HTTP methods gracefully and returns false if the method
137+
// is invalid.
114138
func (oaf *OpenAPISpecFilter) setOperation(
115139
p *openapi3.PathItem,
116140
method, path string,
@@ -119,21 +143,25 @@ func (oaf *OpenAPISpecFilter) setOperation(
119143
defer func() {
120144
if r := recover(); r != nil {
121145
oaf.logger.Warn("unknown HTTP method in spec",
122-
zap.String("path", path),
123-
zap.String("method", method))
146+
zap.String("method", method),
147+
zap.String("path", path))
124148
ok = false
125149
}
126150
}()
127151
p.SetOperation(strings.ToUpper(method), operation)
128152
return true
129153
}
130154

131-
func (oaf *OpenAPISpecFilter) filterRefs(refs map[string]struct{}) {
132-
for ref := range refs {
155+
// filterRefs processes all collected references and ensures they are properly
156+
// included in the filtered spec.
157+
func (oaf *OpenAPISpecFilter) filterRefs() {
158+
for ref := range oaf.collector.refs {
133159
oaf.filterRef(ref)
134160
}
135161
}
136162

163+
// filterRef processes a single reference and copies the referenced component
164+
// to the filtered spec.
137165
func (oaf *OpenAPISpecFilter) filterRef(ref string) {
138166
if oaf.doc.Components == nil {
139167
return
@@ -148,39 +176,49 @@ func (oaf *OpenAPISpecFilter) filterRef(ref string) {
148176
compType, ok := ComponentDefToType(def)
149177
if !ok {
150178
oaf.logger.Warn("unknown component definition",
151-
zap.String("def", def), zap.String("ref", ref))
179+
zap.String("def", def),
180+
zap.String("name", name),
181+
zap.String("ref", ref))
152182
return
153183
}
154-
if !processCopyComponent(
155-
oaf.doc.Components,
184+
if !processCopyComponent(oaf.doc.Components,
156185
oaf.filtered.Components,
157-
compType, name) {
186+
compType,
187+
name,
188+
) {
158189
oaf.logger.Warn("component not found",
159-
zap.String("name", name),
160190
zap.String("def", def),
191+
zap.String("name", name),
161192
zap.String("ref", ref))
162193
}
163194
}
164195

196+
// filterComponents processes all components specified in the configuration and
197+
// copies them to the filtered spec.
165198
func (oaf *OpenAPISpecFilter) filterComponents() {
166199
if oaf.cfg.Components == nil || oaf.doc.Components == nil {
167200
return
168201
}
169202

170203
for _, compTyp := range ComponentTypes() {
171204
for _, name := range ComponentTypeToCfgNames(oaf.cfg.Components, compTyp) {
172-
if !processCopyComponent(
173-
oaf.doc.Components,
205+
if !processCopyComponent(oaf.doc.Components,
174206
oaf.filtered.Components,
175-
compTyp, name) {
207+
compTyp,
208+
name,
209+
) {
176210
oaf.logger.Warn("component not found",
177-
zap.String("name", name),
178-
zap.String("def", ComponentTypeToDef(compTyp)))
211+
zap.String("def", ComponentTypeToDef(compTyp)),
212+
zap.String("name", name))
213+
continue
179214
}
215+
CollectComponent(oaf.collector, oaf.doc.Components, compTyp, name)
180216
}
181217
}
182218
}
183219

220+
// filterOther processes additional OpenAPI elements specified in the configuration,
221+
// including servers, security requirements, tags, and external documentation.
184222
func (oaf *OpenAPISpecFilter) filterOther() {
185223
if oaf.cfg.Servers {
186224
oaf.filtered.Servers = oaf.doc.Servers

0 commit comments

Comments
 (0)