;; 路径段(slug)与保留字校验(设计文档 §4.3)
(defmodule lamina_validate
(export all))
(defun reserved ()
(list "login" "logout" "setup" "admin" "explore" "organizations"
"about" "static" "priv" "favicon.ico" "robots.txt" "assets"))
;; 合法 slug:字母(含大写)、数字、中划线,最长 64,且不是保留字。
;; 保留字按小写比较,Admin 与 admin 一样不能占用系统路径。
(defun slug-pattern () "^[A-Za-z0-9][A-Za-z0-9-]{0,63}$")
(defun reserved? (s)
(lists:member (string:lowercase s) (reserved)))
(defun valid-slug? (s)
(and
(is_list s)
(case (re:run s (slug-pattern))
(`#(match ,_) 'true)
(_ 'false))
(not (reserved? s))))
(defun check-slug (slug)
(cond
((not (is_list slug)) `#(error "名称无效"))
((reserved? slug) `#(error "该名称是系统保留字"))
((case (re:run slug (slug-pattern))
(`#(match ,_) 'false)
(_ 'true))
`#(error "名称只能包含字母、数字和中划线,且以字母或数字开头"))
('true 'ok)))
;; 组织名/仓库名组合在 DB 中唯一;org-name 为 'none 表示未归类仓库
;; 返回 'ok | {error, Msg}
(defun check-availability (org-name slug)
(case (check-slug slug)
('ok
(let ((n (repo-count org-name slug)))
(if (> n 0)
`#(error "该路径已被占用")
'ok)))
(other other)))
(defun repo-count (org-name slug)
(let ((result
(case org-name
('none
(catch (lamina_db:q "SELECT COUNT(*) FROM repositories WHERE name=? AND organization_id IS NULL;"
(list slug))))
(_
(catch (lamina_db:q "SELECT COUNT(*) FROM repositories r JOIN organizations o ON r.organization_id=o.id WHERE r.name=? AND o.name=?;"
(list slug org-name)))))))
(case result
(`((,n)) (to-int n))
(_ 1)))) ; 查询失败按已占用处理,保守拒绝
(defun to-int (v)
(cond
((is_integer v) v)
((is_binary v) (binary_to_integer v))
('true (list_to_integer v))))