Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .luaurc
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"languageMode": "strict",
"aliases": {
"std": "~/.lute/typedefs/1.0.0/std",
"adpt": "./src/adapter/",
"utils": "./src/utils",
"c": "/home/wiz/projects/nova-projects/nova_core/tests/web/",
"core": "./src/core",
"lune": "~/.lune/.typedefs/0.10.4/",
"test-runner": "./tests/runner",
"c": "/home/wiz/projects/nova-projects/nova_core/tests/web/",
"utils": "./src/utils",
"adpt": "./src/adapter/",
"std": "~/.lute/typedefs/1.0.0/std",
"lint": "~/.lute/typedefs/1.0.0/lint",
"lute": "~/.lute/typedefs/1.0.0/lute"
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/Attribute/RegistryGenerator.luau
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ local registry: { [string]: { [string]: (req: any, ...any) -> any } } = {}

local DECORATOR_DIRS = {
"guards",
-- "validators",
"validators",
"interceptors"
}

Expand Down Expand Up @@ -35,7 +35,7 @@ local function loadRegistry(mainDir: string)
local modulePath = mainDir .. "/../" .. dirName .. "/" .. name
local ok, result = pcall(require, modulePath)

if ok and type(result) == "function" then
if ok and type(result) == "function" or type(result) == "table" then
registry[namespace][name] = result
print(`\27[32mLoaded {namespace}: {name}\27[0m`)
else
Expand Down
131 changes: 131 additions & 0 deletions src/core/Attribute/Validator.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
local registry = require("./RegistryGenerator").registry
local Response = require("@utils/Response")
local Types = require("../../types")

local function validateBody(schema: { [string]: Types.BodyField }, data: { [string]: any }): { string }
local errors = {}

for field, rules in schema do
local value = data[field]

if rules.required and value == nil then
table.insert(errors, `{field} is required`)
continue
end

if value == nil then
continue
end

if rules.type and type(value) ~= rules.type then
table.insert(errors, `{field} must be a {rules.type}`)
continue
end

if rules.pattern and not tostring(value):match(rules.pattern) then
table.insert(errors, `{field} does not match expected pattern`)
end

if type(value) == "number" then
if rules.min and value < rules.min then
table.insert(errors, `{field} must be at least {rules.min}`)
end
if rules.max and value > rules.max then
table.insert(errors, `{field} must be at most {rules.max}`)
end
end

if type(value) == "string" then
if rules.min and #value < rules.min then
table.insert(errors, `{field} must be at least {rules.min} characters`)
end
if rules.max and #value > rules.max then
table.insert(errors, `{field} must be at most {rules.max} characters`)
end
end
end

return errors
end

local function validateString(schema: { [string]: Types.StringField }, data: { [string]: any }): { string }
local errors = {}

for field, rules in schema do
local value = data[field]

if rules.required and value == nil then
table.insert(errors, `{field} is required`)
continue
end

if value == nil then
continue
end

local str = tostring(value)

if rules.pattern and not str:match(rules.pattern) then
table.insert(errors, `{field} does not match expected pattern`)
end

if rules.minLength and #str < rules.minLength then
table.insert(errors, `{field} must be at least {rules.minLength} characters`)
end

if rules.maxLength and #str > rules.maxLength then
table.insert(errors, `{field} must be at most {rules.maxLength} characters`)
end
end

return errors
end

local function Validator(args: { string })
return function(req: Types.Request, next: Types.Next)
local attribute = registry["Validator"]

if not attribute then
return next()
end

for _, arg in ipairs(args) do
local rule = (attribute[arg] :: any) :: Types.ValidatorSchema

if not rule then
continue
end

local errors = {}

if rule.body then
for _, err in validateBody(rule.body, req.body :: any or {}) do
table.insert(errors, err)
end
end

if rule.params then
for _, err in validateString(rule.params, req.params or {}) do
table.insert(errors, err)
end
end

if rule.query then
for _, err in validateString(rule.query, req.query or {}) do
table.insert(errors, err)
end
end

if #errors > 0 then
return Response.json({
message = errors,
error = "Bad Request"
}, { status = 400 })
end
end

return next()
end
end

return Validator
7 changes: 5 additions & 2 deletions src/core/Attribute/init.luau
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
local Types = require("../types")

type Attributes = {
[string]: ({any}) -> (any, any) -> any
[string]: ({any}) -> (Types.Request, Types.Next) -> Types.ResponsePayload?
}

--[[
List of current implemented attributes
]]
return {
Guard = require("@self/Guard"),
Interceptor = require("@self/Interceptor")
Interceptor = require("@self/Interceptor"),
Validator = require("@self/Validator")
} :: Attributes
2 changes: 1 addition & 1 deletion src/core/Routing/Generator.luau
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ end
local function applyAttributes(routeModule: any, extractedDecorators: { [string]: { string } })
for _, method in METHOD_NAMES do
local handler = routeModule[method]
if type(handler) ~= "function" then
if typeof(handler) ~= "function" then
continue
end

Expand Down
30 changes: 23 additions & 7 deletions src/types.luau
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-- TODO: Enhance the types

export type Next = () -> NextResponse
export type Next = () -> ResponsePayload

export type Nova = {
--[[
Expand Down Expand Up @@ -38,12 +38,6 @@ export type ResponsePayload = {
config: ResponseOptions,
}

export type NextResponse = {
status: number?,
body: any,
headers: { [string]: string }?,
}

export type Response = {
--[[
Respond with a string
Expand Down Expand Up @@ -71,4 +65,26 @@ export type Response = {
css: (body: string, config: ResponseOptions?) -> ResponsePayload,
}

-- Validators
export type BodyField = {
type: string?,
required: boolean?,
min: number?,
max: number?,
pattern: string?,
}

export type StringField = {
required: boolean?,
minLength: number?,
maxLength: number?,
pattern: string?,
}

export type ValidatorSchema = {
body: { [string]: BodyField }?,
params: { [string]: StringField }?,
query: { [string]: StringField }?,
}

return {}
2 changes: 1 addition & 1 deletion tests/web/.luaurc
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"aliases": {
"lune": "~/.lune/.typedefs/0.10.4/",
"std": "~/.lute/typedefs/1.0.0/std",
"c": "/home/wiz/projects/nova-projects/nova_core/tests/web/",
"lint": "~/.lute/typedefs/1.0.0/lint",
"nova": "../../src/index",
"lune": "~/.lune/.typedefs/0.10.4/",
"pkg": "./lune_packages/",
"lute": "~/.lute/typedefs/1.0.0/lute"
}
Expand Down
5 changes: 5 additions & 0 deletions tests/web/src/app/route.luau
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ function Home.Get()
return Nova.response.send("Hello, World")
end

--@Validator(createUser)
function Home.Post(req: Nova.Request)
return Nova.response.json({ msg = "Got the Response", data = req.body })
end

return Home
11 changes: 11 additions & 0 deletions tests/web/src/validators/createUser.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
local Nova = require("../../../../src/types")

local schema: Nova.ValidatorSchema = {
body = {
username = { type = "string", required = true, min = 3, max = 20 },
age = { type = "number", required = true, min = 18 },
email = { type = "string", required = true, pattern = "^[^@%s]+@[^@%s]+%.[^@%s]+$" },
}
}

return schema
Loading