openapi_slicer 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +128 -0
- data/lib/openapi_slicer.rb +197 -0
- data/scripts/openapi_slicer_cli.rb +82 -0
- data/test/lib/openapi_slicer_test.rb +180 -0
- data/test/scripts/openapi_slicer_cli_test.rb +65 -0
- data/test/test_helper.rb +5 -0
- metadata +63 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7261d0affd63001036a1b223672a24cbad3e4cfe6422255d5cf3eee54cb6e994
|
4
|
+
data.tar.gz: 8e64dec7fb3c9dfcd9aa981e3c442562d11ee9e29b986fafff8542930a353f04
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 507012991091f89621f4a6e2d7cc12d831326df7183c8823f2107a9bdc86de482eec0e58330064f3a61b70976e64a31f4141389267d8583d0cbbdf19a0cf96ca
|
7
|
+
data.tar.gz: b76b6c24c3b8d2ff23baafe2398ded1aee07d385ea037d8748f4e82cd15ea19a65428cacfa7d92c3e371a7785613f7130955ea1baf59cabaf589dab5652e77f9
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
|
2
|
+
# OpenAPI Slicer
|
3
|
+
|
4
|
+
`openapi_slicer` is a Ruby gem designed to extract specific parts of an OpenAPI specification (either in JSON or YAML format) based on a regular expression. It slices paths from the spec that match the given regex and ensures that all necessary tag and component dependencies are included in the result. You can export the filtered OpenAPI spec to a file in JSON or YAML format.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'openapi_slicer'
|
12
|
+
```
|
13
|
+
|
14
|
+
Then execute:
|
15
|
+
|
16
|
+
```bash
|
17
|
+
bundle install
|
18
|
+
```
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
```bash
|
23
|
+
gem install openapi_slicer
|
24
|
+
```
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
### Initializing the Slicer
|
29
|
+
|
30
|
+
First, initialize an `OpenapiSlicer` instance by providing the path to a JSON or YAML OpenAPI spec file.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'openapi_slicer'
|
34
|
+
|
35
|
+
slicer = OpenapiSlicer.new(file_path: 'path/to/openapi_spec.yaml')
|
36
|
+
```
|
37
|
+
|
38
|
+
### Filtering by Paths
|
39
|
+
|
40
|
+
To filter the OpenAPI spec based on a regular expression, use the `filter` method:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# Filter the spec for all paths under '/pets'
|
44
|
+
filtered_spec = slicer.filter(%r{^/pets})
|
45
|
+
```
|
46
|
+
|
47
|
+
This will return a new spec that contains only the paths that match `/pets`, along with all the necessary components, tags, and other dependencies.
|
48
|
+
|
49
|
+
### Exporting the Filtered Spec
|
50
|
+
|
51
|
+
You can also directly export the filtered spec to a file using the `export` method:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
# Export the filtered spec to a new JSON file
|
55
|
+
slicer.export(%r{^/pets}, 'filtered_spec.json')
|
56
|
+
|
57
|
+
# Or export to a YAML file
|
58
|
+
slicer.export(%r{^/pets}, 'filtered_spec.yaml')
|
59
|
+
```
|
60
|
+
|
61
|
+
### Example
|
62
|
+
|
63
|
+
Suppose you have the following paths in your OpenAPI spec:
|
64
|
+
|
65
|
+
- `/pets`
|
66
|
+
- `/pets/{petId}`
|
67
|
+
- `/pets/{petId}/health`
|
68
|
+
- `/owners/{ownerId}`
|
69
|
+
|
70
|
+
Using the `filter` method, you can slice out only the paths under `/pets`:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
filtered_spec = slicer.filter(%r{^/pets})
|
74
|
+
```
|
75
|
+
|
76
|
+
This will return a spec containing:
|
77
|
+
- `/pets`
|
78
|
+
- `/pets/{petId}`
|
79
|
+
- `/pets/{petId}/health`
|
80
|
+
|
81
|
+
Any necessary `$ref` components or tag dependencies will also be included in the filtered spec.
|
82
|
+
|
83
|
+
## Development
|
84
|
+
|
85
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run the tests using:
|
86
|
+
|
87
|
+
```bash
|
88
|
+
rake test
|
89
|
+
```
|
90
|
+
|
91
|
+
To install this gem onto your local machine, run:
|
92
|
+
|
93
|
+
```bash
|
94
|
+
bundle exec rake install
|
95
|
+
```
|
96
|
+
|
97
|
+
|
98
|
+
## Command-Line Interface (CLI)
|
99
|
+
|
100
|
+
To use the CLI, run the `ruby scripts/openapi_slicer` command with the following options:
|
101
|
+
|
102
|
+
- `-i`, `--input FILE`: **Required.** The path to the input OpenAPI specification file (in JSON or YAML format).
|
103
|
+
- `-r`, `--regex REGEX`: **Required.** A regular expression used to filter the paths from the OpenAPI file.
|
104
|
+
- `-o`, `--output FILE`: (Optional) The path where the filtered output will be saved. If not provided, the filtered result will be printed to the console.
|
105
|
+
- `-h`, `--help`: Displays help information for the available options.
|
106
|
+
|
107
|
+
### Example
|
108
|
+
|
109
|
+
Filter an OpenAPI spec file and print the result to the console:
|
110
|
+
```bash
|
111
|
+
ruby scripts/openapi_slicer -i openapi.json -r '/api/v1/users'
|
112
|
+
```
|
113
|
+
|
114
|
+
Filter an OpenAPI spec file and save the result to an output file:
|
115
|
+
```bash
|
116
|
+
ruby scripts/openapi_slicer -i openapi.json -r '/api/v1/users' -o filtered_spec.json
|
117
|
+
```
|
118
|
+
|
119
|
+
If required options are missing, the CLI will display an error message and terminate.
|
120
|
+
|
121
|
+
|
122
|
+
## Contributing
|
123
|
+
|
124
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/thescubageek/openapi_slicer](https://github.com/thescubageek/openapi_slicer).
|
125
|
+
|
126
|
+
## License
|
127
|
+
|
128
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "json"
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
# OpenapiSlicer is a tool that slices an OpenAPI spec based on a regular expression,
|
8
|
+
# and filters out necessary paths and components, ensuring that all dependent components
|
9
|
+
# (schemas, parameters, etc.) are included.
|
10
|
+
class OpenapiSlicer
|
11
|
+
# The current version of the OpenapiSlicer
|
12
|
+
VERSION = "0.2.0"
|
13
|
+
|
14
|
+
# @return [Hash] the OpenAPI specification loaded from the file
|
15
|
+
attr_accessor :spec
|
16
|
+
|
17
|
+
# Initializes the OpenapiSlicer.
|
18
|
+
#
|
19
|
+
# @param file_path [String] the path to the OpenAPI spec file (JSON or YAML)
|
20
|
+
# @raise [RuntimeError] if the file is not a JSON or YAML file
|
21
|
+
def initialize(file_path:)
|
22
|
+
raise "Invalid file type. Only JSON and YAML are supported." unless file_path.match?(/\.(json|ya?ml)$/)
|
23
|
+
|
24
|
+
@file_path = file_path
|
25
|
+
@spec = load_spec(file_path)
|
26
|
+
@components = {}
|
27
|
+
@tags = Set.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Filters the OpenAPI spec paths based on a regular expression and extracts the
|
31
|
+
# necessary components and tags.
|
32
|
+
#
|
33
|
+
# @param regex [Regexp] the regular expression to match against the paths
|
34
|
+
# @return [Hash] the filtered OpenAPI spec with paths and dependencies
|
35
|
+
def filter(regex)
|
36
|
+
paths = @spec["paths"].select { |path, _| path.match?(regex) }
|
37
|
+
dependencies = extract_dependencies(paths)
|
38
|
+
slice_spec(paths, dependencies)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Exports the filtered OpenAPI spec to a file.
|
42
|
+
#
|
43
|
+
# @param regex [Regexp] the regular expression to match against the paths
|
44
|
+
# @param target_file [String] the file path to write the filtered spec to
|
45
|
+
def export(regex, target_file)
|
46
|
+
result = filter(regex)
|
47
|
+
File.open(target_file, "w") do |f|
|
48
|
+
if target_file.match?(/\.json$/)
|
49
|
+
f.write(result.to_json)
|
50
|
+
else
|
51
|
+
f.write(result.to_yaml)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Loads the OpenAPI spec from the file.
|
59
|
+
#
|
60
|
+
# @param file_path [String] the path to the OpenAPI spec file
|
61
|
+
# @return [Hash] the loaded OpenAPI spec
|
62
|
+
def load_spec(file_path)
|
63
|
+
if file_path.end_with?(".json")
|
64
|
+
JSON.parse(File.read(file_path))
|
65
|
+
else
|
66
|
+
YAML.load_file(file_path)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Extracts all component and tag dependencies from the filtered paths.
|
71
|
+
#
|
72
|
+
# @param paths [Hash] the filtered OpenAPI paths
|
73
|
+
# @return [Set] a set of dependencies found in the paths
|
74
|
+
def extract_dependencies(paths)
|
75
|
+
dependencies = Set.new
|
76
|
+
paths.each_value do |operations|
|
77
|
+
operations.each_value do |operation|
|
78
|
+
extract_from_operation(operation, dependencies)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
dependencies
|
82
|
+
end
|
83
|
+
|
84
|
+
# Extracts $ref dependencies from a given operation.
|
85
|
+
#
|
86
|
+
# @param operation [Hash] the operation to extract dependencies from
|
87
|
+
# @param dependencies [Set] the set to store discovered dependencies
|
88
|
+
def extract_from_operation(operation, dependencies)
|
89
|
+
operation["parameters"]&.each { |param| resolve_ref(param["$ref"], dependencies) if param["$ref"] }
|
90
|
+
operation["responses"]&.each_value do |response|
|
91
|
+
resolve_response_refs(response, dependencies)
|
92
|
+
end
|
93
|
+
@tags.merge(operation["tags"]) if operation["tags"]
|
94
|
+
end
|
95
|
+
|
96
|
+
# Resolves component references recursively and adds them to the dependency set.
|
97
|
+
#
|
98
|
+
# @param ref [String] the $ref string pointing to a component
|
99
|
+
# @param dependencies [Set] the set to store discovered dependencies
|
100
|
+
def resolve_ref(ref, dependencies)
|
101
|
+
return unless ref
|
102
|
+
|
103
|
+
component = ref.split("/").last
|
104
|
+
return if dependencies.include?(component)
|
105
|
+
|
106
|
+
dependencies.add(component)
|
107
|
+
|
108
|
+
component_data = slice_component_data(component)
|
109
|
+
return unless component_data
|
110
|
+
|
111
|
+
resolve_component_property_refs(component_data, dependencies)
|
112
|
+
resolve_nested_refs(component_data, dependencies)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Resolves response references recursively.
|
116
|
+
#
|
117
|
+
# @param response [Hash] the response object to resolve references from
|
118
|
+
# @param dependencies [Set] the set to store discovered dependencies
|
119
|
+
def resolve_response_refs(response, dependencies)
|
120
|
+
resolve_ref(response["$ref"], dependencies) if response["$ref"]
|
121
|
+
response["content"]&.each_value do |media_type|
|
122
|
+
resolve_ref(media_type.dig("schema", "$ref"), dependencies) if media_type.dig("schema", "$ref")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Resolves references based on component properties
|
127
|
+
#
|
128
|
+
# @param component_data [Hash] component spec data
|
129
|
+
# @param dependencies []
|
130
|
+
def resolve_component_property_refs(component_data, dependencies)
|
131
|
+
return unless component_data&.[]("properties")
|
132
|
+
|
133
|
+
component_data["properties"].each_value do |property|
|
134
|
+
resolve_ref(property["$ref"], dependencies) if property["$ref"]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# Resolves references based on component properties
|
140
|
+
#
|
141
|
+
# @param component_data [Hash] component spec data
|
142
|
+
# @param dependencies []
|
143
|
+
def resolve_nested_refs(component_data, dependencies)
|
144
|
+
return unless component_data&.[]("allOf")
|
145
|
+
|
146
|
+
component_data["allOf"].each { |sub| resolve_ref(sub["$ref"], dependencies) if sub["$ref"] }
|
147
|
+
end
|
148
|
+
|
149
|
+
# Slices the component data from the specs
|
150
|
+
#
|
151
|
+
# @param component [String] component name
|
152
|
+
# @return [Hash|nil] component data from the specs
|
153
|
+
def slice_component_data(component)
|
154
|
+
@spec.dig("components", "schemas", component) ||
|
155
|
+
@spec.dig("components", "responses", component) ||
|
156
|
+
@spec.dig("components", "parameters", component) ||
|
157
|
+
@spec.dig("components", "requestBodies", component)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Slices the spec, keeping only the filtered paths, components, and tags.
|
161
|
+
#
|
162
|
+
# @param paths [Hash] the filtered paths
|
163
|
+
# @param dependencies [Set] the set of component dependencies
|
164
|
+
# @return [Hash] the sliced OpenAPI spec
|
165
|
+
def slice_spec(paths, dependencies)
|
166
|
+
result = {
|
167
|
+
"openapi" => @spec["openapi"],
|
168
|
+
"info" => @spec["info"],
|
169
|
+
"paths" => paths,
|
170
|
+
"components" => slice_components(dependencies),
|
171
|
+
"tags" => slice_tags
|
172
|
+
}
|
173
|
+
result["servers"] = @spec["servers"] if @spec["servers"]
|
174
|
+
result
|
175
|
+
end
|
176
|
+
|
177
|
+
# Slices and retains only the necessary components.
|
178
|
+
#
|
179
|
+
# @param dependencies [Set] the set of component dependencies
|
180
|
+
# @return [Hash] the sliced components
|
181
|
+
def slice_components(dependencies)
|
182
|
+
sliced = {}
|
183
|
+
%w[schemas responses parameters requestBodies].each do |type|
|
184
|
+
next unless @spec["components"] && @spec["components"][type]
|
185
|
+
|
186
|
+
sliced[type] = @spec["components"][type].select { |name, _| dependencies.include?(name) }
|
187
|
+
end
|
188
|
+
sliced
|
189
|
+
end
|
190
|
+
|
191
|
+
# Slices and retains only the necessary tags.
|
192
|
+
#
|
193
|
+
# @return [Array] the sliced tags
|
194
|
+
def slice_tags
|
195
|
+
@spec["tags"]&.select { |tag| @tags.include?(tag["name"]) }
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require_relative '../lib/openapi_slicer'
|
5
|
+
|
6
|
+
# Command-line interface (CLI) for the OpenAPI Slicer.
|
7
|
+
# This class provides a way to interact with the OpenAPI Slicer via the command line,
|
8
|
+
# allowing users to filter and export portions of an OpenAPI specification based on a regular expression.
|
9
|
+
class OpenapiSlicerCLI
|
10
|
+
# @return [Hash] the options passed from the command line, including input file path, regex, and output file path.
|
11
|
+
attr_reader :options
|
12
|
+
|
13
|
+
# Initializes the CLI with arguments passed from the command line.
|
14
|
+
#
|
15
|
+
# @param argv [Array<String>] the array of arguments passed from the command line.
|
16
|
+
def initialize(argv)
|
17
|
+
@argv = argv
|
18
|
+
@options = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Parses the command-line options using OptionParser.
|
22
|
+
# It expects the following options:
|
23
|
+
# - `-i`, `--input FILE`: The input OpenAPI file path (required).
|
24
|
+
# - `-r`, `--regex REGEX`: The regular expression used for filtering paths in the OpenAPI spec (required).
|
25
|
+
# - `-o`, `--output FILE`: The output file path to save the filtered result (optional).
|
26
|
+
# - `-h`, `--help`: Displays usage help.
|
27
|
+
def parse_options
|
28
|
+
OptionParser.new do |opts|
|
29
|
+
opts.banner = "Usage: openapi_slicer [options]"
|
30
|
+
|
31
|
+
opts.on("-i", "--input FILE", "Input OpenAPI file path") do |file|
|
32
|
+
options[:input_file] = file
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-r", "--regex REGEX", "Regex pattern for filtering") do |regex|
|
36
|
+
options[:regex] = regex
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("-o", "--output FILE", "Output file path") do |file|
|
40
|
+
options[:output_file] = file
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("-h", "--help", "Displays Help") do
|
44
|
+
puts opts
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
end.parse!(@argv)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Validates that the required options (`input_file` and `regex`) are provided.
|
51
|
+
# If any required option is missing, an error message is displayed, and the script exits with a non-zero status.
|
52
|
+
def validate_options
|
53
|
+
%i[input_file regex].each do |opt|
|
54
|
+
unless options[opt]
|
55
|
+
puts "Missing option: --#{opt.to_s.gsub('_', '-')}"
|
56
|
+
exit(1)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Runs the CLI by parsing options, validating them, and invoking the OpenAPI Slicer.
|
62
|
+
# Depending on whether an output file is specified, it either prints the filtered result to the console
|
63
|
+
# or writes it to the output file.
|
64
|
+
def run
|
65
|
+
parse_options
|
66
|
+
validate_options
|
67
|
+
|
68
|
+
slicer = OpenapiSlicer.new(file_path: options[:input_file])
|
69
|
+
if options[:output_file]
|
70
|
+
slicer.export(options[:regex], options[:output_file])
|
71
|
+
puts "File created: #{options[:output_file]}"
|
72
|
+
else
|
73
|
+
pp slicer.filter(options[:regex])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# If this script is run directly, execute the CLI.
|
79
|
+
if __FILE__ == $0
|
80
|
+
cli = OpenapiSlicerCLI.new(ARGV)
|
81
|
+
cli.run
|
82
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# test_open_api_slicer.rb
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require "test_helper"
|
6
|
+
require "json"
|
7
|
+
require "yaml"
|
8
|
+
require_relative "../../lib/openapi_slicer"
|
9
|
+
|
10
|
+
class OpenapiSlicerTest < Minitest::Test
|
11
|
+
def setup
|
12
|
+
# Create mock OpenAPI spec in both JSON and YAML format for testing
|
13
|
+
@mock_spec = {
|
14
|
+
"openapi" => "3.0.0",
|
15
|
+
"info" => { "title" => "Test API", "version" => "1.0.0" },
|
16
|
+
"paths" => {
|
17
|
+
"/pets" => {
|
18
|
+
"get" => {
|
19
|
+
"tags" => ["Pets"],
|
20
|
+
"responses" => {
|
21
|
+
"200" => {
|
22
|
+
"description" => "A list of pets",
|
23
|
+
"content" => {
|
24
|
+
"application/json" => {
|
25
|
+
"schema" => { "$ref" => "#/components/schemas/Pet" }
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
},
|
32
|
+
"/pets/{petId}" => {
|
33
|
+
"get" => {
|
34
|
+
"tags" => ["Pets"],
|
35
|
+
"parameters" => [
|
36
|
+
{ "$ref" => "#/components/parameters/PetId" }
|
37
|
+
],
|
38
|
+
"responses" => {
|
39
|
+
"200" => {
|
40
|
+
"description" => "A pet",
|
41
|
+
"content" => {
|
42
|
+
"application/json" => {
|
43
|
+
"schema" => { "$ref" => "#/components/schemas/Pet" }
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
},
|
50
|
+
"/pets/{petId}/health" => {
|
51
|
+
"get" => {
|
52
|
+
"tags" => ["Pets"],
|
53
|
+
"parameters" => [
|
54
|
+
{ "$ref" => "#/components/parameters/PetId" }
|
55
|
+
],
|
56
|
+
"responses" => {
|
57
|
+
"200" => {
|
58
|
+
"description" => "Pet health status",
|
59
|
+
"content" => {
|
60
|
+
"application/json" => {
|
61
|
+
"schema" => { "type" => "string" }
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
68
|
+
},
|
69
|
+
"components" => {
|
70
|
+
"schemas" => {
|
71
|
+
"Pet" => {
|
72
|
+
"type" => "object",
|
73
|
+
"properties" => {
|
74
|
+
"id" => { "type" => "integer" },
|
75
|
+
"name" => { "type" => "string" }
|
76
|
+
}
|
77
|
+
}
|
78
|
+
},
|
79
|
+
"parameters" => {
|
80
|
+
"PetId" => {
|
81
|
+
"name" => "petId",
|
82
|
+
"in" => "path",
|
83
|
+
"required" => true,
|
84
|
+
"schema" => { "type" => "integer" }
|
85
|
+
}
|
86
|
+
}
|
87
|
+
},
|
88
|
+
"tags" => [{ "name" => "Pets", "description" => "Operations about pets" }]
|
89
|
+
}
|
90
|
+
|
91
|
+
# Save the mock spec to files
|
92
|
+
File.write("test_spec.json", @mock_spec.to_json)
|
93
|
+
File.write("test_spec.yaml", @mock_spec.to_yaml)
|
94
|
+
end
|
95
|
+
|
96
|
+
def teardown
|
97
|
+
# Clean up the created files
|
98
|
+
File.delete("test_spec.json")
|
99
|
+
File.delete("test_spec.yaml")
|
100
|
+
File.delete("sliced_spec.json") if File.exist?("sliced_spec.json")
|
101
|
+
File.delete("sliced_spec.yaml") if File.exist?("sliced_spec.yaml")
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_initialize_raises_error_on_invalid_file_type
|
105
|
+
assert_raises(RuntimeError) do
|
106
|
+
::OpenapiSlicer.new(file_path: "invalid.txt")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_initialize_loads_valid_json_file
|
111
|
+
slicer = ::OpenapiSlicer.new(file_path: "test_spec.json")
|
112
|
+
assert_equal "3.0.0", slicer.spec["openapi"]
|
113
|
+
assert_equal "Test API", slicer.spec["info"]["title"]
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_initialize_loads_valid_yaml_file
|
117
|
+
slicer = ::OpenapiSlicer.new(file_path: "test_spec.yaml")
|
118
|
+
assert_equal "3.0.0", slicer.spec["openapi"]
|
119
|
+
assert_equal "Test API", slicer.spec["info"]["title"]
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_filter_slices_paths_and_dependencies_matching_regex
|
123
|
+
slicer = ::OpenapiSlicer.new(file_path: "test_spec.json")
|
124
|
+
result = slicer.filter(%r{^/pets})
|
125
|
+
|
126
|
+
assert_includes result["paths"], "/pets"
|
127
|
+
assert_includes result["paths"], "/pets/{petId}"
|
128
|
+
assert_includes result["paths"], "/pets/{petId}/health"
|
129
|
+
assert_includes result["components"]["schemas"], "Pet"
|
130
|
+
assert_includes result["components"]["parameters"], "PetId"
|
131
|
+
assert_includes result["tags"].map { |tag| tag["name"] }, "Pets"
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_filter_returns_only_nested_paths_under_pets_petid
|
135
|
+
slicer = ::OpenapiSlicer.new(file_path: "test_spec.json")
|
136
|
+
result = slicer.filter(%r{^/pets/\{petId\}})
|
137
|
+
|
138
|
+
assert_includes result["paths"], "/pets/{petId}"
|
139
|
+
assert_includes result["paths"], "/pets/{petId}/health"
|
140
|
+
assert_equal 2, result["paths"].size
|
141
|
+
assert_includes result["components"]["schemas"], "Pet"
|
142
|
+
assert_includes result["components"]["parameters"], "PetId"
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_filter_returns_empty_result_for_non_matching_paths
|
146
|
+
slicer = ::OpenapiSlicer.new(file_path: "test_spec.json")
|
147
|
+
result = slicer.filter(%r{^/nonexistent})
|
148
|
+
|
149
|
+
assert_empty result["paths"]
|
150
|
+
assert_empty result["components"]["schemas"]
|
151
|
+
assert_empty result["components"]["parameters"]
|
152
|
+
assert_empty result["tags"]
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_export_correctly_exports_filtered_spec_to_json
|
156
|
+
slicer = ::OpenapiSlicer.new(file_path: "test_spec.json")
|
157
|
+
slicer.export(%r{^/pets}, "sliced_spec.json")
|
158
|
+
|
159
|
+
sliced_spec = JSON.parse(File.read("sliced_spec.json"))
|
160
|
+
assert_includes sliced_spec["paths"], "/pets"
|
161
|
+
assert_includes sliced_spec["paths"], "/pets/{petId}"
|
162
|
+
assert_includes sliced_spec["paths"], "/pets/{petId}/health"
|
163
|
+
assert_includes sliced_spec["components"]["schemas"], "Pet"
|
164
|
+
assert_includes sliced_spec["components"]["parameters"], "PetId"
|
165
|
+
assert_includes sliced_spec["tags"].map { |tag| tag["name"] }, "Pets"
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_export_correctly_exports_filtered_spec_to_yaml
|
169
|
+
slicer = ::OpenapiSlicer.new(file_path: "test_spec.yaml")
|
170
|
+
slicer.export(%r{^/pets}, "sliced_spec.yaml")
|
171
|
+
|
172
|
+
sliced_spec = YAML.load_file("sliced_spec.yaml")
|
173
|
+
assert_includes sliced_spec["paths"], "/pets"
|
174
|
+
assert_includes sliced_spec["paths"], "/pets/{petId}"
|
175
|
+
assert_includes sliced_spec["paths"], "/pets/{petId}/health"
|
176
|
+
assert_includes sliced_spec["components"]["schemas"], "Pet"
|
177
|
+
assert_includes sliced_spec["components"]["parameters"], "PetId"
|
178
|
+
assert_includes sliced_spec["tags"].map { |tag| tag["name"] }, "Pets"
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'test_helper'
|
3
|
+
require_relative "../../scripts/openapi_slicer_cli"
|
4
|
+
|
5
|
+
class OpenapiSlicerCLITest < Minitest::Test
|
6
|
+
def setup
|
7
|
+
# Redirect stdout and stderr to capture outputs for assertions
|
8
|
+
@original_stdout = $stdout
|
9
|
+
$stdout = StringIO.new
|
10
|
+
@original_stderr = $stderr
|
11
|
+
$stderr = StringIO.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
# Restore stdout and stderr
|
16
|
+
$stdout = @original_stdout
|
17
|
+
$stderr = @original_stderr
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_missing_required_options
|
21
|
+
argv = []
|
22
|
+
cli = ::OpenapiSlicerCLI.new(argv)
|
23
|
+
# Expect the script to exit due to missing options
|
24
|
+
assert_raises(SystemExit) { cli.run }
|
25
|
+
output = $stdout.string
|
26
|
+
assert_match(/Missing option: --input-file/, output)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_help_option
|
30
|
+
argv = ['-h']
|
31
|
+
cli = ::OpenapiSlicerCLI.new(argv)
|
32
|
+
# Expect the script to exit after displaying help
|
33
|
+
assert_raises(SystemExit) { cli.run }
|
34
|
+
output = $stdout.string
|
35
|
+
assert_match(/Usage: openapi_slicer/, output)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_run_with_output_file
|
39
|
+
# Mock the OpenapiSlicer instance
|
40
|
+
slicer_mock = Minitest::Mock.new
|
41
|
+
slicer_mock.expect(:export, nil, ['users.*', 'output.yaml'])
|
42
|
+
OpenapiSlicer.stub(:new, slicer_mock) do
|
43
|
+
argv = ['-i', 'input.yaml', '-r', 'users.*', '-o', 'output.yaml']
|
44
|
+
cli = ::OpenapiSlicerCLI.new(argv)
|
45
|
+
cli.run
|
46
|
+
output = $stdout.string
|
47
|
+
assert_match(/File created: output.yaml/, output)
|
48
|
+
end
|
49
|
+
slicer_mock.verify
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_run_without_output_file
|
53
|
+
# Mock the OpenapiSlicer instance
|
54
|
+
slicer_mock = Minitest::Mock.new
|
55
|
+
slicer_mock.expect(:filter, { data: 'filtered_data' }, ['users.*'])
|
56
|
+
OpenapiSlicer.stub(:new, slicer_mock) do
|
57
|
+
argv = ['-i', 'input.yaml', '-r', 'users.*']
|
58
|
+
cli = ::OpenapiSlicerCLI.new(argv)
|
59
|
+
cli.run
|
60
|
+
output = $stdout.string
|
61
|
+
assert_match(/:data=>"filtered_data"/, output)
|
62
|
+
end
|
63
|
+
slicer_mock.verify
|
64
|
+
end
|
65
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: openapi_slicer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- TheScubaGeek
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-10-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
description: OpenapiSlicer allows you to slice OpenAPI specs, selecting paths and
|
28
|
+
their dependencies based on regular expressions.
|
29
|
+
email:
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- README.md
|
35
|
+
- lib/openapi_slicer.rb
|
36
|
+
- scripts/openapi_slicer_cli.rb
|
37
|
+
- test/lib/openapi_slicer_test.rb
|
38
|
+
- test/scripts/openapi_slicer_cli_test.rb
|
39
|
+
- test/test_helper.rb
|
40
|
+
homepage: https://rubygems.org/gems/openapi_slicer
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 3.0.0
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubygems_version: 3.5.11
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: A tool to slice OpenAPI specs based on regular expressions
|
63
|
+
test_files: []
|