Class: RouteGuard::Rules::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/route_guard/rules/base.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Base

Returns a new instance of Base.



8
9
10
# File 'lib/route_guard/rules/base.rb', line 8

def initialize(options = {})
  @options = options
end

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



6
7
8
# File 'lib/route_guard/rules/base.rb', line 6

def options
  @options
end

Instance Method Details

#analyze(routes, report) ⇒ Object

Raises:

  • (NotImplementedError)


12
13
14
# File 'lib/route_guard/rules/base.rb', line 12

def analyze(routes, report)
  raise NotImplementedError, "#{self.class} must implement #analyze(routes, report)"
end

#expand_path(path) ⇒ Object

Expands optional path segments in route paths e.g., "/users(.:format)" -> ["/users", "/users.:format"] "/posts(/:year(/:month))" -> ["/posts", "/posts/:year", "/posts/:year/:month"]



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
# File 'lib/route_guard/rules/base.rb', line 19

def expand_path(path)
  start_idx = path.index("(")
  return [path] unless start_idx

  # Find matching ')' at top level
  depth = 0
  end_idx = nil
  path.chars.each_with_index do |char, idx|
    if char == "("
      depth += 1
    elsif char == ")"
      depth -= 1
      if depth == 0
        end_idx = idx
        break
      end
    end
  end

  raise "Unmatched parenthesis in path: #{path}" unless end_idx

  before = path[0...start_idx]
  inside = path[(start_idx + 1)...end_idx]
  after = path[(end_idx + 1)..-1]

  inside_expansions = expand_path(inside)
  after_expansions = expand_path(after)

  results = []
  # Case 1: Omit the inside group
  after_expansions.each do |after_exp|
    results << (before + after_exp)
  end
  # Case 2: Include the inside group
  inside_expansions.each do |inside_exp|
    after_expansions.each do |after_exp|
      results << (before + inside_exp + after_exp)
    end
  end

  results.uniq
end

#path_shadows?(segA, segB, constraintsA, constraintsB) ⇒ Boolean

Checks if Route A matches (shadows) Route B's path representation.

Returns:

  • (Boolean)


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/route_guard/rules/base.rb', line 87

def path_shadows?(segA, segB, constraintsA, constraintsB)
  idxA = 0
  idxB = 0

  while idxA < segA.length && idxB < segB.length
    sA = segA[idxA]
    sB = segB[idxB]

    if sA.start_with?("*")
      return true
    end

    if sB.start_with?("*")
      return false
    end

    if sA.start_with?(":")
      param_A = sA[1..-1]
      constA = constraintsA[param_A.to_sym]

      if sB.start_with?(":")
        param_B = sB[1..-1]
        constB = constraintsB[param_B.to_sym]

        if constA
          if constB
            match = if constA.is_a?(Regexp)
                      if constB.is_a?(Regexp)
                        constB.source == constA.source || constA.match?(constB.source)
                      else
                        constA.match?(constB.to_s)
                      end
                    else
                      if constB.is_a?(Regexp)
                        constB.match?(constA.to_s)
                      else
                        constA.to_s == constB.to_s
                      end
                    end
            return false unless match
          else
            return false
          end
        end
      else
        # sB is literal
        if constA
          match = if constA.is_a?(Regexp)
                    constA.match?(sB)
                  else
                    constA.to_s == sB
                  end
          return false unless match
        end
      end
    else
      # sA is literal
      if sB.start_with?(":")
        return false
      else
        return false if sA != sB
      end
    end

    idxA += 1
    idxB += 1
  end

  if idxA == segA.length && idxB == segB.length
    true
  else
    false
  end
end

#split_segments(path) ⇒ Object

Splits a path string into segment tokens, handling dots and slashes



63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/route_guard/rules/base.rb', line 63

def split_segments(path)
  segments = []
  path.split("/").each do |seg|
    if seg.include?(".")
      parts = seg.split(".")
      segments << parts.first
      parts[1..-1].each { |p| segments << ".#{p}" }
    else
      segments << seg
    end
  end
  segments
end

#verb_matches?(verbA, verbB) ⇒ Boolean

Checks if verbA matches verbB (handling ANY/wildcard)

Returns:

  • (Boolean)


78
79
80
81
82
83
84
# File 'lib/route_guard/rules/base.rb', line 78

def verb_matches?(verbA, verbB)
  return true if verbA == "ANY" || verbB == "ANY"

  verbsA = verbA.split("|")
  verbsB = verbB.split("|")
  (verbsA & verbsB).any?
end