swagcov 0.5.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10f7019116f2fb27f9bcf01ed15450bb26e715778722c26448571ed006fb1675
4
- data.tar.gz: 35d038789bbdb47d93720fa0ab5e07d95eefc92810145e083fb3076e6f151e13
3
+ metadata.gz: 1df02b05f23038aacedc8a990a47012d27f90f64f7185c689507face26acd8c4
4
+ data.tar.gz: 8d2e127ed1b1de208ef87fe6a8920fe505fbdceade1f430300b26e2bb6441d19
5
5
  SHA512:
6
- metadata.gz: 53e051e7b1c1b50b1cf23f0774fdd13aad99619463e7838ef8c5834d7e172fb5948e682813787ac22bfac8db64452dc4405ebc436d0266a99935915b012ebc90
7
- data.tar.gz: bc67b90db65336931ba6895ad5ee51f63ad3aea5855b2e44f53e05a6c7b7cc57746f483f5d2c24bb651841f437b4c6d5c09bfec77d419cb0cf5e15f7b119f53d
6
+ metadata.gz: 4faad26270ff8aceddad224fdb26b6fcd8c86eb0d1c2a75c8bff082aa829187a5d571b7aff037b4a8c5bc907c26c446d55a5807420b111f18abc5c7b475a445c
7
+ data.tar.gz: a8bbfa8964e97929d4e190353729e399e9f0bf59d78acef28084699e902be2689bf8e7bf2a2ea3289f10114040c10112204c0954da319da4c38971c6ea7f0c5e
data/CHANGELOG.md CHANGED
@@ -2,76 +2,116 @@
2
2
  ## main (unreleased)
3
3
  -
4
4
 
5
+ ## 0.7.0 (2025-04-18)
6
+ ### Enhancement
7
+ - Add support for Ruby 3.5 ([#98](https://github.com/smridge/swagcov/pull/98))
8
+ - Add rake task for auto-generation of `.swagcov_todo.yml` file ([#93](https://github.com/smridge/swagcov/pull/93), [#96](https://github.com/smridge/swagcov/pull/96))
9
+ ```shell
10
+ bundle exec rake swagcov:generate_todo
11
+ ```
12
+
13
+ ### Refactor
14
+ - Add `Swagcov.project_root` method ([#86](https://github.com/smridge/swagcov/pull/86))
15
+ - Separate coverage collection and output ([#83](https://github.com/smridge/swagcov/pull/83))
16
+ - Replace ActiveSupport string method with ruby string method ([#85](https://github.com/smridge/swagcov/pull/85))
17
+ - Replace `Swagcov::Coverage.new.report` with `Swagcov::Command::ReportCoverage.new.run` ([#89](https://github.com/smridge/swagcov/pull/89))
18
+ - Rename `Swagcov::Install` to `Swagcov::Command::GenerateDotfile` ([#88](https://github.com/smridge/swagcov/pull/88))
19
+ - Refactor `GenerateDotfile` to be consistent with `GenerateTodoFile` ([#97](https://github.com/smridge/swagcov/pull/97))
20
+
21
+ ### Fix
22
+ - Add exit code to install task + update messaging ([#87](https://github.com/smridge/swagcov/pull/87))
23
+ - Ignored path verb matching when duplicate path keys are present ([#92](https://github.com/smridge/swagcov/pull/92))
24
+ - Fix exit code error handling ([#95](https://github.com/smridge/swagcov/pull/95))
25
+
26
+ ### Development
27
+ - Add irb console for local development ([#94](https://github.com/smridge/swagcov/pull/94))
28
+
29
+ ## 0.6.0 (2025-04-09)
30
+ ### Fix
31
+ - Grammatical number for endpoint(s) count output ([#78](https://github.com/smridge/swagcov/pull/78))
32
+
33
+ ### Enhancement
34
+ - Add support for Rails 4.2 ([#72](https://github.com/smridge/swagcov/pull/72))
35
+
36
+ ### Refactor
37
+ - `BREAKING CHANGE`: `Swagcov::BadConfigurationError` is now `Swagcov::Errors::BadConfiguration` ([#74](https://github.com/smridge/swagcov/pull/74))
38
+ - `BREAKING CHANGE`: `Swagcov::VERSION` is now `Swagcov::Version::STRING` ([#77](https://github.com/smridge/swagcov/pull/77))
39
+ - Improve path matching processing for `ignore` and `only` routes ([#65](https://github.com/smridge/swagcov/pull/65))
40
+
41
+ ### Code Coverage
42
+ - Add test coverage reporting ([#68](https://github.com/smridge/swagcov/pull/68), [#69](https://github.com/smridge/swagcov/pull/69))
43
+
5
44
  ## 0.5.0 (2025-03-26)
6
45
  ### Enhancement
7
- - Add rake task for configuration installation ([#59](https://github.com/smridge/swagcov/pull/59))
8
- ```shell
9
- bundle exec rake swagcov:install
10
- ```
11
- - Extend `ignore` routes configuration to exclude only specific actions ([#60](https://github.com/smridge/swagcov/pull/60))
12
- ```yml
13
- routes:
14
- paths:
15
- ignore:
16
- - /v2/users # existing configuration that ignores all associated actions (verbs)
17
- - /v2/users/:id: # new option to extend to specific actions
18
- - GET
19
- ```
46
+ - Add rake task for configuration installation ([#59](https://github.com/smridge/swagcov/pull/59))
47
+ ```shell
48
+ bundle exec rake swagcov:install
49
+ ```
50
+ - Extend `ignore` routes configuration to exclude only specific actions ([#60](https://github.com/smridge/swagcov/pull/60))
51
+ ```yml
52
+ routes:
53
+ paths:
54
+ ignore:
55
+ - /v2/users # existing configuration that ignores all associated actions (verbs)
56
+ - /v2/users/:id: # new option to extend to specific actions
57
+ - GET
58
+ ```
20
59
 
21
60
  ## 0.4.1 (2025-03-18)
22
61
  ### Fix
23
62
  - Output width for better layout alignment ([#48](https://github.com/smridge/swagcov/pull/48))
24
63
 
25
64
  ### Code Coverage
26
- - Added official support for ruby 3.3 and 3.4 and rails 7.1, 7.2, 8.0. See [unit_tests.yml](/.github/workflows/unit_tests.yml) and [system_tests.yml](/.github/workflows/system_tests.yml) for detail
65
+ - Added official support for ruby 3.3 and 3.4 and rails 7.1, 7.2, 8.0. See [tests.yml](/.github/workflows/tests.yml) for detail
27
66
 
28
67
  ## 0.4.0 (2022-08-11)
29
68
  - Improve OpenAPI file processing ([#26](https://github.com/smridge/swagcov/pull/26))
30
69
 
31
70
  ## 0.3.0 (2022-02-21)
32
71
  ### Enhancement
33
- - Raise specific `Swagcov::BadConfigurationError` for malinformed yaml files ([#23](https://github.com/smridge/swagcov/pull/23))
72
+ - Raise specific `Swagcov::BadConfigurationError` for malformed yaml files ([#23](https://github.com/smridge/swagcov/pull/23))
34
73
 
35
74
  ### Security
36
- - Require Multi-Factor Authentication for RubyGems privileged operations ([#16](https://github.com/smridge/swagcov/pull/16))
75
+ - Require Multi-Factor Authentication for RubyGems privileged operations ([#16](https://github.com/smridge/swagcov/pull/16))
37
76
 
38
77
  ### Code Coverage
39
- - Add Sandbox Application and specs for Rails 5.1 ([#20](https://github.com/smridge/swagcov/pull/20))
40
- - Add specs for Rails 5.2 ([#7](https://github.com/smridge/swagcov/pull/7)), ([#14](https://github.com/smridge/swagcov/pull/14))
41
- - Add Sandbox Application and specs for Rails 6.0 ([#17](https://github.com/smridge/swagcov/pull/17))
42
- - Add Sandbox Application and specs for Rails 6.1 ([#22](https://github.com/smridge/swagcov/pull/22))
43
- - Add GitHub Actions to run specs ([#18](https://github.com/smridge/swagcov/pull/18))
44
- - Add GitHub CodeQL ([#13](https://github.com/smridge/swagcov/pull/13))
78
+ - Add Sandbox Application and specs for Rails 5.1 ([#20](https://github.com/smridge/swagcov/pull/20))
79
+ - Add specs for Rails 5.2 ([#7](https://github.com/smridge/swagcov/pull/7)), ([#14](https://github.com/smridge/swagcov/pull/14))
80
+ - Add Sandbox Application and specs for Rails 6.0 ([#17](https://github.com/smridge/swagcov/pull/17))
81
+ - Add Sandbox Application and specs for Rails 6.1 ([#22](https://github.com/smridge/swagcov/pull/22))
82
+ - Add GitHub Actions to run specs ([#18](https://github.com/smridge/swagcov/pull/18))
83
+ - Add GitHub CodeQL ([#13](https://github.com/smridge/swagcov/pull/13))
45
84
 
46
85
  ### Refactor
47
- - Move `SystemExit` to rake task for easier testing ([#24](https://github.com/smridge/swagcov/pull/24))
48
- - Reduce complexity when matching routes ([#15](https://github.com/smridge/swagcov/pull/15))
86
+ - Move `SystemExit` to rake task for easier testing ([#24](https://github.com/smridge/swagcov/pull/24))
87
+ - Reduce complexity when matching routes ([#15](https://github.com/smridge/swagcov/pull/15))
49
88
 
50
89
  ## 0.2.5 (2021-09-14)
51
90
  ### Fix
52
- - Matching routes against swagger paths. Previously, partial paths could result in a match ([#12](https://github.com/smridge/swagcov/pull/12))
91
+ - Matching routes against swagger paths. Previously, partial paths could result in a match ([#12](https://github.com/smridge/swagcov/pull/12))
53
92
 
54
93
  ## 0.2.4 (2021-04-30)
55
94
  ### Fix
56
- - If a route path does not start with "^" match the entire path ([#5](https://github.com/smridge/swagcov/pull/5))
95
+ - If a route path does not start with "^" match the entire path ([#5](https://github.com/smridge/swagcov/pull/5))
96
+
57
97
  ### Enhancement
58
- - Raise specific `Swagcov::BadConfigurationError` error if bad or missing configuration ([#5](https://github.com/smridge/swagcov/pull/5))
98
+ - Raise specific `Swagcov::BadConfigurationError` error if bad or missing configuration ([#5](https://github.com/smridge/swagcov/pull/5))
59
99
 
60
100
  ## 0.2.3 (2021-04-23)
61
101
  ### Fix
62
- - Exclude ActiveStorage and ActionMailer routes ([#3](https://github.com/smridge/swagcov/pull/3))
102
+ - Exclude ActiveStorage and ActionMailer routes ([#3](https://github.com/smridge/swagcov/pull/3))
63
103
 
64
104
  ## 0.2.2 (2021-04-22)
65
105
  ### Fix
66
- - Exclude Rails Internal Routes and Mounted Applications ([#2](https://github.com/smridge/swagcov/pull/2))
106
+ - Exclude Rails Internal Routes and Mounted Applications ([#2](https://github.com/smridge/swagcov/pull/2))
67
107
 
68
108
  ## 0.2.1 (2021-04-21)
69
109
  ### Fix
70
- - Exceptions caused by missing dependency for strings. ([#1](https://github.com/smridge/swagcov/pull/1))
110
+ - Exceptions caused by missing dependency for strings. ([#1](https://github.com/smridge/swagcov/pull/1))
71
111
 
72
112
  ## 0.2.0 (2021-04-20)
73
113
  ### Enhancement
74
- - Add Exit status to easily build a pass/fail into build pipeline
114
+ - Add Exit status to easily build a pass/fail into build pipeline
75
115
 
76
116
  ## 0.1.0 (2021-02-24)
77
117
  - Create Rake Task for checking documentation coverage (rails only)
data/README.md CHANGED
@@ -4,12 +4,37 @@
4
4
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop-hq/rubocop)
5
5
  [![GitHub License](https://img.shields.io/github/license/smridge/swagcov.svg)](https://github.com/smridge/swagcov/blob/main/LICENSE)
6
6
 
7
+ ![Tests Build](https://github.com/smridge/swagcov/actions/workflows/tests.yml/badge.svg)
8
+ ![Linting Build](https://github.com/smridge/swagcov/actions/workflows/linting.yml/badge.svg)
9
+ ![CodeQL Build](https://github.com/smridge/swagcov/actions/workflows/codeql-analysis.yml/badge.svg)
10
+ [![Coverage Status](https://coveralls.io/repos/github/smridge/swagcov/badge.svg?branch=main)](https://coveralls.io/github/smridge/swagcov?branch=main)
11
+
7
12
  See OpenAPI documentation coverage report for Rails Routes.
8
13
 
9
14
  ## Usages
10
15
  - See overview of different endpoints covered, missing and what you choose to ignore.
11
16
  - Add pass/fail to your build pipeline when missing Documentation Coverage.
12
17
 
18
+ | `rake task` | `rails console` | Description |
19
+ | :--- | :--- | :--- |
20
+ | `rake swagcov` | `Swagcov::Command::ReportCoverage.new.run` | Check documentation coverage |
21
+ | `rake swagcov:install` | `Swagcov::Command::GenerateDotfile.new.run` | Install required `.swagcov.yml` config file |
22
+ | `rake swagcov:generate_todo` | `Swagcov::Command::GenerateTodoFile.new.run` | Generate `.swagcov_todo.yml` |
23
+
24
+ ## Ruby and Rails Version Support
25
+ Versioning support from a test coverage perspective, see [tests.yml](/.github/workflows/tests.yml) for detail
26
+ | `ruby -v` | `rails 4.2` | `rails 5.0` | `rails 5.1` | `rails 5.2` | `rails 6.0` | `rails 6.1` | `rails 7.0` | `rails 7.1` | `rails 7.2` | `rails 8.0` |
27
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
28
+ | `2.5` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
29
+ | `2.6` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
30
+ | `2.7` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
31
+ | `3.0` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
32
+ | `3.1` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
33
+ | `3.2` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
34
+ | `3.3` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
35
+ | `3.4` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
36
+ | `3.5` | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
37
+
13
38
  ## Installation
14
39
  Add this line to your application's Gemfile:
15
40
  ```ruby
@@ -61,6 +86,8 @@ bundle exec rake swagcov:install
61
86
  - ^/v1
62
87
  ignore:
63
88
  - /v1/foobar/:token
89
+ - /v1/foobar:
90
+ - GET
64
91
  ```
65
92
 
66
93
  Execute:
@@ -68,7 +95,8 @@ Execute:
68
95
  bundle exec rake swagcov
69
96
  ```
70
97
 
71
- ### Example configurations and output from running `bundle exec rake swagcov` from the root of your Rails Application:
98
+ ## Examples
99
+ Configurations and output from running `bundle exec rake swagcov` from the root of your Rails Application
72
100
  - All Routes (minimal configuration):
73
101
  ```yml
74
102
  docs:
@@ -108,9 +136,6 @@ bundle exec rake swagcov
108
136
  ```
109
137
  <img src="https://raw.githubusercontent.com/smridge/swagcov/main/images/ignore-and-only-endpoints.png">
110
138
 
111
- ## TODO
112
- - Add autogeneration of ignore paths
113
-
114
139
  ## Contributing
115
140
  See [CONTRIBUTING.md](CONTRIBUTING.md) for detail
116
141
 
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swagcov
4
+ module Command
5
+ class GenerateDotfile
6
+ attr_reader :dotfile
7
+
8
+ def initialize basename: ::Swagcov::Dotfile::DEFAULT_CONFIG_FILE_NAME
9
+ @dotfile = ::Swagcov.project_root.join(basename)
10
+ end
11
+
12
+ def run
13
+ if ::File.exist?(@dotfile)
14
+ $stdout.puts "#{@dotfile.basename} already exists at #{@dotfile.dirname}"
15
+ return ::Swagcov::STATUS_ERROR
16
+ end
17
+
18
+ ::File.write(
19
+ dotfile,
20
+ <<~YAML
21
+ ## Required field:
22
+ # List your OpenAPI documentation files
23
+ docs:
24
+ paths:
25
+ - swagger.yaml
26
+
27
+ ## Optional fields:
28
+ # routes:
29
+ # paths:
30
+ # only:
31
+ # - ^/v2 # only track v2 endpoints
32
+ # ignore:
33
+ # - /v2/users # do not track certain endpoints
34
+ # - /v2/users/:id: # ignore only certain actions (verbs)
35
+ # - GET
36
+ YAML
37
+ )
38
+
39
+ $stdout.puts "created #{@dotfile.basename} at #{@dotfile.dirname}"
40
+
41
+ ::Swagcov::STATUS_SUCCESS
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swagcov
4
+ module Command
5
+ class GenerateTodoFile
6
+ def initialize basename: ::Swagcov::Dotfile::TODO_CONFIG_FILE_NAME,
7
+ data: ::Swagcov::Coverage.new(dotfile: ::Swagcov::Dotfile.new(skip_todo: true)).collect[:uncovered]
8
+ @dotfile = ::Swagcov.project_root.join(basename)
9
+ @data = data
10
+ end
11
+
12
+ def run
13
+ ::File.write(
14
+ @dotfile,
15
+ <<~YAML
16
+ # This configuration was auto generated
17
+ # The intent is to remove these route configurations as documentation is added
18
+ #{routes_yaml}
19
+ YAML
20
+ )
21
+
22
+ $stdout.puts "created #{@dotfile.basename} at #{@dotfile.dirname}"
23
+
24
+ ::Swagcov::STATUS_SUCCESS
25
+ end
26
+
27
+ private
28
+
29
+ def routes_yaml
30
+ return if routes.empty?
31
+
32
+ {
33
+ "routes" => {
34
+ "paths" => {
35
+ "ignore" => routes
36
+ }
37
+ }
38
+ }.to_yaml.strip
39
+ end
40
+
41
+ def routes
42
+ hash = {}
43
+
44
+ @data.each do |route|
45
+ hash[route[:path]] ? hash[route[:path]] << route[:verb] : hash[route[:path]] = [route[:verb]]
46
+ end
47
+
48
+ @routes ||= hash.map { |key, value| { key => value } }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swagcov
4
+ module Command
5
+ class ReportCoverage
6
+ def initialize data: nil
7
+ @data = data
8
+ end
9
+
10
+ def run
11
+ data = @data || ::Swagcov::Coverage.new.collect
12
+
13
+ ::Swagcov::Formatter::Console.new(data: data).run
14
+ rescue ::Swagcov::Errors::BadConfiguration => e
15
+ warn "#{e.class}: #{e.message}"
16
+ ::Swagcov::STATUS_ERROR
17
+ end
18
+ end
19
+ end
20
+ end
@@ -2,65 +2,57 @@
2
2
 
3
3
  module Swagcov
4
4
  class Coverage
5
- attr_reader :total, :covered, :ignored, :routes_not_covered, :routes_covered, :routes_ignored
6
-
7
- def initialize dotfile: Swagcov::Dotfile.new, routes: ::Rails.application.routes.routes
8
- @total = 0
9
- @covered = 0
10
- @ignored = 0
11
- @routes_not_covered = []
12
- @routes_covered = []
13
- @routes_ignored = []
5
+ attr_reader :dotfile
6
+
7
+ def initialize dotfile: ::Swagcov::Dotfile.new, routes: ::Rails.application.routes.routes
14
8
  @dotfile = dotfile
15
9
  @routes = routes
10
+ @data = {
11
+ covered: [],
12
+ ignored: [],
13
+ uncovered: [],
14
+ total_count: 0,
15
+ covered_count: 0,
16
+ ignored_count: 0,
17
+ uncovered_count: 0
18
+ }
16
19
  end
17
20
 
18
- def report
19
- collect_coverage
20
- routes_output(@routes_covered, "green")
21
- routes_output(@routes_ignored, "yellow")
22
- routes_output(@routes_not_covered, "red")
23
-
24
- final_output
25
-
26
- @total - @covered
27
- end
28
-
29
- private
30
-
31
- attr_reader :dotfile
32
-
33
- def collect_coverage
34
- openapi_files = ::Swagcov::OpenapiFiles.new(filepaths: dotfile.doc_paths)
21
+ def collect
22
+ openapi_files = ::Swagcov::OpenapiFiles.new(filepaths: dotfile.docs_config)
23
+ rails_version = ::Rails::VERSION::STRING
35
24
 
36
25
  @routes.each do |route|
37
26
  path = route.path.spec.to_s.chomp("(.:format)")
27
+ verb = rails_version > "5" ? route.verb : route.verb.inspect.gsub(%r{[$^/]}, "")
38
28
 
39
- next if third_party_route?(route, path)
29
+ next if third_party_route?(route, path, rails_version)
40
30
 
41
- if dotfile.ignore_path?(path, verb: route.verb)
42
- @ignored += 1
43
- @routes_ignored << { verb: route.verb, path: path, status: "ignored" }
31
+ if dotfile.ignore_path?(path, verb: verb)
32
+ update_data(:ignored, verb, path, "ignored")
44
33
  next
45
34
  end
46
35
 
47
36
  next if dotfile.only_path_mismatch?(path)
48
37
 
49
- @total += 1
38
+ @data[:total_count] += 1
50
39
 
51
- if (response_keys = openapi_files.find_response_keys(path: path, route_verb: route.verb))
52
- @covered += 1
53
- @routes_covered << { verb: route.verb, path: path, status: response_keys.join(" ") }
40
+ if (response_keys = openapi_files.find_response_keys(path: path, route_verb: verb))
41
+ update_data(:covered, verb, path, response_keys.join(" "))
54
42
  else
55
- @routes_not_covered << { verb: route.verb, path: path, status: "none" }
43
+ update_data(:uncovered, verb, path, "none")
56
44
  end
57
45
  end
46
+
47
+ @data
58
48
  end
59
49
 
60
- def third_party_route? route, path
50
+ private
51
+
52
+ def third_party_route? route, path, rails_version
61
53
  # https://github.com/rails/rails/blob/48f3c3e201b57a4832314b2c957a3b303e89bfea/actionpack/lib/action_dispatch/routing/inspector.rb#L105-L107
62
54
  # Skips route paths like ["/rails/info/properties", "/rails/info", "/rails/mailers"]
63
- route.internal ||
55
+ internal_rails_route?(route, rails_version) ||
64
56
 
65
57
  # Skips routes like "/sidekiq"
66
58
  route.verb.blank? ||
@@ -71,62 +63,17 @@ module Swagcov
71
63
  path.include?("/active_storage/") || path.include?("/action_mailbox/")
72
64
  end
73
65
 
74
- def routes_output routes, status_color
75
- routes.each do |route|
76
- $stdout.puts(
77
- format(
78
- "%<verb>10s %<path>-#{min_width(:path) + 1}s %<status>s",
79
- { verb: route[:verb], path: route[:path], status: route[:status].send(status_color) }
80
- )
81
- )
66
+ def internal_rails_route? route, rails_version
67
+ if rails_version > "5"
68
+ route.internal
69
+ else
70
+ ::ActionDispatch::Routing::RouteWrapper.new(route).internal?
82
71
  end
83
72
  end
84
73
 
85
- def min_width key
86
- strings =
87
- @routes_covered.map { |hash| hash[key] } +
88
- @routes_ignored.map { |hash| hash[key] } +
89
- @routes_not_covered.map { |hash| hash[key] }
90
-
91
- strings.max_by(&:length).size
92
- end
93
-
94
- def final_output
95
- $stdout.puts
96
- $stdout.puts(
97
- format(
98
- "OpenAPI documentation coverage %<percentage>.2f%% (%<covered>d/%<total>d)",
99
- { percentage: 100.0 * @covered / @total, covered: @covered, total: @total }
100
- )
101
- )
102
-
103
- $stdout.puts(
104
- format(
105
- "%<total>s endpoints ignored",
106
- { total: @ignored.to_s.yellow }
107
- )
108
- )
109
-
110
- $stdout.puts(
111
- format(
112
- "%<total>s endpoints checked",
113
- { total: @total.to_s.blue }
114
- )
115
- )
116
-
117
- $stdout.puts(
118
- format(
119
- "%<covered>s endpoints covered",
120
- { covered: @covered.to_s.green }
121
- )
122
- )
123
-
124
- $stdout.puts(
125
- format(
126
- "%<missing>s endpoints missing documentation",
127
- { missing: (@total - @covered).to_s.red }
128
- )
129
- )
74
+ def update_data key, verb, path, status
75
+ @data[:"#{key}_count"] += 1
76
+ @data[key] << { verb: verb, path: path, status: status }
130
77
  end
131
78
  end
132
79
  end
@@ -1,58 +1,67 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Swagcov
4
- class BadConfigurationError < RuntimeError
5
- end
6
-
7
4
  class Dotfile
8
5
  DEFAULT_CONFIG_FILE_NAME = ".swagcov.yml"
6
+ TODO_CONFIG_FILE_NAME = ".swagcov_todo.yml"
9
7
 
10
- def initialize pathname: ::Rails.root.join(DEFAULT_CONFIG_FILE_NAME)
11
- @dotfile = load_yaml(pathname)
8
+ def initialize basename: DEFAULT_CONFIG_FILE_NAME, todo_basename: TODO_CONFIG_FILE_NAME, skip_todo: false
9
+ @dotfile = load_yaml(basename, required: true)
12
10
 
13
- raise BadConfigurationError, "Invalid config file (#{DEFAULT_CONFIG_FILE_NAME})" unless valid?
11
+ raise ::Swagcov::Errors::BadConfiguration, "Invalid config file (#{DEFAULT_CONFIG_FILE_NAME})" unless valid?
12
+
13
+ @todo_file = load_yaml(todo_basename) unless skip_todo
14
+ @ignored_regex = path_config_regex(ignored_config)
15
+ @only_regex = path_config_regex(only_config)
14
16
  end
15
17
 
16
18
  def ignore_path? path, verb:
17
- ignore_all_path_actions = ignored_regex&.match?(path)
19
+ return false unless @ignored_config
20
+
21
+ ignore_all_path_actions = @ignored_regex.match?(path)
18
22
 
19
- ignored_verbs = ignored_config&.find { |config| config[path] }
23
+ ignored_verbs =
24
+ @ignored_config.select { |config| config[path]&.is_a?(::Array) }.map(&:values).flatten.map(&:downcase)
20
25
 
21
- return ignore_all_path_actions unless ignored_verbs.is_a?(::Hash)
26
+ return ignore_all_path_actions if ignored_verbs.empty?
22
27
 
23
- ignored_verbs.values.flatten.map(&:downcase).any?(verb.downcase)
28
+ ignored_verbs.any?(verb.downcase)
24
29
  end
25
30
 
26
31
  def only_path_mismatch? path
27
- only_regex && !only_regex.match?(path)
32
+ @only_config && !@only_regex.match?(path)
28
33
  end
29
34
 
30
- def doc_paths
31
- dotfile.dig("docs", "paths")
35
+ def docs_config
36
+ @docs_config ||= dotfile.dig("docs", "paths")
32
37
  end
33
38
 
34
- private
35
-
36
- attr_reader :dotfile
37
-
38
- def load_yaml pathname
39
- raise BadConfigurationError, "Missing config file (#{DEFAULT_CONFIG_FILE_NAME})" unless pathname.exist?
39
+ def ignored_config
40
+ dotfile_routes = dotfile.dig("routes", "paths", "ignore").to_a
41
+ todo_routes = todo_file ? todo_file.dig("routes", "paths", "ignore").to_a : []
42
+ routes = dotfile_routes + todo_routes
40
43
 
41
- YAML.load_file(pathname)
42
- rescue Psych::SyntaxError
43
- raise BadConfigurationError, "Malinformed config file (#{DEFAULT_CONFIG_FILE_NAME})"
44
+ @ignored_config ||= routes.empty? ? nil : routes
44
45
  end
45
46
 
46
- def ignored_regex
47
- @ignored_regex ||= path_config_regex(ignored_config)
47
+ def only_config
48
+ @only_config ||= dotfile.dig("routes", "paths", "only")
48
49
  end
49
50
 
50
- def ignored_config
51
- @ignored_config ||= dotfile.dig("routes", "paths", "ignore")
52
- end
51
+ private
52
+
53
+ attr_reader :dotfile, :todo_file
54
+
55
+ def load_yaml basename, required: false
56
+ pathname = ::Swagcov.project_root.join(basename)
57
+
58
+ raise ::Swagcov::Errors::BadConfiguration, "Missing config file (#{basename})" if !pathname.exist? && required
59
+
60
+ return unless pathname.exist?
53
61
 
54
- def only_regex
55
- @only_regex ||= path_config_regex(dotfile.dig("routes", "paths", "only"))
62
+ ::YAML.load_file(pathname)
63
+ rescue ::Psych::SyntaxError
64
+ raise ::Swagcov::Errors::BadConfiguration, "Malformed config file (#{basename})"
56
65
  end
57
66
 
58
67
  def path_config_regex path_config
@@ -63,7 +72,7 @@ module Swagcov
63
72
  if path.is_a?(::Hash)
64
73
  "^#{path.keys.first}$"
65
74
  else
66
- path.first == "^" ? path : "^#{path}$"
75
+ path.chr == "^" ? path : "^#{path}$"
67
76
  end
68
77
  end
69
78
 
@@ -71,7 +80,7 @@ module Swagcov
71
80
  end
72
81
 
73
82
  def valid?
74
- dotfile && doc_paths
83
+ dotfile && docs_config
75
84
  end
76
85
  end
77
86
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swagcov
4
+ module Errors
5
+ class BadConfiguration < ::RuntimeError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Swagcov
4
+ module Formatter
5
+ class Console
6
+ attr_reader :data
7
+
8
+ def initialize data: ::Swagcov::Coverage.new.collect
9
+ @data = data
10
+ end
11
+
12
+ def run
13
+ routes_output(data[:covered], "green")
14
+ routes_output(data[:ignored], "yellow")
15
+ routes_output(data[:uncovered], "red")
16
+ final_output
17
+
18
+ data[:uncovered_count].zero? ? ::Swagcov::STATUS_SUCCESS : ::Swagcov::STATUS_OFFENSES
19
+ end
20
+
21
+ private
22
+
23
+ def routes_output routes, status_color
24
+ routes.each do |route|
25
+ $stdout.puts(
26
+ format(
27
+ "%<verb>10s %<path>-#{min_width(:path) + 1}s %<status>s",
28
+ { verb: route[:verb], path: route[:path], status: route[:status].send(status_color) }
29
+ )
30
+ )
31
+ end
32
+ end
33
+
34
+ def min_width key
35
+ strings =
36
+ data[:covered].map { |hash| hash[key] } +
37
+ data[:ignored].map { |hash| hash[key] } +
38
+ data[:uncovered].map { |hash| hash[key] }
39
+
40
+ strings.max_by(&:length).size
41
+ end
42
+
43
+ def final_output
44
+ $stdout.puts
45
+ $stdout.puts(
46
+ format(
47
+ "OpenAPI documentation coverage %<percentage>.2f%% (%<covered>d/%<total>d)",
48
+ {
49
+ percentage: 100.0 * data[:covered_count] / data[:total_count],
50
+ covered: data[:covered_count],
51
+ total: data[:total_count]
52
+ }
53
+ )
54
+ )
55
+
56
+ count_output
57
+ end
58
+
59
+ def count_output
60
+ {
61
+ ignored: "yellow",
62
+ total: "blue",
63
+ covered: "green",
64
+ uncovered: "red"
65
+ }.each do |key, color|
66
+ count = data[:"#{key}_count"]
67
+
68
+ $stdout.puts(
69
+ format(
70
+ "%<status>s #{key} #{count == 1 ? 'endpoint' : 'endpoints'}",
71
+ { status: count.to_s.send(color) }
72
+ )
73
+ )
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -9,7 +9,7 @@ module Swagcov
9
9
 
10
10
  def find_response_keys path:, route_verb:
11
11
  # replace :id with {id}
12
- regex = Regexp.new("^#{path.gsub(%r{:[^/]+}, '\\{[^/]+\\}')}?$")
12
+ regex = ::Regexp.new("^#{path.gsub(%r{:[^/]+}, '\\{[^/]+\\}')}?$")
13
13
 
14
14
  matching_paths_key = @openapi_paths.keys.grep(regex).first
15
15
  matching_request_method_key = @openapi_paths.dig(matching_paths_key, route_verb.downcase)
@@ -20,15 +20,15 @@ module Swagcov
20
20
  private
21
21
 
22
22
  def load_yamls
23
- Dir.glob(@filepaths).reduce({}) do |hash, filepath|
23
+ ::Dir.glob(@filepaths).reduce({}) do |hash, filepath|
24
24
  hash.merge(load_yaml(filepath))
25
25
  end
26
26
  end
27
27
 
28
28
  def load_yaml filepath
29
- YAML.load_file(filepath)["paths"]
30
- rescue Psych::SyntaxError
31
- raise BadConfigurationError, "Malinformed openapi file (#{filepath})"
29
+ ::YAML.load_file(filepath)["paths"]
30
+ rescue ::Psych::SyntaxError
31
+ raise ::Swagcov::Errors::BadConfiguration, "Malformed openapi file (#{filepath})"
32
32
  end
33
33
  end
34
34
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Swagcov
4
- VERSION = "0.5.0"
4
+ module Version
5
+ STRING = "0.7.0"
6
+ end
5
7
  end
data/lib/swagcov.rb CHANGED
@@ -1,16 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "swagcov/version"
4
-
5
- if defined?(Rails)
6
- require "swagcov/railtie"
7
- require "swagcov/dotfile"
8
- require "swagcov/coverage"
9
- require "swagcov/openapi_files"
10
- require "swagcov/install"
11
- end
3
+ require "rails"
12
4
 
5
+ require "swagcov/command/generate_dotfile"
6
+ require "swagcov/command/generate_todo_file"
7
+ require "swagcov/command/report_coverage"
13
8
  require "swagcov/core_ext/string"
9
+ require "swagcov/formatter/console"
10
+ require "swagcov/coverage"
11
+ require "swagcov/dotfile"
12
+ require "swagcov/errors"
13
+ require "swagcov/openapi_files"
14
+ require "swagcov/railtie"
15
+ require "swagcov/version"
14
16
 
15
17
  module Swagcov
18
+ module_function
19
+
20
+ STATUS_SUCCESS = 0
21
+ STATUS_OFFENSES = 1
22
+ STATUS_ERROR = 2
23
+
24
+ def project_root
25
+ ::Rails.root || ::Pathname.new(::FileUtils.pwd)
26
+ end
16
27
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :swagcov do
4
+ desc "generate todo config file"
5
+ task generate_todo: :environment do
6
+ exit Swagcov::Command::GenerateTodoFile.new.run
7
+ end
8
+ end
@@ -3,6 +3,6 @@
3
3
  namespace :swagcov do
4
4
  desc "create config file"
5
5
  task install: :environment do
6
- Swagcov::Install.new.generate_dotfile
6
+ exit Swagcov::Command::GenerateDotfile.new.run
7
7
  end
8
8
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  desc "check documentation coverage for endpoints"
4
4
  task swagcov: :environment do
5
- exit Swagcov::Coverage.new.report
5
+ exit Swagcov::Command::ReportCoverage.new.run
6
6
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swagcov
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sarah Ridge
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-26 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '5'
18
+ version: '4.2'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '5'
25
+ version: '4.2'
26
26
  email:
27
27
  - sarahmarie@hey.com
28
28
  executables: []
@@ -34,15 +34,20 @@ files:
34
34
  - README.md
35
35
  - Rakefile
36
36
  - lib/swagcov.rb
37
+ - lib/swagcov/command/generate_dotfile.rb
38
+ - lib/swagcov/command/generate_todo_file.rb
39
+ - lib/swagcov/command/report_coverage.rb
37
40
  - lib/swagcov/core_ext/string.rb
38
41
  - lib/swagcov/coverage.rb
39
42
  - lib/swagcov/dotfile.rb
40
- - lib/swagcov/install.rb
43
+ - lib/swagcov/errors.rb
44
+ - lib/swagcov/formatter/console.rb
41
45
  - lib/swagcov/openapi_files.rb
42
46
  - lib/swagcov/railtie.rb
43
47
  - lib/swagcov/version.rb
48
+ - lib/tasks/swagcov.rake
49
+ - lib/tasks/swagcov/generate_todo.rake
44
50
  - lib/tasks/swagcov/install.rake
45
- - lib/tasks/swagcove.rake
46
51
  homepage: https://github.com/smridge/swagcov
47
52
  licenses:
48
53
  - MIT
@@ -66,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
71
  - !ruby/object:Gem::Version
67
72
  version: '0'
68
73
  requirements: []
69
- rubygems_version: 3.6.6
74
+ rubygems_version: 3.6.8
70
75
  specification_version: 4
71
76
  summary: OpenAPI documentation coverage for Rails Routes
72
77
  test_files: []
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Swagcov
4
- class Install
5
- attr_reader :dotfile
6
-
7
- def initialize pathname: ::Rails.root.join(::Swagcov::Dotfile::DEFAULT_CONFIG_FILE_NAME).to_s
8
- @dotfile = pathname
9
- end
10
-
11
- def generate_dotfile
12
- if ::File.exist?(dotfile)
13
- $stdout.puts "#{dotfile} already exists"
14
- return
15
- end
16
-
17
- ::File.write(
18
- dotfile,
19
- <<~YAML
20
- ## Required field:
21
- # List your OpenAPI documentation files
22
- docs:
23
- paths:
24
- - swagger.yaml
25
-
26
- ## Optional fields:
27
- # routes:
28
- # paths:
29
- # only:
30
- # - ^/v2 # only track v2 endpoints
31
- # ignore:
32
- # - /v2/users # do not track certain endpoints
33
- # - /v2/users/:id: # ignore only certain actions (verbs)
34
- # - GET
35
- YAML
36
- )
37
-
38
- $stdout.puts "created #{::Swagcov::Dotfile::DEFAULT_CONFIG_FILE_NAME}"
39
- end
40
- end
41
- end