Skip to content

Commit 8363975

Browse files
authored
Merge pull request #699 from code0-tech/698-add-cop-to-check-for-non-existing-error-codes
add cop to check for error codes
2 parents 2effaa8 + 0513afe commit 8363975

File tree

4 files changed

+106
-0
lines changed

4 files changed

+106
-0
lines changed

.rubocop.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,6 @@ Style/SymbolProc:
105105

106106
Style/TrailingCommaInHashLiteral:
107107
EnforcedStyleForMultiline: comma
108+
109+
Sagittarius/ErrorCode:
110+
Enabled: true

app/services/error_code.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def self.error_codes
5656
invalid_namespace_member: { description: 'The namespace member is invalid because of active model errors' },
5757
invalid_attachment: { description: 'The attachment is invalid because of active model errors' },
5858
invalid_namespace_license: { description: 'The namespace license is invalid because of active model errors' },
59+
invalid_flow: { description: 'The flow is invalid because of active model errors' },
5960
project_not_found: { description: 'The namespace project with the given identifier was not found' },
6061
runtime_not_found: { description: 'The runtime with the given identifier was not found' },
6162
namespace_not_found: { description: 'The namespace with the given identifier was not found' },

docs/graphql/enum/errorcodeenum.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Represents the available error responses
2525
| `INCONSISTENT_NAMESPACE` | Resources are from different namespaces |
2626
| `INVALID_ATTACHMENT` | The attachment is invalid because of active model errors |
2727
| `INVALID_EXTERNAL_IDENTITY` | This external identity is invalid |
28+
| `INVALID_FLOW` | The flow is invalid because of active model errors |
2829
| `INVALID_FLOW_SETTING` | The flow setting is invalid because of active model errors |
2930
| `INVALID_LOGIN_DATA` | Invalid login data provided |
3031
| `INVALID_NAMESPACE_LICENSE` | The namespace license is invalid because of active model errors |
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Sagittarius
6+
class ErrorCode < RuboCop::Cop::Base
7+
MSG = 'Error code %s doesnt exist.'
8+
9+
def_node_matcher :is_service_error?, <<~PATTERN
10+
(send (const _ :ServiceResponse) :error ...)
11+
PATTERN
12+
13+
def on_send(node)
14+
return unless in_service?(node)
15+
return unless is_service_error?(node)
16+
17+
node.children.each do |child|
18+
next unless child.is_a?(RuboCop::AST::HashNode)
19+
20+
child.children.each do |child_child|
21+
next unless child_child.is_a?(RuboCop::AST::PairNode)
22+
next unless child_child.children[0].sym_type?
23+
next unless child_child.children[1].sym_type?
24+
next unless child_child.children[0].value == :error_code
25+
26+
code = child_child.children[1].value
27+
add_offense(child_child.children[1], message: MSG % ":#{code}") unless exists_error_code?(code)
28+
end
29+
end
30+
end
31+
32+
def exists_error_code?(code)
33+
@exists_error_code ||= extract_all_error_codes
34+
35+
@exists_error_code.include?(code)
36+
end
37+
38+
def extract_error_code_hash_from_ast(ast)
39+
return {} unless ast
40+
41+
method_defs = ast.each_node(:def, :defs).select do |node|
42+
node.method_name == :error_codes
43+
end
44+
45+
return {} if method_defs.empty?
46+
47+
# Collect all hash literals returned by these methods
48+
hash_nodes = method_defs.filter_map do |m|
49+
body = m.body
50+
next unless body
51+
52+
# The body can be:
53+
# - a literal hash
54+
# - a call to `super.merge({ ... })`
55+
if body.hash_type?
56+
body
57+
elsif body.send_type? && body.method_name == :merge
58+
merge_arg = body.arguments.first
59+
merge_arg if merge_arg&.hash_type?
60+
end
61+
end
62+
63+
hash_nodes.flat_map(&:pairs).each_with_object({}) do |pair, h|
64+
key = pair.key
65+
next unless key.sym_type?
66+
67+
h[key.value] = pair.value
68+
end
69+
end
70+
71+
def extract_all_error_codes
72+
files = Dir.glob("#{__dir__}/../../../../**/app/services/**/error_code.rb")
73+
74+
merged = {}
75+
76+
files.each do |path|
77+
next unless File.exist?(path)
78+
79+
ast = RuboCop::ProcessedSource.new(File.read(path), RUBY_VERSION.to_f).ast
80+
partial = extract_error_code_hash_from_ast(ast)
81+
merged.merge!(partial) # child overrides parent, matches super.merge behavior
82+
end
83+
84+
merged.keys
85+
end
86+
87+
def dirname(node)
88+
File.dirname(filepath(node))
89+
end
90+
91+
def filepath(node)
92+
node.location.expression.source_buffer.name
93+
end
94+
95+
def in_service?(node)
96+
dirname(node).include?('app/services') # .include? because the path is ../app/services/...
97+
end
98+
end
99+
end
100+
end
101+
end

0 commit comments

Comments
 (0)