Class: YapiCheck::Executor

Inherits:
Object
  • Object
show all
Defined in:
lib/yapi_check/executor.rb

Constant Summary collapse

YAPI_PARAMS_CLASS =

当前支持检查的YAPI请求参数类型

{
  'string' => :String,
  'number' => :Float,
  'integer' => :Integer
}.freeze

Class Method Summary collapse

Class Method Details

.check_request_params(_method, yapi_params, action_source) ⇒ Object

检查请求参数是否正确(json支持检查类型, query和form不支持)



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/yapi_check/executor.rb', line 112

def self.check_request_params(_method, yapi_params, action_source)
  errors = []
  yapi_params.each do |yapi_param|
    # 必填性验证
    required_check = yapi_param.required == 1 ? "required(:#{yapi_param.name}" : "optional(:#{yapi_param.name}"
    errors << "#{yapi_param.name}必填性错误,期待#{required_check}" unless action_source.include?(required_check)
    # 类型验证
    if yapi_param.category == 'json'
      type_errors = get_must_not_in_strings(yapi_param).select { |x| action_source.include?(x) }
      errors << "#{yapi_param.name}类型错误, 期待#{required_check}, #{YAPI_PARAMS_CLASS[yapi_param.type]}}" unless type_errors.empty?
    end
  end

  errors
end

.check_request_title(action_title, yapi_title) ⇒ Object

检查接口名称是否一致



141
142
143
144
145
# File 'lib/yapi_check/executor.rb', line 141

def self.check_request_title(action_title, yapi_title)
  errors = []
  errors << action_title unless action_title == yapi_title
  errors
end

.check_response(response_params, jbuilder_source) ⇒ Object

核对响应



148
149
150
151
152
153
154
# File 'lib/yapi_check/executor.rb', line 148

def self.check_response(response_params, jbuilder_source)
  errors = []
  response_params.each do |response_param|
    errors << response_param.name unless jbuilder_source =~ get_response_param_type(response_param.name, response_param.type)
  end
  errors
end

.check_single_interface(interface_id, method_mapping, path_mapping) ⇒ Object

检查单个接口



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/yapi_check/executor.rb', line 170

def self.check_single_interface(interface_id, method_mapping, path_mapping)
  params = {
    token: YapiCheck::Config.yapi_project_token,
    id: interface_id
  }
  response = HTTP.get("#{YapiCheck::Config.yapi_project_domain}/api/interface/get", params: params)
  begin
    detail = JSON.parse(response.body)['data'].to_h
  rescue JSON::ParserError => e
    puts "=== 解析detail JSON出错#{e.backtrace}"
    return
  end
  endpoint = "#{YapiCheck::Config.yapi_api_prefix}#{path_mapping[interface_id]}"
  controller, action = get_controller_and_action(endpoint, method_mapping[interface_id])
  unless controller.respond_to?(:instance_method)
    puts "=== #{endpoint} 接口未实现"
    return
  end
  action_source = controller.instance_method(action).source
  error = {}
  response_params = get_response_params(detail)
  begin
    if response_params.present?
      jbuilder_source = expand_jbuilder_with_partial("#{YapiCheck::Config.yapi_api_prefix}#{path_mapping[interface_id]}.json.jbuilder")
      error[:response_error] = check_response(response_params, jbuilder_source)
    end
  rescue StandardError => e
    puts e
    puts "=== #{path_mapping[interface_id]} 接口所在目录中缺少的jbuilder文件, 请判断是否需要添加对应jbuilder文件 ==="
  end
  yapi_params = get_yapi_params(detail)
  endpoint_error = {}
  error[:request_params_error] = check_request_params(method_mapping[interface_id], yapi_params, action_source)
  endpoint_error["#{method_mapping[interface_id]} #{path_mapping[interface_id]}"] = error if error[:request_params_error].present? || error[:response_error].present?
  if endpoint_error.present?
    puts "接口: #{path_mapping[interface_id]} 对应文档中的请求参数或响应参数与当前代码存在不一致,"
    puts "请求参数不同之处: #{error[:request_params_error]}"
    puts "响应参数不同之处: #{error[:response_error]}"
  else
    puts "=== 请求类型: #{method_mapping[interface_id]}, 接口: #{path_mapping[interface_id]} 检查无误 ==="
  end
end

.expand_jbuilder_with_partial(jbuilder_file_name, parent_file_name = nil) ⇒ Object

使用子视图展开jbuilder(递归)



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/yapi_check/executor.rb', line 214

