Last active
December 15, 2024 04:00
-
-
Save cameronpcampbell/93292ddf0be03ca284c810254333baaa to your computer and use it in GitHub Desktop.
mock_web_framework.luau
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--!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