1
2
3
4
5
6
7
8
9
10
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
65
66
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
100
101
102
103
104
105
|
# frozen_string_literal: true
# Extending this module will give you the ability of defining
# enum values in a declarative way.
#
# module DismissalReasons
# extend DeclarativeEnum
#
# key :dismissal_reason
# name 'DismissalReasonOfVulnerability'
#
# description <<~TEXT
# This enum holds the user selected dismissal reason
# when they are dismissing the vulnerabilities
# TEXT
#
# define do
# acceptable_risk value: 0, description: 'The vulnerability is known but is considered to be an acceptable business risk.'
# false_positive value: 1, description: 'An error in reporting the presence of a vulnerability in a system when the vulnerability is not present.'
# used_in_tests value: 2, description: 'The finding is not a vulnerability because it is part of a test or is test data.'
# end
#
# Then we can use this module to register enums for our Active Record models like so,
#
# class VulnerabilityFeedback
# declarative_enum DismissalReasons
# end
#
# Also we can use this module to create GraphQL Enum types like so,
#
# module Types
# module Vulnerabilities
# class DismissalReasonEnum < BaseEnum
# declarative_enum DismissalReasons
# end
# end
# end
#
# rubocop:disable Gitlab/ModuleWithInstanceVariables
module DeclarativeEnum
# This `prepended` hook will merge the enum definition
# of the prepended module into the base module to be
# used by `prepend_mod_with` helper method.
def prepended(base)
base.definition.merge!(definition)
end
def key(new_key = nil)
@key = new_key if new_key
@key
end
def name(new_name = nil)
@name = new_name if new_name
@name
end
def description(new_description = nil)
@description = new_description if new_description
@description
end
def define(&block)
raise LocalJumpError, 'No block given' unless block
@definition = Builder.new(definition, block).build
end
# We can use this method later to apply some sanity checks
# but for now, returning a Hash without any check is enough.
def definition
@definition.to_h
end
class Builder
KeyCollisionError = Class.new(StandardError)
def initialize(definition, block)
@definition = definition
@block = block
end
def build
instance_exec(&@block)
@definition
end
private
def method_missing(name, *arguments, value: nil, description: nil, &block)
key = name.downcase.to_sym
raise KeyCollisionError, "'#{key}' collides with an existing enum key!" if @definition[key]
@definition[key] = {
value: value,
description: description
}
end
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|