Commit 368f4ff1 authored by nanahira's avatar nanahira

auto type infer

parent d92737fe
Pipeline #6286 passed with stages
in 1 minute and 24 seconds
# Koishi Utils Schemagen # koishi-utils-schemagen
在 Koishi.js 中,使用类装饰器定义 Schema。 在 Koishi.js 中,使用类装饰器定义 Schema。
...@@ -23,14 +23,18 @@ export class Config { ...@@ -23,14 +23,18 @@ export class Config {
@DefineSchema({ type: 'number', required: true }) @DefineSchema({ type: 'number', required: true })
foo: number; foo: number;
// 非数组类型会尝试自动推断类型
@DefineSchema()
fooAutomated: number;
@DefineSchema({ type: 'string', default: 'shigma' }) @DefineSchema({ type: 'string', default: 'shigma' })
bar: string; bar: string;
@DefineSchema({ type: 'boolean', default: true, hidden: true }) @DefineSchema({ type: 'boolean', default: true, hidden: true })
baz: boolean; baz: boolean;
// 数组 // 数组,系统会自动推断该类型为数组,但是此时 type 不可省略
@DefineSchema({ type: 'string', array: true, default: ['foo', 'bar'] }) @DefineSchema({ type: 'string', default: ['foo', 'bar'] })
ant: string[]; ant: string[];
// 也可以用定义好的 Schema // 也可以用定义好的 Schema
...@@ -49,7 +53,7 @@ export class Config { ...@@ -49,7 +53,7 @@ export class Config {
@ObjectSchema(B) @ObjectSchema(B)
anotherB: B; anotherB: B;
// 字典 // 字典,type 也不可省略
@DefineSchema({ type: B, dict: true }) @DefineSchema({ type: B, dict: true })
biDict: Record<string, B>; biDict: Record<string, B>;
...@@ -66,4 +70,8 @@ export class Config { ...@@ -66,4 +70,8 @@ export class Config {
const schema = schemaFromClass(Config); const schema = schemaFromClass(Config);
// 直接获取 Config 对象并实例化,可以代替 Schema.validate 使用。对于嵌套类会进行循环实例化。 // 直接获取 Config 对象并实例化,可以代替 Schema.validate 使用。对于嵌套类会进行循环实例化。
const config = schemaTransform(Config, someObject); const config = schemaTransform(Config, someObject);
``` ```
\ No newline at end of file
### 类型推断
koishi-utils-schemagen 会尝试自动从类定义推断 Schema 类型,但是无法自动推断数组和字典类型。
export * from './src/def';
export * from './src/decorators';
export * from './src/methods';
{ {
"name": "koishi-utils-schemagen", "name": "koishi-utils-schemagen",
"version": "1.0.7", "version": "1.1.0",
"description": "在 Koishi.js 中,使用类装饰器定义 Schema", "description": "在 Koishi.js 中,使用类装饰器定义 Schema",
"main": "dist/src/index.js", "main": "dist/index.js",
"typings": "dist/src/index.d.ts", "typings": "dist/index.d.ts",
"scripts": { "scripts": {
"lint": "eslint --fix .", "lint": "eslint --fix .",
"build": "tsc" "build": "tsc"
......
...@@ -20,13 +20,48 @@ function transformDict<T>(cl: ClassConstructor<T>, val: any, array: boolean) { ...@@ -20,13 +20,48 @@ function transformDict<T>(cl: ClassConstructor<T>, val: any, array: boolean) {
} }
} }
export function DefineSchema(options: SchemaOptions): PropertyDecorator { function getStringFromNativeType(nativeType: any) {
if (!nativeType) {
return;
}
const nativeTypeString = nativeType.toString() as string;
if (!nativeTypeString) {
return;
}
if (nativeTypeString.startsWith('class')) {
return 'class';
}
if (!nativeTypeString.startsWith('function ')) {
return;
}
const firstLeftBracketPos = nativeTypeString.indexOf('()');
if (firstLeftBracketPos === -1) {
return;
}
const typeString = nativeTypeString.slice(9, firstLeftBracketPos);
return typeString.toLowerCase();
}
export function DefineSchema(options: SchemaOptions = {}): PropertyDecorator {
return (obj, key) => { return (obj, key) => {
const objClass = obj.constructor; const objClass = obj.constructor;
const keys: string[] = const keys: string[] =
Reflect.getMetadata(SchemaKeysMetaKey, objClass) || []; Reflect.getMetadata(SchemaKeysMetaKey, objClass) || [];
keys.push(key.toString()); keys.push(key.toString());
Reflect.defineMetadata(SchemaKeysMetaKey, keys, objClass); Reflect.defineMetadata(SchemaKeysMetaKey, keys, objClass);
const nativeType = Reflect.getMetadata('design:type', obj, key);
const nativeTypeString = getStringFromNativeType(nativeType);
if (!options.type) {
if (nativeTypeString && nativeTypeString !== 'array') {
options.type =
nativeTypeString === 'class' ? nativeType : nativeTypeString;
} else {
options.type = 'any';
}
}
if (nativeTypeString === 'array') {
options.array = true;
}
Reflect.defineMetadata(SchemaMetaKey, options, objClass, key); Reflect.defineMetadata(SchemaMetaKey, options, objClass, key);
if (options.type && typeof options.type !== 'string') { if (options.type && typeof options.type !== 'string') {
const cl = options.type as ClassConstructor<any>; const cl = options.type as ClassConstructor<any>;
......
...@@ -5,6 +5,7 @@ export type SchemaType = ...@@ -5,6 +5,7 @@ export type SchemaType =
| 'string' | 'string'
| 'number' | 'number'
| 'boolean' | 'boolean'
| 'object'
| 'any' | 'any'
| 'never' | 'never'
| ClassConstructor<any>; | ClassConstructor<any>;
......
export * from './def';
export * from './decorators';
export * from './methods';
...@@ -27,6 +27,8 @@ function getBasePropertySchemaFromOptions(options: SchemaOptions) { ...@@ -27,6 +27,8 @@ function getBasePropertySchemaFromOptions(options: SchemaOptions) {
return Schema.number(); return Schema.number();
case 'boolean': case 'boolean':
return Schema.boolean(); return Schema.boolean();
case 'object':
return Schema.object({}, true);
default: default:
return Schema.any(); return Schema.any();
} }
......
import 'reflect-metadata';
function LogType(): PropertyDecorator {
return (target, key) => {
const t = Reflect.getMetadata('design:type', target, key);
console.log(key, t, typeof t, t.toString());
};
}
class B<T> {
foo: T;
}
class A {
@LogType()
foo: string;
@LogType()
bar: number;
@LogType()
a: Date;
@LogType()
m: Map<string, any>;
@LogType()
s: Set<any>;
@LogType()
b: B<string>;
@LogType()
c: boolean;
@LogType()
d: B<any>[];
@LogType()
aaa: any;
}
...@@ -3,17 +3,17 @@ import { ...@@ -3,17 +3,17 @@ import {
SchemaConf, SchemaConf,
schemaFromClass, schemaFromClass,
schemaTransform, schemaTransform,
} from '../src'; } from '..';
import { Schema } from 'koishi'; import { Schema } from 'koishi';
@SchemaConf({ @SchemaConf({
desc: 'my desc', desc: 'my desc',
}) })
class B { class B {
@DefineSchema({ type: 'number', default: 2, desc: 'aaaaa' }) @DefineSchema({ default: 2, desc: 'aaaaa' })
aa: number; aa: number;
@DefineSchema({ type: 'boolean', default: true }) @DefineSchema({ default: true })
bb: boolean; bb: boolean;
} }
...@@ -21,22 +21,22 @@ class B { ...@@ -21,22 +21,22 @@ class B {
desc: 'my base desc', desc: 'my base desc',
}) })
class A { class A {
@DefineSchema({ type: 'number', required: true }) @DefineSchema({ required: true })
a: number; a: number;
@DefineSchema({ type: 'string', default: 'shigma' }) @DefineSchema({ default: 'shigma' })
b: string; b: string;
@DefineSchema({ type: 'string', array: true, default: ['foo', 'bar'] }) @DefineSchema({ type: 'string', default: ['foo', 'bar'] })
c: string[]; c: string[];
@DefineSchema({ type: B }) @DefineSchema()
bi: B; bi: B;
@DefineSchema({ type: B, array: true }) @DefineSchema({ type: B })
biArr: B[]; biArr: B[];
@DefineSchema({ type: B, dict: true, array: true }) @DefineSchema({ type: B, dict: true })
biDict: Record<string, B>[]; biDict: Record<string, B>[];
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment