yapi_check 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 20968fe1514e86325f979ba7041f82c75db592f74905978e7876644b3d217fd1
4
+ data.tar.gz: c65b430731996baf54cabe776b6d497933052d7cbcb30fd30728bf2705e2bb13
5
+ SHA512:
6
+ metadata.gz: 75de9664e3764fb17c641094430000afff2cf1e8a953283dd7c8cba9eba650d090719746f2af0be324f6ae79e457a9cdd0293e98e6c6706b95426c81e81ab490
7
+ data.tar.gz: f9c0be6dc4b99c10ad8937b72e235b26e942c925e0e60ac91b6e151675fe23e6320c8b2d489187c5cd39746ed12630b2f58a542f4b803e54b57b22e22a949507
data/.rubocop.yml ADDED
@@ -0,0 +1,61 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ NewCops: enable
4
+ Exclude:
5
+ - db/schema.rb
6
+ - bin/**
7
+ SuggestExtensions: false
8
+ Style/RedundantReturn:
9
+ Enabled: false
10
+ Style/NumericPredicate:
11
+ Enabled: false
12
+ Style/FrozenStringLiteralComment:
13
+ Enabled: false
14
+ Style/Documentation:
15
+ Enabled: false
16
+ Style/AsciiComments:
17
+ Enabled: false
18
+ Style/ClassAndModuleChildren:
19
+ Enabled: false
20
+ Layout/LineLength:
21
+ Enabled: false
22
+ Metrics/MethodLength:
23
+ Max: 150
24
+ Metrics/CyclomaticComplexity:
25
+ Max: 60
26
+ Metrics/ClassLength:
27
+ Max: 410
28
+ Metrics/BlockLength:
29
+ Enabled: false
30
+ Naming/AccessorMethodName:
31
+ Enabled: false
32
+ Metrics/AbcSize:
33
+ Enabled: false
34
+ Metrics/ParameterLists:
35
+ Max: 14
36
+ Style/FormatStringToken:
37
+ Enabled: false
38
+ Style/FormatString:
39
+ Enabled: false
40
+ Naming/VariableNumber:
41
+ Enabled: false
42
+ Style/GuardClause:
43
+ Enabled: false
44
+ Metrics/PerceivedComplexity:
45
+ Enabled: false
46
+ Style/ConditionalAssignment:
47
+ Enabled: false
48
+ Metrics/BlockNesting:
49
+ Max: 4
50
+ Style/CaseLikeIf:
51
+ Enabled: false
52
+ Lint/SuppressedException:
53
+ Enabled: false
54
+ Style/NumericLiterals:
55
+ Enabled: false
56
+ Lint/DuplicateBranch:
57
+ Enabled: false
58
+ Style/HashSyntax:
59
+ Enabled: false
60
+ Style/WordArray:
61
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Released]
2
+
3
+ ## [1.0.0] - 2024-03-13
4
+
5
+ Release first version.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in yapi_check.gemspec
4
+ gemspec
5
+
6
+ gem 'rake', '~> 13.0'
7
+
8
+ gem 'minitest', '~> 5.0'
9
+
10
+ gem 'rubocop', '~> 1.21'
data/Gemfile.lock ADDED
@@ -0,0 +1,242 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ yapi_check (1.0.0)
5
+ http (>= 3.0)
6
+ method_source (~> 1.0)
7
+ rails (>= 5.2)
8
+ railties (>= 4.0)
9
+ rake
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ actioncable (7.1.3.2)
15
+ actionpack (= 7.1.3.2)
16
+ activesupport (= 7.1.3.2)
17
+ nio4r (~> 2.0)
18
+ websocket-driver (>= 0.6.1)
19
+ zeitwerk (~> 2.6)
20
+ actionmailbox (7.1.3.2)
21
+ actionpack (= 7.1.3.2)
22
+ activejob (= 7.1.3.2)
23
+ activerecord (= 7.1.3.2)
24
+ activestorage (= 7.1.3.2)
25
+ activesupport (= 7.1.3.2)
26
+ mail (>= 2.7.1)
27
+ net-imap
28
+ net-pop
29
+ net-smtp
30
+ actionmailer (7.1.3.2)
31
+ actionpack (= 7.1.3.2)
32
+ actionview (= 7.1.3.2)
33
+ activejob (= 7.1.3.2)
34
+ activesupport (= 7.1.3.2)
35
+ mail (~> 2.5, >= 2.5.4)
36
+ net-imap
37
+ net-pop
38
+ net-smtp
39
+ rails-dom-testing (~> 2.2)
40
+ actionpack (7.1.3.2)
41
+ actionview (= 7.1.3.2)
42
+ activesupport (= 7.1.3.2)
43
+ nokogiri (>= 1.8.5)
44
+ racc
45
+ rack (>= 2.2.4)
46
+ rack-session (>= 1.0.1)
47
+ rack-test (>= 0.6.3)
48
+ rails-dom-testing (~> 2.2)
49
+ rails-html-sanitizer (~> 1.6)
50
+ actiontext (7.1.3.2)
51
+ actionpack (= 7.1.3.2)
52
+ activerecord (= 7.1.3.2)
53
+ activestorage (= 7.1.3.2)
54
+ activesupport (= 7.1.3.2)
55
+ globalid (>= 0.6.0)
56
+ nokogiri (>= 1.8.5)
57
+ actionview (7.1.3.2)
58
+ activesupport (= 7.1.3.2)
59
+ builder (~> 3.1)
60
+ erubi (~> 1.11)
61
+ rails-dom-testing (~> 2.2)
62
+ rails-html-sanitizer (~> 1.6)
63
+ activejob (7.1.3.2)
64
+ activesupport (= 7.1.3.2)
65
+ globalid (>= 0.3.6)
66
+ activemodel (7.1.3.2)
67
+ activesupport (= 7.1.3.2)
68
+ activerecord (7.1.3.2)
69
+ activemodel (= 7.1.3.2)
70
+ activesupport (= 7.1.3.2)
71
+ timeout (>= 0.4.0)
72
+ activestorage (7.1.3.2)
73
+ actionpack (= 7.1.3.2)
74
+ activejob (= 7.1.3.2)
75
+ activerecord (= 7.1.3.2)
76
+ activesupport (= 7.1.3.2)
77
+ marcel (~> 1.0)
78
+ activesupport (7.1.3.2)
79
+ base64
80
+ bigdecimal
81
+ concurrent-ruby (~> 1.0, >= 1.0.2)
82
+ connection_pool (>= 2.2.5)
83
+ drb
84
+ i18n (>= 1.6, < 2)
85
+ minitest (>= 5.1)
86
+ mutex_m
87
+ tzinfo (~> 2.0)
88
+ addressable (2.8.6)
89
+ public_suffix (>= 2.0.2, < 6.0)
90
+ ast (2.4.2)
91
+ base64 (0.2.0)
92
+ bigdecimal (3.1.6)
93
+ builder (3.2.4)
94
+ concurrent-ruby (1.2.3)
95
+ connection_pool (2.4.1)
96
+ crass (1.0.6)
97
+ date (3.3.4)
98
+ domain_name (0.6.20240107)
99
+ drb (2.2.1)
100
+ erubi (1.12.0)
101
+ ffi (1.16.3)
102
+ ffi-compiler (1.0.1)
103
+ ffi (>= 1.0.0)
104
+ rake
105
+ globalid (1.2.1)
106
+ activesupport (>= 6.1)
107
+ http (5.2.0)
108
+ addressable (~> 2.8)
109
+ base64 (~> 0.1)
110
+ http-cookie (~> 1.0)
111
+ http-form_data (~> 2.2)
112
+ llhttp-ffi (~> 0.5.0)
113
+ http-cookie (1.0.5)
114
+ domain_name (~> 0.5)
115
+ http-form_data (2.3.0)
116
+ i18n (1.14.3)
117
+ concurrent-ruby (~> 1.0)
118
+ racc (~> 1.7)
119
+ io-console (0.7.2)
120
+ irb (1.11.2)
121
+ rdoc
122
+ reline (>= 0.4.2)
123
+ json (2.7.1)
124
+ language_server-protocol (3.17.0.3)
125
+ llhttp-ffi (0.5.0)
126
+ ffi-compiler (~> 1.0)
127
+ rake (~> 13.0)
128
+ loofah (2.22.0)
129
+ crass (~> 1.0.2)
130
+ nokogiri (>= 1.12.0)
131
+ mail (2.8.1)
132
+ mini_mime (>= 0.1.1)
133
+ net-imap
134
+ net-pop
135
+ net-smtp
136
+ marcel (1.0.4)
137
+ method_source (1.0.0)
138
+ mini_mime (1.1.5)
139
+ minitest (5.22.2)
140
+ mutex_m (0.2.0)
141
+ net-imap (0.4.10)
142
+ date
143
+ net-protocol
144
+ net-pop (0.1.2)
145
+ net-protocol
146
+ net-protocol (0.2.2)
147
+ timeout
148
+ net-smtp (0.4.0.1)
149
+ net-protocol
150
+ nio4r (2.7.0)
151
+ nokogiri (1.16.2-x86_64-darwin)
152
+ racc (~> 1.4)
153
+ parallel (1.24.0)
154
+ parser (3.3.0.5)
155
+ ast (~> 2.4.1)
156
+ racc
157
+ psych (5.1.2)
158
+ stringio
159
+ public_suffix (5.0.4)
160
+ racc (1.7.3)
161
+ rack (3.0.9.1)
162
+ rack-session (2.0.0)
163
+ rack (>= 3.0.0)
164
+ rack-test (2.1.0)
165
+ rack (>= 1.3)
166
+ rackup (2.1.0)
167
+ rack (>= 3)
168
+ webrick (~> 1.8)
169
+ rails (7.1.3.2)
170
+ actioncable (= 7.1.3.2)
171
+ actionmailbox (= 7.1.3.2)
172
+ actionmailer (= 7.1.3.2)
173
+ actionpack (= 7.1.3.2)
174
+ actiontext (= 7.1.3.2)
175
+ actionview (= 7.1.3.2)
176
+ activejob (= 7.1.3.2)
177
+ activemodel (= 7.1.3.2)
178
+ activerecord (= 7.1.3.2)
179
+ activestorage (= 7.1.3.2)
180
+ activesupport (= 7.1.3.2)
181
+ bundler (>= 1.15.0)
182
+ railties (= 7.1.3.2)
183
+ rails-dom-testing (2.2.0)
184
+ activesupport (>= 5.0.0)
185
+ minitest
186
+ nokogiri (>= 1.6)
187
+ rails-html-sanitizer (1.6.0)
188
+ loofah (~> 2.21)
189
+ nokogiri (~> 1.14)
190
+ railties (7.1.3.2)
191
+ actionpack (= 7.1.3.2)
192
+ activesupport (= 7.1.3.2)
193
+ irb
194
+ rackup (>= 1.0.0)
195
+ rake (>= 12.2)
196
+ thor (~> 1.0, >= 1.2.2)
197
+ zeitwerk (~> 2.6)
198
+ rainbow (3.1.1)
199
+ rake (13.1.0)
200
+ rdoc (6.6.2)
201
+ psych (>= 4.0.0)
202
+ regexp_parser (2.9.0)
203
+ reline (0.4.3)
204
+ io-console (~> 0.5)
205
+ rexml (3.2.6)
206
+ rubocop (1.60.2)
207
+ json (~> 2.3)
208
+ language_server-protocol (>= 3.17.0)
209
+ parallel (~> 1.10)
210
+ parser (>= 3.3.0.2)
211
+ rainbow (>= 2.2.2, < 4.0)
212
+ regexp_parser (>= 1.8, < 3.0)
213
+ rexml (>= 3.2.5, < 4.0)
214
+ rubocop-ast (>= 1.30.0, < 2.0)
215
+ ruby-progressbar (~> 1.7)
216
+ unicode-display_width (>= 2.4.0, < 3.0)
217
+ rubocop-ast (1.31.1)
218
+ parser (>= 3.3.0.4)
219
+ ruby-progressbar (1.13.0)
220
+ stringio (3.1.0)
221
+ thor (1.3.1)
222
+ timeout (0.4.1)
223
+ tzinfo (2.0.6)
224
+ concurrent-ruby (~> 1.0)
225
+ unicode-display_width (2.5.0)
226
+ webrick (1.8.1)
227
+ websocket-driver (0.7.6)
228
+ websocket-extensions (>= 0.1.0)
229
+ websocket-extensions (0.1.5)
230
+ zeitwerk (2.6.13)
231
+
232
+ PLATFORMS
233
+ x86_64-darwin
234
+
235
+ DEPENDENCIES
236
+ minitest (~> 5.0)
237
+ rake (~> 13.0)
238
+ rubocop (~> 1.21)
239
+ yapi_check!
240
+
241
+ BUNDLED WITH
242
+ 2.5.6
data/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # YapiCheck
2
+
3
+ [en](./README.md) | [zh-CN](./README.zh-CN.md)
4
+
5
+ > YAPI is an excellent tool for managing API documentation, but maintaining consistency between the API documentation and code can be challenging. Instead of relying on manual recognition, let this tool automate the process for you.
6
+
7
+ This is a tool for checking the consistency and conformity between YAPI API documentation and code, ensuring strong consistency and adherence to standards.
8
+
9
+ ## Key Features
10
+
11
+ ### 1. Request Parameter Check
12
+ + Scans the source code of actions, captures the lucky_param parameters of actions, and matches them with the input metadata of YAPI documentation.
13
+ + Supports mandatory and optional validation
14
+ + JSON parameters support type validation, including String, Float, and Integer types
15
+ + Parameter checks require each field to be on a separate line, like:
16
+ ```ruby
17
+ required(:name, :String) # Name
18
+ required(:phone, :Integer) # Phone number
19
+ required(:height, :Float) # Height
20
+ optional(:email, :Email) # Email
21
+ optional(:tags, :ArrayJSON) # Tags
22
+ ```
23
+ ### 2. Response Parameter Check
24
+ + Matches Jbuilder source code with the output metadata of YAPI documentation.
25
+ + Supports sub-views
26
+ + Supports paths relative to the root directory or the current file directory
27
+ + Action names must match the Jbuilder file names
28
+ + Output requires each returned field to be on a separate line
29
+ + Output requires mandatory type conversion:
30
+ + Strings: Recognized using to_s, json.attr_name ... to_s
31
+ + Integers: Recognized using to_i, json.attr_name ... to_i
32
+ + Floats: Recognized using to_f, json.attr_name ... to_f
33
+ + Arrays: Recognized using to_a or do, json.attr_name ... each do
34
+ + Hashes: Recognized using to_h or do, json.attr_name each do
35
+ ```ruby
36
+ json.user do
37
+ json.name user.name.to_s
38
+ json.phone user.phone.to_i
39
+ json.height user.height.to_f
40
+ json.email user.email.to_s
41
+ json.tags user.tags.to_a
42
+ end
43
+ ```
44
+ + Does not support boolean types; use integers instead
45
+ + Attributes must be included in the data object, such as: {"code":200, "data":{}}
46
+ ### 3. Batch Check
47
+ + Supports checking all interfaces of a single project
48
+ + Supports checking a single interface
49
+ + Supports checking interfaces under a single tag
50
+
51
+ ## Usage Instructions
52
+
53
+ 1. Add the following line to your Rails project's Gemfile:
54
+
55
+ ```ruby
56
+ gem 'yapi_check'
57
+ ```
58
+
59
+ 2. Run bundle install:
60
+
61
+ ```shell
62
+ $ bundle install
63
+ ```
64
+
65
+ 3. Insert the following code into your project's Rakefile:
66
+
67
+ ```ruby
68
+ require 'yapi_check/tasks'
69
+ ENV['YAPI_PROJECT_TOKEN'] = 'THE_TOKEN_FROM_YOUR_YAPI_PROJECT' # YAPI project unique identifier (required), recommended to configure in the project. You can find this token in the YAPI project settings.
70
+ ENV['YAPI_PROJECT_DOMAIN'] = 'http://YOUR_YAPI_WEBSITE' # YAPI project domain, recommended to configure on your local machine to avoid changes in ~/.bashrc or ~/.zshrc
71
+ ENV['YAPI_API_PREFIX'] = '' # YAPI project API prefix, can be set to '', defaults to /api/v1 if not set
72
+ ```
73
+
74
+ 4. Run YAPI check:
75
+ Note: If using zsh, install the rake plugin in ~/.zshrc to support special syntax plugins=(... rake)
76
+
77
+ ```shell
78
+ # Full check
79
+ $ bundle exec rake yapi:check
80
+ # or
81
+ $ rails yapi:check
82
+
83
+ # Check a single interface
84
+ $ bundle exec rake yapi:check[/healthy_lives/exit_healthy_life]
85
+ # or
86
+ $ noglob rails yapi:check[/healthy_lives/exit_healthy_life]
87
+ # or
88
+ $ rails 'yapi:check[/healthy_lives/exit_healthy_life]'
89
+
90
+ # Check a single tag
91
+ $ bundle exec rake yapi:check[,3.3.0]
92
+ # or
93
+ $ noglob rails yapi:check[,3.3.0]
94
+ # or
95
+ $ rails 'yapi:check[,3.3.0]'
96
+ ```
97
+
98
+ ## Notes
99
+
100
+ This tool is only for Rails projects. Request parameter checks heavily depend on [lucky_param](https://github.com/shootingfly/lucky_param), and response parameter checks heavily depend on [Jbuilder](https://github.com/rails/jbuilder).
101
+
102
+ ## Contributing
103
+
104
+ 1. Fork it ( https://github.com/shootingfly/yapi_check/fork )
105
+ 2. Create your feature branch (git checkout -b my-new-feature)
106
+ 3. Make your changes
107
+ 4. Run `ruby test/yapi_check_test.rb` to run the tests
108
+ 5. Commit your changes (git commit -am 'Add some feature')
109
+ 6. Push to the branch (git push origin my-new-feature)
110
+ 7. Create a new Pull Request
111
+
112
+ ## License
113
+
114
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/README.zh-CN.md ADDED
@@ -0,0 +1,110 @@
1
+ # YapiCheck
2
+
3
+ [en](./README.md) | [zh-CN](./README.zh-CN.md)
4
+
5
+ > YAPI是一款非常棒的接口文档管理工具, 但保持接口文档与代码一致性是一个难题, 与其通过人眼识别, 不如让工具自动化帮你处理
6
+
7
+ 这是一款用于核对YAPI接口文档与代码的工具, 具有强一致性与规范性.
8
+
9
+ ## 主要功能
10
+ ### 1. 请求参数检查
11
+ + 通过扫描action的源代码,并捕获action的lucky_param参数,与YAPI文档的入参元数据进行匹配
12
+ + 支持必填性与非必填性验证
13
+ + 其中JSON参数支持类型验证, 支持判断String, Float, Integer三种类型
14
+ + 参数检测要求每个字段都单独成行, 如
15
+ ```ruby
16
+ required(:name, :String) # 姓名
17
+ required(:phone, :Integer) # 手机号
18
+ required(:height, :Float) # 身高
19
+ optional(:email, :Email) # 邮箱
20
+ optional(:tags, :ArrayJSON) # 标签
21
+ ```
22
+ ### 2. 响应参数检查
23
+ + 通过扫描jbuilder源码,与YAPI文档的出参元数据进行匹配
24
+ + 支持子视图
25
+ + 支持使用相对根目录或相对当前文件目录的路径
26
+ + action名称必须与jbuilder文件名保持一致
27
+ + 出参要求每个返回的字段都单独成行
28
+ + 出参要求强制类型转化:
29
+ + 字符串: 使用to_s识别, json.attr_name ... to_s识别
30
+ + 整型: 使用to_i识别, json.attr_name ... to_i识别
31
+ + 浮点数: 使用to_f识别, json.attr_name ... to_f识别
32
+ + 数组: 使用to_a识别或do识别, json.attr_name ... each do识别
33
+ + 字典: 使用to_h识别或do识别, json.attr_name each do识别
34
+ ```ruby
35
+ json.user do
36
+ json.name user.name.to_s
37
+ json.phone user.phone.to_i
38
+ json.height user.height.to_f
39
+ json.email user.email.to_s
40
+ json.tags user.tags.to_a
41
+ end
42
+ ```
43
+ + 不支持布尔型, 请用整型代替
44
+ + 属性必须包含在data对象中,如:{"code":200, "data":{}}
45
+ ### 3. 批量检查
46
+ + 支持检查单个项目的所有接口
47
+ + 支持仅检查单个接口
48
+ + 支持仅检查单个标签下的接口
49
+
50
+ ## 使用说明
51
+
52
+ 1. 添加本行在Rails项目的Gemfile中
53
+
54
+ ```ruby
55
+ gem 'yapi_check'
56
+ ```
57
+
58
+ 2. 执行bundle install
59
+ ```shell
60
+ $ bundle install
61
+ ```
62
+
63
+ 3. 在项目的Rakefile中间插入如下代码
64
+ ```ruby
65
+ require 'yapi_check/tasks'
66
+ ENV['YAPI_PROJECT_TOKEN'] = 'THE_TOKEN_FROM_YOUR_YAPI_PROJECT' # YAPI项目唯一标识(必填), 建议配置在项目里. 该配置可进入YAPI项目查看并拷贝, 功能路径: 设置 -> token配置
67
+ ENV['YAPI_PROJECT_DOMAIN'] = 'http://YOUR_YAPI_WEBSITE' # YAPI项目域名, 建议配置在个人电脑里, 避免变更 ~/.bashrc or ~/.zshrc
68
+ ENV['YAPI_API_PREFIX'] = '' # YAPI项目接口前缀, 可设置为'', 不设置默认为/api/v1
69
+ ```
70
+
71
+ 4. 执行YAPI检查
72
+ 注: 若使用zsh, 请于~/.zshrc中安装rake插件以支持特殊语法 plugins=(... rake)
73
+
74
+ ```shell
75
+ # 完整检查
76
+ $ bundle exec rake yapi:check
77
+ # or
78
+ $ rails yapi:check
79
+
80
+ # 单个接口检查
81
+ $ bundle exec rake yapi:check[/healthy_lives/exit_healthy_life]
82
+ # or
83
+ $ noglob rails yapi:check[/healthy_lives/exit_healthy_life]
84
+ # or
85
+ $ rails 'yapi:check[/healthy_lives/exit_healthy_life]'
86
+
87
+ # 单个标签检查
88
+ $ bundle exec rake yapi:check[,3.3.0]
89
+ # or
90
+ $ noglob rails yapi:check[,3.3.0]
91
+ # or
92
+ $ rails 'yapi:check[,3.3.0]'
93
+ ```
94
+
95
+ ## 注意事项
96
+ 本工具仅限Rails项目使用, 请求参数检查强依赖[lucky_param](https://github.com/shootingfly/lucky_param), 响应参数检查强依赖[jbuilder](https://github.com/rails/jbuilder)
97
+
98
+ ## 贡献
99
+
100
+ 1. Fork它 ( https://github.com/shootingfly/yapi_check/fork )
101
+ 2. 创建一个新的功能分支 (git checkout -b my-new-feature)
102
+ 3. 进行你的更改
103
+ 4. 运行 `ruby test/yapi_check_test.rb` 来运行测试
104
+ 5. 提交你的更改 (git commit -am 'Add some feature')
105
+ 6. 推送到分支 (git push origin my-new-feature)
106
+ 7. 创建一个新的Pull请求
107
+
108
+ ## 许可证
109
+
110
+ 该Gem以[MIT许可证](https://opensource.org/licenses/MIT)的形式提供。
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ require 'rubocop/rake_task'
11
+
12
+ RuboCop::RakeTask.new
13
+
14
+ task default: %i[test rubocop]
data/bin/console ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bundler/setup'
3
+ require 'yapi_check'
4
+
5
+ # You can add fixtures and/or initialization code here to make experimenting
6
+ # with your gem easier. You can also use a different console, if you like.
7
+
8
+ # (If you use this, don't forget to add pry to your Gemfile!)
9
+ # require 'pry'
10
+ # Pry.start
11
+
12
+ require 'irb'
13
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,18 @@
1
+ module YapiCheck
2
+ class Config
3
+ # YAPI项目令牌
4
+ def self.yapi_project_token
5
+ ENV.fetch('YAPI_PROJECT_TOKEN', nil)
6
+ end
7
+
8
+ # YAPI项目开放API访问域名
9
+ def self.yapi_project_domain
10
+ ENV.fetch('YAPI_PROJECT_DOMAIN', nil)
11
+ end
12
+
13
+ # 接口统一前缀, 默认为/api/v1
14
+ def self.yapi_api_prefix
15
+ ENV.fetch('YAPI_API_PREFIX', '/api/v1')
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,241 @@
1
+ module YapiCheck
2
+ class Executor
3
+ # 当前支持检查的YAPI请求参数类型
4
+ YAPI_PARAMS_CLASS = {
5
+ 'string' => :String,
6
+ 'number' => :Float,
7
+ 'integer' => :Integer
8
+ }.freeze
9
+
10
+ # 获取YAPI参数
11
+ def self.get_yapi_params(detail)
12
+ yapi_params = []
13
+ form_params = Array(detail['req_body_form'])
14
+ form_params.each do |form|
15
+ s = YapiParams.new
16
+ s.name = form['name']
17
+ s.example = form['example']
18
+ s.desc = form['desc']
19
+ s.required = form['required'].to_i
20
+ s.category = 'form'
21
+ yapi_params << s
22
+ end
23
+ query_params = Array(detail['req_query'])
24
+ query_params.each do |query|
25
+ s = YapiParams.new
26
+ s.name = query['name']
27
+ s.example = query['example']
28
+ s.desc = query['desc']
29
+ s.required = query['required'].to_i
30
+ s.category = 'query'
31
+ yapi_params << s
32
+ end
33
+ exist_key_arr = []
34
+ begin
35
+ json_data = JSON.parse(detail['req_body_other'] || '{}')
36
+ rescue JSON::ParserError => e
37
+ puts "=== 解析json_data JSON出错#{e.backtrace}"
38
+ return yapi_params
39
+ end
40
+ # 先遍历必填项
41
+ json_params = json_data['required'] || {}
42
+ json_params.each do |param_name|
43
+ s = YapiParams.new
44
+ s.name = param_name
45
+ s.required = 1
46
+ s.type = json_data['properties'][param_name]['type']
47
+ s.category = 'json'
48
+ exist_key_arr << param_name
49
+ yapi_params << s
50
+ end
51
+ if json_data['properties'].present?
52
+ json_data['properties'].each do |k, v|
53
+ next if exist_key_arr.include?(k)
54
+
55
+ s = YapiParams.new
56
+ s.name = k
57
+ s.type = v['type']
58
+ s.category = 'json'
59
+ s.required = 0
60
+ yapi_params << s
61
+ end
62
+ end
63
+ yapi_params
64
+ end
65
+
66
+ # 获取响应参数
67
+ def self.get_response_params(detail)
68
+ yapi_params = []
69
+ begin
70
+ json_params = JSON.parse(detail['res_body'] || '{}')['properties']['data']['properties'] || {}
71
+ rescue JSON::ParserError => e
72
+ puts "=== 解析json_params JSON出错#{e.backtrace}"
73
+ return []
74
+ end
75
+ json_params.each do |k, v|
76
+ s = YapiParams.new
77
+ s.name = k
78
+ s.type = v['type']
79
+ s.category = 'json'
80
+ yapi_params << s
81
+ properties = nil
82
+ case v['type']
83
+ when 'object'
84
+ properties = v['properties']
85
+ when 'array'
86
+ properties = v['items']['properties']
87
+ end
88
+ next unless properties.present?
89
+
90
+ properties.each do |sk, sv|
91
+ s = YapiParams.new
92
+ s.name = sk
93
+ s.type = sv['type']
94
+ s.category = 'json'
95
+ yapi_params << s
96
+ end
97
+ end
98
+ yapi_params
99
+ end
100
+
101
+ # 获取Rails控制器和动作
102
+ def self.get_controller_and_action(endpoint, method)
103
+ request_controller_and_action = Rails.application.routes.recognize_path(endpoint, method: method)
104
+ controller = "#{request_controller_and_action[:controller]}_controller".classify.constantize
105
+ action = request_controller_and_action[:action].to_sym
106
+ [controller, action]
107
+ rescue ActionController::RoutingError
108
+ []
109
+ end
110
+
111
+ # 检查请求参数是否正确(json支持检查类型, query和form不支持)
112
+ def self.check_request_params(_method, yapi_params, action_source)
113
+ errors = []
114
+ yapi_params.each do |yapi_param|
115
+ # 必填性验证
116
+ required_check = yapi_param.required == 1 ? "required(:#{yapi_param.name}" : "optional(:#{yapi_param.name}"
117
+ errors << "#{yapi_param.name}必填性错误,期待#{required_check}" unless action_source.include?(required_check)
118
+ # 类型验证
119
+ if yapi_param.category == 'json'
120
+ type_errors = get_must_not_in_strings(yapi_param).select { |x| action_source.include?(x) }
121
+ errors << "#{yapi_param.name}类型错误, 期待#{required_check}, #{YAPI_PARAMS_CLASS[yapi_param.type]}}" unless type_errors.empty?
122
+ end
123
+ end
124
+
125
+ errors
126
+ end
127
+
128
+ # 反向匹配参数类型
129
+ def self.get_must_not_in_strings(yapi_param)
130
+ name = yapi_param.name
131
+ case yapi_param.type
132
+ when 'string' then ["#{name}, :Integer)", "#{name}, :Float)"]
133
+ when 'number' then ["#{name}, :String)", "#{name}, :Integer)"]
134
+ when 'integer' then ["#{name}, :String)", "#{name}, :Float)"]
135
+ else
136
+ []
137
+ end
138
+ end
139
+
140
+ # 检查接口名称是否一致
141
+ def self.check_request_title(action_title, yapi_title)
142
+ errors = []
143
+ errors << action_title unless action_title == yapi_title
144
+ errors
145
+ end
146
+
147
+ # 核对响应
148
+ def self.check_response(response_params, jbuilder_source)
149
+ errors = []
150
+ response_params.each do |response_param|
151
+ errors << response_param.name unless jbuilder_source =~ get_response_param_type(response_param.name, response_param.type)
152
+ end
153
+ errors
154
+ end
155
+
156
+ # 获取响应参数对应的类型(正则表达式)
157
+ def self.get_response_param_type(name, type)
158
+ case type
159
+ when 'string' then /json\.#{name}\s.*\.to_s/
160
+ when 'number' then /json\.#{name}\s.*\.to_f/
161
+ when 'integer' then /json\.#{name}\s.*\.to_i/
162
+ when 'array' then /json\.#{name}\s(.*\.to_a|.*\sdo)/
163
+ when 'object' then /json\.#{name}\s(.*\.to_h|do\s)/
164
+ else
165
+ raise "Unknown parameter type: #{type}"
166
+ end
167
+ end
168
+
169
+ # 检查单个接口
170
+ def self.check_single_interface(interface_id, method_mapping, path_mapping)
171
+ params = {
172
+ token: YapiCheck::Config.yapi_project_token,
173
+ id: interface_id
174
+ }
175
+ response = HTTP.get("#{YapiCheck::Config.yapi_project_domain}/api/interface/get", params: params)
176
+ begin
177
+ detail = JSON.parse(response.body)['data'].to_h
178
+ rescue JSON::ParserError => e
179
+ puts "=== 解析detail JSON出错#{e.backtrace}"
180
+ return
181
+ end
182
+ endpoint = "#{YapiCheck::Config.yapi_api_prefix}#{path_mapping[interface_id]}"
183
+ controller, action = get_controller_and_action(endpoint, method_mapping[interface_id])
184
+ unless controller.respond_to?(:instance_method)
185
+ puts "=== #{endpoint} 接口未实现"
186
+ return
187
+ end
188
+ action_source = controller.instance_method(action).source
189
+ error = {}
190
+ response_params = get_response_params(detail)
191
+ begin
192
+ if response_params.present?
193
+ jbuilder_source = expand_jbuilder_with_partial("#{YapiCheck::Config.yapi_api_prefix}#{path_mapping[interface_id]}.json.jbuilder")
194
+ error[:response_error] = check_response(response_params, jbuilder_source)
195
+ end
196
+ rescue StandardError => e
197
+ puts e
198
+ puts "=== #{path_mapping[interface_id]} 接口所在目录中缺少的jbuilder文件, 请判断是否需要添加对应jbuilder文件 ==="
199
+ end
200
+ yapi_params = get_yapi_params(detail)
201
+ endpoint_error = {}
202
+ error[:request_params_error] = check_request_params(method_mapping[interface_id], yapi_params, action_source)
203
+ endpoint_error["#{method_mapping[interface_id]} #{path_mapping[interface_id]}"] = error if error[:request_params_error].present? || error[:response_error].present?
204
+ if endpoint_error.present?
205
+ puts "接口: #{path_mapping[interface_id]} 对应文档中的请求参数或响应参数与当前代码存在不一致,"
206
+ puts "请求参数不同之处: #{error[:request_params_error]}"
207
+ puts "响应参数不同之处: #{error[:response_error]}"
208
+ else
209
+ puts "=== 请求类型: #{method_mapping[interface_id]}, 接口: #{path_mapping[interface_id]} 检查无误 ==="
210
+ end
211
+ end
212
+
213
+ # 使用子视图展开jbuilder(递归)
214
+ def self.expand_jbuilder_with_partial(jbuilder_file_name, parent_file_name = nil)
215
+ parent_file_name ||= jbuilder_file_name # 若是相对路径需要知道父级的文件名
216
+ jbuilder_source = []
217
+ jbuilder_view_path = File.join('app/views', jbuilder_file_name)
218
+ File.open(jbuilder_view_path) do |file|
219
+ file.each_line(chomp: true) do |line|
220
+ if line =~ %r{json\.partial! '([a-z0-9_/]+)'}
221
+ partial_file_name = Regexp.last_match(1)
222
+ partial_file_name_arr = partial_file_name.split('/')
223
+ if partial_file_name_arr.size == 1
224
+ # 相对路径 base_measurement => api/v1/measurements/_base_measurement
225
+ partial_view_name = File.join(File.dirname(parent_file_name), "_#{partial_file_name}")
226
+ else
227
+ # 绝对路径 api/v1/measurements/base_measurement => api/v1/measurements/_base_measurement
228
+ partial_file_name_arr[-1] = "_#{partial_file_name_arr[-1]}"
229
+ partial_view_name = partial_file_name_arr.join('/')
230
+ end
231
+ jbuilder_source << expand_jbuilder_with_partial("#{partial_view_name}.json.jbuilder", parent_file_name)
232
+ else
233
+ jbuilder_source << line
234
+ end
235
+ end
236
+ end
237
+
238
+ jbuilder_source.join("\n")
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,67 @@
1
+ require_relative '../yapi_check'
2
+
3
+ desc 'YAPI检查'
4
+ namespace :yapi do
5
+ task :check, %i[uri version] => :environment do |_task, args|
6
+ raise '请设置YAPI_PROJECT_TOKEN环境变量 ' if YapiCheck::Config.yapi_project_token.nil?
7
+ raise '请设置YAPI_PROJECT_DOMAIN环境变量 ' if YapiCheck::Config.yapi_project_domain.nil?
8
+
9
+ puts "\n=== 开启YAPI接口检查(注意: 接口若使用非json参数, 则无法核对参数类型) ===\n"
10
+ # 获取接口列表
11
+ params = {
12
+ token: YapiCheck::Config.yapi_project_token,
13
+ page: 1,
14
+ limit: 1000
15
+ }
16
+ response = HTTP.get("#{YapiCheck::Config.yapi_project_domain}/api/interface/list", params: params)
17
+ interfaces = Array(JSON.parse(response.body).dig('data', 'list'))
18
+ interface_mapping = {}
19
+ method_mapping = {}
20
+ tag_mapping = {}
21
+ path_mapping = {}
22
+ interfaces.each do |interface|
23
+ interface_id = interface['_id']
24
+ method = interface['method']
25
+ path = interface['path']
26
+ tags = interface['tag']
27
+ method_mapping[interface_id] = method
28
+ path_mapping[interface_id] = path
29
+ interface_mapping[path] = interface_id
30
+ # 根据访问路径check某个API
31
+ if args[:uri].present?
32
+ if interface_mapping[args[:uri]].nil?
33
+ next
34
+ else
35
+ YapiCheck::Executor.check_single_interface(interface_mapping[args[:uri]], method_mapping, path_mapping)
36
+ puts "\n"
37
+ end
38
+
39
+ break
40
+ end
41
+ tags.each do |tag|
42
+ if tag_mapping.key?(tag)
43
+ tag_mapping[tag] << interface_id
44
+ else
45
+ tag_mapping[tag] = [interface_id]
46
+ end
47
+ end
48
+ # 版本号和URI均不传,检查除[接口说明]内容外的所有接口
49
+ if args[:version].blank? && args[:uri].blank?
50
+ if interface['title'].start_with?('[接口说明]')
51
+ next
52
+ else
53
+ YapiCheck::Executor.check_single_interface(interface_id, method_mapping, path_mapping)
54
+ puts "\n"
55
+ end
56
+ end
57
+ end
58
+ # 根据版本check某些API
59
+ if args[:version].present? && !tag_mapping[args[:version]].nil?
60
+ interface_ids = tag_mapping[args[:version]]
61
+ interface_ids.each do |interface_id|
62
+ YapiCheck::Executor.check_single_interface(interface_id, method_mapping, path_mapping)
63
+ puts "\n"
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module YapiCheck
2
+ VERSION = '1.0.0'.freeze
3
+ end
@@ -0,0 +1,5 @@
1
+ module YapiCheck
2
+ class YapiParams
3
+ attr_accessor :name, :type, :example, :desc, :required, :category
4
+ end
5
+ end
data/lib/yapi_check.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'method_source'
2
+ require 'http'
3
+
4
+ require_relative 'yapi_check/executor'
5
+ require_relative 'yapi_check/config'
6
+ require_relative 'yapi_check/version'
7
+ require_relative 'yapi_check/yapi_params'
8
+
9
+ module YapiCheck
10
+ class Error < StandardError
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ module YapiCheck
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yapi_check
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - KlayHU
8
+ - Shootingfly
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2024-03-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: http
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '3.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '3.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: method_source
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rails
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '5.2'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '5.2'
70
+ - !ruby/object:Gem::Dependency
71
+ name: railties
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '4.0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '4.0'
84
+ description: |2
85
+ YapiCheck is a gem that automates the process of checking the consistency and conformity between YAPI API documentation and Rails code.
86
+ It includes features for validating request and response parameters, supporting batch checks, and more.
87
+ email:
88
+ - hudongrui_klay@163.com
89
+ - 790174750@qq.com
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".rubocop.yml"
95
+ - CHANGELOG.md
96
+ - Gemfile
97
+ - Gemfile.lock
98
+ - README.md
99
+ - README.zh-CN.md
100
+ - Rakefile
101
+ - bin/console
102
+ - bin/setup
103
+ - lib/yapi_check.rb
104
+ - lib/yapi_check/config.rb
105
+ - lib/yapi_check/executor.rb
106
+ - lib/yapi_check/tasks.rb
107
+ - lib/yapi_check/version.rb
108
+ - lib/yapi_check/yapi_params.rb
109
+ - sig/yapi_check.rbs
110
+ homepage: https://github.com/shootingfly/yapi_check
111
+ licenses:
112
+ - MIT
113
+ metadata:
114
+ source_code_uri: https://github.com/shootingfly/yapi_check.git
115
+ rubygems_mfa_required: 'true'
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 3.0.0
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubygems_version: 3.5.6
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: A tool for checking consistency between YAPI API documentation and Rails
135
+ code.
136
+ test_files: []