Skip to content

Instantly share code, notes, and snippets.

@cameronpcampbell
Last active December 15, 2024 04:00
Show Gist options
  • Save cameronpcampbell/93292ddf0be03ca284c810254333baaa to your computer and use it in GitHub Desktop.
Save cameronpcampbell/93292ddf0be03ca284c810254333baaa to your computer and use it in GitHub Desktop.
mock_web_framework.luau
--!strict
type AppRunConfig = {
port: number
}
type function _stringSplitOnce(str: string, delimiter: string)
local start, finish = string.find(str, delimiter, 1, true)
if start then
local before = string.sub(str, 1, start - 1)
local after = string.sub(str, finish + 1)
return { before, after }
else
return { str, nil }
end
end
type function _tableIsEmpty(input: { [any]: any })
for _ in input:properties() do return false end
return true
end
type function StringToType(str: string)
return
if str == "never" then types.never
elseif str == "any" then types.any
elseif str == "boolean" then types.boolean
elseif str == "number" then types.number
elseif str == "string" then types.string
elseif str == "true" then types.singleton(true)
elseif str == "false" then types.singleton(true)
else types.unknown
end
type function CombinePaths(previousPath: string?, path: string)
local previousPathValue = previousPath:value()
if not previousPathValue then return path end
return types.singleton(`{previousPathValue}{path:value()}`)
end
type function GetParamType(segment: string)
local explicitType = string.match(segment, "%b[]$")
if explicitType then
explicitType = string.sub(explicitType, 2, #explicitType - 1)
end
return StringToType(explicitType)
end
type function GetParams(path: string)
local segments = string.split(path, "/")
local params = types.newtable()
for _, segment in segments do
if #segment == 0 then continue end
if string.sub(segment, 1, 1) ~= ":" then continue end
local _,_, paramName = string.find(segment, "^:([^[]+)")
local paramType = GetParamType(segment)
params:setreadproperty(types.singleton(paramName), paramType)
end
return params
end
type function GetFirstDefinedStringParams(path: string)
return string.split(path, "?")[1]
end
type function GetLastDefinedStringParams(path: string)
local searchParams = string.split(path, "?")
return searchParams[#searchParams]
end
type function GetAllDefinedStringParams(path: string)
return string.gsub(path, "?", "&")
end
type function GetSearchParams(path: string)
--[[
You can change the behaviour of how search params are parse below this comment block:
- GetFirstDefinedStringParams: only parses the first defined search parameters.
- GetLastDefinedStringParams: only parses the last defined search parameters.
- GetAllDefinedStringParams: parses all the defined search parameters, where later defined ones take precedence.
]]
path = GetFirstDefinedStringParams(path)
-- path = GetLastDefinedStringParams(path)
-- path = GetAllDefinedStringParams(path)
local segments = string.split(path, "&")
local params = types.newtable()
for _, segment in segments do
if #segment == 0 then continue end
local _,_, paramName = string.find(segment, "^:?([^[]+)")
local paramType = GetParamType(segment)
params:setreadproperty(types.singleton(paramName), paramType)
end
return params
end
type function BuildContext(path: string, previousPath: string?)
if previousPath then
path = `{previousPath:value()}{path:value()}`
else
path = path:value()
end
path = string.gsub(path, "%s+", "")
local sections = _stringSplitOnce(path, "?")
local context = types.newtable()
local maybeParamsStr, maybeSearchParamsStr = sections[1], sections[2]
if maybeParamsStr and #maybeParamsStr ~= 0 then
local params = GetParams(maybeParamsStr)
if not _tableIsEmpty(params) then
context:setreadproperty(types.singleton("params"), params)
end
end
if maybeSearchParamsStr and #maybeSearchParamsStr ~= 0 then
local searchParams = GetSearchParams(maybeSearchParamsStr)
if not _tableIsEmpty(searchParams) then
context:setreadproperty(types.singleton("searchParams"), searchParams)
end
end
return context
end
local function Route<S>(str: S | "")
return function(handler: (ctx: BuildContext<S, nil>) -> ...any)
-- Implementation here.
end
end
local WebFramework = {
new = function()
-- Implementation here.
return {
get = Route,
post = Route,
patch = Route,
delete = Route,
run = function(config: AppRunConfig)
-- Implementation here.
end
}
end
}
--- Example ---
local App = WebFramework.new()
App.get "/profile/:profileId[number] ? full[boolean]" (function(ctx)
local id = ctx.params.profileId
return `Profile ID: {id}.`
end)
App.run { port = 3000 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment