module GoRouteGroupScope

Overview

Shared Go route-group scope resolution for the Go framework taggers.

Both go_auth and go_security answer the same question: a middleware registration (x.Use(...)) appears on some line — which URL prefix does it guard? Go routers express grouping two structurally different ways, and they have to be tracked differently:

Modelling both with a single push/pop stack makes sibling assignment groups accumulate: /api then /admin resolves the second group's middleware to /api/admin. That is both a false negative (the real /admin/* routes lose their tag) and — worse for a security tool — a false positive (/api/admin/* routes are reported as guarded when they are not).

The third case is a group whose path is not a string literal (r.Group(cfg.APIBase + "/v1")). Its prefix is genuinely unknowable to a line-based scanner, and it must NOT collapse to "global": a real app that builds every group that way would have every endpoint — including its explicitly public ones — tagged from whichever .Use(auth) happened to be scanned first. Unknown keeps that case distinguishable from Global so callers can decline to tag, matching spring_security's treatment of a filter chain scoped only by a matcher it cannot resolve.

Direct including types

Defined in:

tagger/framework_taggers/go/group_scope.cr

Constant Summary

ASSIGN_GROUP = /(\w+)\s*:?=\s*(\w+)\.(?:Group|Route|Party|PartyFunc)\s*\(\s*"([^"]*)"/

name := parent.Group("/seg") — assignment group with a literal path.

ASSIGN_GROUP_ANY = /(\w+)\s*:?=\s*(\w+)\.(?:Group|Route|Party|PartyFunc)\s*\(/

The same assignment shape with any first argument, literal or not. Used to tell "this variable is a route group whose prefix we can't read" from "this variable is not a route group at all".

CHAINED_GROUP_USE = /(\w+)\.(?:Group|Route|Party|PartyFunc)\s*\(\s*"([^"]*)"\s*\)\s*\.\s*(?:Use|Pre)\s*\(/

r.Group("/api").Use(auth) — the group is created and the middleware registered in one chained expression, so it is neither an assignment nor a closure group. USE_CALL cannot see the receiver here (the character before .Use is )), so match the chain directly.

CHAINED_GROUP_USE_ANY = /(\w+)\.(?:Group|Route|Party|PartyFunc)\s*\([^)]*\)\s*\.\s*(?:Use|Pre)\s*\(/
CLOSURE_GROUP = /(\w+)\.(?:Group|Route|Party|PartyFunc|Mount)\s*\(\s*"([^"]*)"\s*,\s*func/

parent.Group("/seg", func(...) / .Route / .Party closure group.

CLOSURE_GROUP_ANY = /(\w+)\.(?:Group|Route|Party|PartyFunc|Mount)\s*\(.*\bfunc\b/
GLOBAL_SCOPE = {kind: ScopeKind::Global, prefix: "/"}
UNKNOWN_SCOPE = {kind: ScopeKind::Unknown, prefix: "/"}
USE_CALL = /(\w+)\.(?:Use|Pre)\s*\(/

A .Use(...) / .Pre(...) middleware registration call.

Class Method Summary

Instance Method Summary

Class Method Detail

def self.join_prefix(base : String, seg : String) : String #

Join a base prefix and a new path segment into a normalized URL prefix: ("", "/web") -> "/web" ("/api", "v1") -> "/api/v1"


[View source]

Instance Method Detail

def each_group_scoped_line(content : String, &) : Nil #

Walk content line by line, maintaining the route-group state, and yield each stripped line alongside it. Callers resolve a middleware registration with #resolve_use_scope.


[View source]
def prefix_covers?(prefix : String, url : String) : Bool #

Segment-aware prefix match so "/web" guards "/web/x" but not "/website". The root scope "/" guards every endpoint.


[View source]
def resolve_use_scope(stripped : String, scopes : Scopes) : Scope | Nil #

The scope a middleware registration on stripped guards, or nil when the line registers no middleware. Callers must decline to tag an Unknown scope rather than treating it as global.


[View source]