deliver: 交付收口(引擎代为提交)
This commit is contained in:
parent
2da3cd39f8
commit
fd6580a859
130
scripts/check_crud_json.py
Normal file
130
scripts/check_crud_json.py
Normal file
@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""CRUD 定义(json/*.json)规范自检(QC 退回意见 #4/#5/#6 的 CI 化)。
|
||||
|
||||
三条硬规则(依据 crud-definition-spec / module-development-spec):
|
||||
1. **合法键**:`params` 只允许规范定义的键。`order_by` / `page_size` 是**自创键**——
|
||||
排序用 `sortby`(数组),分页由 sqlor `sqlPaging()` 用 ns 的 `page`/`rows` 处理,
|
||||
在 CRUD JSON 里硬编码 ORDER BY / 页大小属禁项(且与 sortby 语义冲突)。
|
||||
2. **alias 唯一**:alias 只用于「同一张表的多个逻辑视图」。两张**不同**的表共用同一
|
||||
alias,xls2ui 会生成到同一个 `wwwroot/{alias}/` 目录互相覆盖。
|
||||
3. **文件名**:`json/{table_name}.json` 或 `{alias}.json`。文件名与 tblname/alias 都不
|
||||
匹配时,人和工具都对不上号(曾出现 evidence_pbl_artifact.json 这种两头不靠的命名)。
|
||||
|
||||
退出码 0=PASS,1=FAIL。不连库、无第三方依赖,可直接放 CI。
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
|
||||
JSON_DIR = os.path.join(ROOT, 'json')
|
||||
|
||||
# crud-definition-spec 中 list/tree 视图 params 的合法键集合
|
||||
ALLOWED_PARAM_KEYS = {
|
||||
'sortby', 'logined_userorgid', 'logined_userid', 'data_filter',
|
||||
'confidential_fields', 'editor', 'browserfields', 'editexclouded',
|
||||
'edit_exclouded_fields', 'subtables', 'idField', 'textField',
|
||||
'parentField', 'editable', 'uitype',
|
||||
# editable 三 URL 规范上要求「params 顶层」(xls2ddl 从顶层读),同时兼容
|
||||
# 旧写法嵌在 editable 内 —— 两处都允许,但顶层必须存在(见 TOP_LEVEL_URL_KEYS)
|
||||
'new_data_url', 'update_data_url', 'delete_data_url',
|
||||
# 展示/检索辅助键(规范内已文档化的可选项)
|
||||
'filter_labels', 'filter_label', 'filter_title', 'data_url',
|
||||
}
|
||||
FORBIDDEN_PARAM_KEYS = {'order_by', 'page_size', 'page', 'rows', 'limit', 'offset',
|
||||
'fields', 'joins', 'select_fields', 'grid', 'form', 'components'}
|
||||
TOP_LEVEL_URL_KEYS = ('new_data_url', 'update_data_url', 'delete_data_url')
|
||||
|
||||
|
||||
def main():
|
||||
problems = []
|
||||
alias_map = {}
|
||||
if not os.path.isdir(JSON_DIR):
|
||||
print('[pbl_evidence] no json/ directory')
|
||||
return 1
|
||||
|
||||
for fn in sorted(os.listdir(JSON_DIR)):
|
||||
if not fn.endswith('.json'):
|
||||
continue
|
||||
rel = 'json/' + fn
|
||||
path = os.path.join(JSON_DIR, fn)
|
||||
try:
|
||||
d = json.loads(io.open(path, encoding='utf-8').read())
|
||||
except Exception as exc:
|
||||
problems.append('%s 不是合法 JSON:%s' % (rel, exc))
|
||||
continue
|
||||
|
||||
tbl = d.get('tblname')
|
||||
if not tbl:
|
||||
problems.append('%s 缺 tblname(禁 tablename/name/type)' % rel)
|
||||
alias = d.get('alias')
|
||||
if alias:
|
||||
alias_map.setdefault(alias, set()).add(tbl or rel)
|
||||
|
||||
# 规则 3:文件名必须等于 {tblname}.json 或 {alias}.json
|
||||
stem = fn[:-5]
|
||||
allowed_names = {tbl, alias} - {None}
|
||||
# 多逻辑视图约定 alias = {table}_{view},故也接受以 tbl 开头的下划线名
|
||||
if stem not in allowed_names and not any(
|
||||
stem == t or stem.startswith(t + '_') for t in allowed_names):
|
||||
problems.append('%s 文件名与 tblname=%s / alias=%s 均不匹配'
|
||||
'(应为 {tblname}.json 或 {alias}.json)' % (rel, tbl, alias))
|
||||
|
||||
params = d.get('params')
|
||||
if not isinstance(params, dict) or not params:
|
||||
problems.append('%s 缺 params 或为空' % rel)
|
||||
continue
|
||||
|
||||
# 规则 1:合法键 / 禁键
|
||||
for key in params:
|
||||
if key in FORBIDDEN_PARAM_KEYS:
|
||||
problems.append('%s params.%s 是自创/禁用键(排序用 sortby 数组,'
|
||||
'分页由 sqlor sqlPaging 用 ns.page/rows 处理)' % (rel, key))
|
||||
elif key not in ALLOWED_PARAM_KEYS:
|
||||
problems.append('%s params.%s 不在 crud-definition-spec 合法键内' % (rel, key))
|
||||
|
||||
# sortby 必须是数组
|
||||
if 'sortby' in params and not isinstance(params['sortby'], list):
|
||||
problems.append('%s params.sortby 必须是数组' % rel)
|
||||
|
||||
# editable:顶层三 URL 必须齐(xls2ddl 从 params 顶层读,嵌套写法会被静默忽略)
|
||||
for key in TOP_LEVEL_URL_KEYS:
|
||||
if key not in params:
|
||||
problems.append('%s params 顶层缺 %s(嵌套在 editable 内会被 xls2ui 静默忽略,'
|
||||
'生成默认 add_{table}.dspy)' % (rel, key))
|
||||
ed = params.get('editable')
|
||||
if isinstance(ed, dict):
|
||||
for key in TOP_LEVEL_URL_KEYS:
|
||||
if key not in ed:
|
||||
problems.append('%s params.editable 缺 %s' % (rel, key))
|
||||
|
||||
# browserfields.alters 的 uitype=code 项必须有 data 或 dataurl
|
||||
bf = params.get('browserfields') or {}
|
||||
for fname, spec in (bf.get('alters') or {}).items():
|
||||
if not isinstance(spec, dict):
|
||||
problems.append('%s alters.%s 不是对象' % (rel, fname))
|
||||
continue
|
||||
if spec.get('uitype') != 'code':
|
||||
problems.append('%s alters.%s uitype 应为 "code"' % (rel, fname))
|
||||
if not spec.get('data') and not spec.get('dataurl'):
|
||||
problems.append('%s alters.%s 既无 data 也无 dataurl' % (rel, fname))
|
||||
if spec.get('data_field'):
|
||||
problems.append('%s alters.%s 用了已废弃的 data_field' % (rel, fname))
|
||||
|
||||
# 规则 2:alias 不得被两张不同的表共用
|
||||
for alias, tbls in sorted(alias_map.items()):
|
||||
if len(tbls) > 1:
|
||||
problems.append('alias "%s" 被多张表共用 %s —— xls2ui 会生成到同一 '
|
||||
'wwwroot/%s/ 目录互相覆盖' % (alias, sorted(tbls), alias))
|
||||
|
||||
for line in problems:
|
||||
print('FAIL ' + line)
|
||||
print('[pbl_evidence] crud json: %s (%d 项问题)'
|
||||
% ('PASS' if not problems else 'FAIL', len(problems)))
|
||||
return 0 if not problems else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Loading…
x
Reference in New Issue
Block a user