def self.expand_jbuilder_with_partial(jbuilder_file_name, parent_file_name = nil)
  parent_file_name ||= jbuilder_file_name # 若是相对路径需要知道父级的文件名
  jbuilder_source = []
  jbuilder_view_path = File.join('app/views', jbuilder_file_name)
  File.open(jbuilder_view_path) do |file|
    file.each_line(chomp: true) do |line|
      if line =~ %r{json\.partial! '([a-z0-9_/]+)'}
        partial_file_name = Regexp.last_match(1)
        partial_file_name_arr = partial_file_name.split('/')
        if partial_file_name_arr.size == 1
          # 相对路径 base_measurement => api/v1/measurements/_base_measurement
          partial_view_name = File.join(File.dirname(parent_file_name), "_#{partial_file_name}")
        else
          # 绝对路径 api/v1/measurements/base_measurement => api/v1/measurements/_base_measurement
          partial_file_name_arr[-1] = "_#{partial_file_name_arr[-1]}"
          partial_view_name = partial_file_name_arr.join('/')
        end
        jbuilder_source << expand_jbuilder_with_partial("#{partial_view_name}.json.jbuilder", parent_file_name)
      else
        jbuilder_source << line
      end
    end
  end

  jbuilder_source.join("\n")
end

.get_controller_and_action(endpoint, method) ⇒ Object

获取Rails控制器和动作



102
103
104
105
106
107
108
109
# File 'lib/yapi_check/executor.rb', line 102

def self.get_controller_and_action(endpoint, method)
  request_controller_and_action = Rails.application.routes.recognize_path(endpoint, method: method)
  controller = "#{request_controller_and_action[:controller]}_controller".classify.constantize
  action = request_controller_and_action[:action].to_sym
  [controller, action]
rescue ActionController::RoutingError
  []
end

.get_must_not_in_strings(yapi_param) ⇒ Object

反向匹配参数类型



129
130
131
132
133
134
135
136
137
138
# File 'lib/yapi_check/executor.rb', line 129

def self.get_must_not_in_strings(yapi_param)
  name = yapi_param.name
  case yapi_param.type
  when 'string' then ["#{name}, :Integer)", "#{name}, :Float)"]
  when 'number' then ["#{name}, :String)", "#{name}, :Integer)"]
  when 'integer' then ["#{name}, :String)", "#{name}, :Float)"]
  else
    []
  end
end

.get_response_param_type(name, type) ⇒ Object

获取响应参数对应的类型(正则表达式)



157
158
159
160
161
162
163
164
165
166
167
# File 'lib/yapi_check/executor.rb', line 157

def self.get_response_param_type(name, type)
  case type
  when 'string' then /json\.#{name}\s.*\.to_s/
  when 'number' then /json\.#{name}\s.*\.to_f/
  when 'integer' then /json\.#{name}\s.*\.to_i/
  when 'array' then /json\.#{name}\s(.*\.to_a|.*\sdo)/
  when 'object' then /json\.#{name}\s(.*\.to_h|do\s)/
  else
    raise "Unknown parameter type: #{type}"
  end
end

.get_response_params(detail) ⇒ Object

获取响应参数



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/yapi_check/executor.rb', line 67

def self.get_response_params(detail)
  yapi_params = []
  begin
    json_params = JSON.parse(detail['res_body'] || '{}')['properties']['data']['properties'] || {}
  rescue JSON::ParserError => e
    puts "=== 解析json_params JSON出错#{e.backtrace}"
    return []
  end
  json_params.each do |k, v|
    s = YapiParams.new
    s.name = k
    s.type = v['type']
    s.category = 'json'
    yapi_params << s
    properties = nil
    case v['type']
    when 'object'
      properties = v['properties']
    when 'array'
      properties = v['items']['properties']
    end
    next unless properties.present?

    properties.each do |sk, sv|
      s = YapiParams.new
      s.name = sk
      s.type = sv['type']
      s.category = 'json'
      yapi_params << s
    end
  end
  yapi_params
end

.get_yapi_params(detail) ⇒ Object

获取YAPI参数



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/yapi_check/executor.rb', line 11

def self.get_yapi_params(detail)
  yapi_params = []
  form_params = Array(detail['req_body_form'])
  form_params.each do |form|
    s = YapiParams.new
    s.name = form['name']
    s.example = form['example']
    s.desc = form['desc']
    s.required = form['required'].to_i
    s.category = 'form'
    yapi_params << s
  end
  query_params = Array(detail['req_query'])
  query_params.each do |query|
    s = YapiParams.new
    s.name = query['name']
    s.example = query['example']
    s.desc = query['desc']
    s.required = query['required'].to_i
    s.category = 'query'
    yapi_params << s
  end
  exist_key_arr = []
  begin
    json_data = JSON.parse(detail['req_body_other'] || '{}')
  rescue JSON::ParserError => e
    puts "=== 解析json_data JSON出错#{e.backtrace}"
    return yapi_params
  end
  # 先遍历必填项
  json_params = json_data['required'] || {}
  json_params.each do |param_name|
    s = YapiParams.new
    s.name = param_name
    s.required = 1
    s.type = json_data['properties'][param_name]['type']
    s.category = 'json'
    exist_key_arr << param_name
    yapi_params << s
  end
  if json_data['properties'].present?
    json_data['properties'].each do |k, v|
      next if exist_key_arr.include?(k)

      s = YapiParams.new
      s.name = k
      s.type = v['type']
      s.category = 'json'
      s.required = 0
      yapi_params << s
    end
  end
  yapi_params
end