class

Analyzer::Gleam::Wisp

Inherits Analyzer < FileHelper < Reference < Object

Wisp has no route table — routing is a case over the split path, and the verb comes either from a second subject in the same case or from the handler it delegates to. All four shapes are common in real apps:

case wisp.path_segments(req) {                  # path only
  ["users", id] -> user(req, id)
}

case wisp.path_segments(req), req.method {      # path, method
  ["api", "teams"], http.Post -> create_team(req)
}

case req.method, wisp.path_segments(req) {      # method, path
  Get, ["members"] -> members.list(req)
}

let path = wisp.path_segments(req)              # via let bindings
let method = req.method
case method, path {
  Get, [] -> home.render(req)
}

Rather than assume an order, the subject is parsed to learn which comma position holds the path and which holds the method, and each arm is then read against those positions. When the arm carries no verb the handler it calls is followed (resolve_handler) to find a case req.method or a wisp.require_method.

Constants

BODY_VERBS = Set {"POST", "PUT", "PATCH", "ANY"}

Verbs that can carry a request body.

CALL_REGEX = /(?:\b([a-z_][A-Za-z0-9_]*)\s*\.\s*)?\b([a-z_][A-Za-z0-9_]*)\s*\(/
CASE_KEYWORD = /\bcase\b/
COOKIE_REGEX = /wisp\.get_cookie\s*\(\s*[^,]+,\s*"([^"]+)"/
FORM_FIELD_REGEX = /key_find\s*\(\s*[A-Za-z_][A-Za-z0-9_]*\.(?:values|files)\s*,\s*"([^"]+)"/

list.key_find(formdata.values, "title") — the binder is named formdata, form, … so match any receiver.

FORM_REGEX = /wisp\.require_form\s*\(/
FUNCTION_DEF = /^\s*(?:pub\s+)?fn\s+([a-z_][A-Za-z0-9_]*)\s*\(/
HEADER_REGEX = /\bget_header\s*\(\s*(?:[^,"()]+,\s*)?"([^"]+)"/

2-arg request.get_header(req, "accept") and the piped 1-arg |> request.get_header("accept") are both idiomatic.

HTTP_METHODS = {"Get" => "GET", "Post" => "POST", "Put" => "PUT", "Patch" => "PATCH", "Delete" => "DELETE", "Head" => "HEAD", "Options" => "OPTIONS", "Trace" => "TRACE", "Connect" => "CONNECT"}
IGNORED_CALL_MODULES = Set {"wisp", "gleam", "http", "request", "response", "io", "list", "string", "int", "float", "result", "option", "json", "dict", "bool", "bit_array", "dynamic", "decode", "uri", "bytes_tree", "string_tree", "mist", "case", "fn", "use", "let"}

Calls into the standard library and wisp itself are responses and helpers, never route handlers.

IMPORT_LINE = /^\s*import\s+([a-z_][A-Za-z0-9_\/]*)(?:\s*\.\s*\{[^}]*\})?(?:\s+as\s+([a-z_][A-Za-z0-9_]*))?/
JSON_FIELD_REGEX = /\bfield\s*\(\s*"([^"]+)"/

A wisp.require_json body is decoded elsewhere; decode.field is where the field names actually appear.

JSON_REGEX = /wisp\.require_json\s*\(/
MAX_RESOLVE_DEPTH = 3

Depth limit for following a route arm through handler functions to the case req.method that names its verb.

MAX_SUBJECT_LINES = 8

A case subject spans at most a handful of lines even when the formatter wraps it.

METHOD_BINDING = /\blet\s+([a-z_][A-Za-z0-9_]*)\s*=\s*[a-z_][A-Za-z0-9_]*\.method\b/
METHOD_CASE = /\bcase\s+[A-Za-z_][A-Za-z0-9_]*\.method\s*\{/
METHOD_SOURCE = /\.method\b/
METHOD_TOKEN = /\A(?:[a-z_][A-Za-z0-9_]*\.)?([A-Z][A-Za-z0-9_]*)\z/
NON_ROUTE_BODY = /\A\s*(?:wisp\.)?(?:method_not_allowed|not_found|bad_request|unprocessable_entity|internal_server_error|handle_request\s*\(\s*\))/

_ -> wisp.method_not_allowed([Get, Post]) and _, _ -> wisp.not_found() are the fallthrough arms every wisp router ends with. They match a path but serve no endpoint.

PATH_BINDING = /\blet\s+([a-z_][A-Za-z0-9_]*)\s*=[^\n]*\bpath_segments\s*\(/

let path = wisp.path_segments(req) / let method = req.method. Apps that bind these before the case would otherwise leave the subject looking like two bare identifiers.

PATH_SOURCE = /\bpath_segments\s*\(/
REQUIRE_METHOD = /require_method\s*\(\s*[^,)]+,\s*(?:[a-z_][A-Za-z0-9_]*\.)?([A-Z][A-Za-z0-9_]*)/
SERVE_STATIC = /wisp\.serve_static\s*\(/
STRING_BODY = /wisp\.require_(?:string|bit_array)_body\s*\(/

Class methods

tech_name
Source

Instance methods

analyze
Source
tech

Instance-side view of the same declaration. The per-file rescues live on this base class, which has no way to name the analyzer that is running inside them, so a skipped file could not be attributed to a tech. Deriving it from analyzer_for keeps the name written exactly once.

Source

Nested types