Improve type definitions
This commit is contained in:
parent
76edcdbe45
commit
b679163d01
7 changed files with 110 additions and 73 deletions
38
src/prelude/schema.ts
Normal file
38
src/prelude/schema.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
export type Schema = {
|
||||||
|
type: 'number' | 'string' | 'array' | 'object' | any;
|
||||||
|
optional?: boolean;
|
||||||
|
items?: Schema;
|
||||||
|
properties?: Obj;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Obj = { [key: string]: Schema };
|
||||||
|
|
||||||
|
export type ObjType<s extends Obj> = { [P in keyof s]: SchemaType<s[P]> };
|
||||||
|
|
||||||
|
// https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2
|
||||||
|
type MyType<T extends Schema> = {
|
||||||
|
0: any;
|
||||||
|
1: SchemaType<T>;
|
||||||
|
}[T extends Schema ? 1 : 0];
|
||||||
|
|
||||||
|
export type SchemaType<p extends Schema> =
|
||||||
|
p['type'] extends 'number' ? number :
|
||||||
|
p['type'] extends 'string' ? string :
|
||||||
|
p['type'] extends 'array' ? MyType<p['items']>[] :
|
||||||
|
p['type'] extends 'object' ? ObjType<p['properties']> :
|
||||||
|
any;
|
||||||
|
|
||||||
|
export function convertOpenApiSchema(schema: Schema) {
|
||||||
|
const x = JSON.parse(JSON.stringify(schema)); // copy
|
||||||
|
if (!['string', 'number', 'boolean', 'array', 'object'].includes(x.type)) {
|
||||||
|
x['$ref'] = `#/components/schemas/${x.type}`;
|
||||||
|
}
|
||||||
|
if (x.type === 'object' && x.properties) {
|
||||||
|
x.required = Object.entries(x.properties).filter(([k, v]: any) => !v.isOptional).map(([k, v]: any) => k);
|
||||||
|
for (const k of Object.keys(x.properties)) {
|
||||||
|
x.properties[k] = convertOpenApiSchema(x.properties[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { Context } from 'cafy';
|
import { Context } from 'cafy';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as glob from 'glob';
|
import * as glob from 'glob';
|
||||||
|
import { Schema } from '../../prelude/schema';
|
||||||
|
|
||||||
export type Param = {
|
export type Param = {
|
||||||
validator: Context<any>;
|
validator: Context<any>;
|
||||||
|
@ -29,7 +30,7 @@ export interface IEndpointMeta {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
res?: any;
|
res?: Schema;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* このエンドポイントにリクエストするのにユーザー情報が必須か否か
|
* このエンドポイントにリクエストするのにユーザー情報が必須か否か
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import define from '../../define';
|
import define from '../../define';
|
||||||
import notesChart from '../../../../services/chart/notes';
|
import notesChart, { notesLogSchema } from '../../../../services/chart/notes';
|
||||||
|
import { convertLog } from '../../../../services/chart';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
stability: 'stable',
|
stability: 'stable',
|
||||||
|
@ -28,12 +29,7 @@ export const meta = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
res: {
|
res: convertLog(notesLogSchema),
|
||||||
type: 'array',
|
|
||||||
items: {
|
|
||||||
type: 'object',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps) => {
|
export default define(meta, async (ps) => {
|
||||||
|
|
|
@ -175,12 +175,10 @@ export const meta = {
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
props: {
|
properties: {
|
||||||
createdNote: {
|
createdNote: {
|
||||||
type: 'Note',
|
type: 'Note',
|
||||||
desc: {
|
description: '作成した投稿'
|
||||||
'ja-JP': '作成した投稿'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import config from '../../../config';
|
||||||
import { errors as basicErrors } from './errors';
|
import { errors as basicErrors } from './errors';
|
||||||
import { schemas } from './schemas';
|
import { schemas } from './schemas';
|
||||||
import { description } from './description';
|
import { description } from './description';
|
||||||
|
import { convertOpenApiSchema } from '../../../prelude/schema';
|
||||||
|
|
||||||
export function genOpenapiSpec(lang = 'ja-JP') {
|
export function genOpenapiSpec(lang = 'ja-JP') {
|
||||||
const spec = {
|
const spec = {
|
||||||
|
@ -104,33 +105,7 @@ export function genOpenapiSpec(lang = 'ja-JP') {
|
||||||
|
|
||||||
const required = endpoint.meta.params ? Object.entries(endpoint.meta.params).filter(([k, v]) => !v.validator.isOptional).map(([k, v]) => k) : [];
|
const required = endpoint.meta.params ? Object.entries(endpoint.meta.params).filter(([k, v]) => !v.validator.isOptional).map(([k, v]) => k) : [];
|
||||||
|
|
||||||
const resSchema = endpoint.meta.res ? renderType(endpoint.meta.res) : {};
|
const resSchema = endpoint.meta.res ? convertOpenApiSchema(endpoint.meta.res) : {};
|
||||||
|
|
||||||
function renderType(x: any) {
|
|
||||||
const res = {} as any;
|
|
||||||
|
|
||||||
if (['User', 'Note', 'DriveFile'].includes(x.type)) {
|
|
||||||
res['$ref'] = `#/components/schemas/${x.type}`;
|
|
||||||
} else if (x.type === 'object') {
|
|
||||||
res['type'] = 'object';
|
|
||||||
if (x.props) {
|
|
||||||
const props = {} as any;
|
|
||||||
for (const kv of Object.entries(x.props)) {
|
|
||||||
props[kv[0]] = renderType(kv[1]);
|
|
||||||
}
|
|
||||||
res['properties'] = props;
|
|
||||||
}
|
|
||||||
} else if (x.type === 'array') {
|
|
||||||
res['type'] = 'array';
|
|
||||||
if (x.items) {
|
|
||||||
res['items'] = renderType(x.items);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res['type'] = x.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = {
|
const info = {
|
||||||
operationId: endpoint.name,
|
operationId: endpoint.name,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import * as mongo from 'mongodb';
|
||||||
import db from '../../db/mongodb';
|
import db from '../../db/mongodb';
|
||||||
import { ICollection } from 'monk';
|
import { ICollection } from 'monk';
|
||||||
import Logger from '../../misc/logger';
|
import Logger from '../../misc/logger';
|
||||||
|
import { Schema } from '../../prelude/schema';
|
||||||
|
|
||||||
const logger = new Logger('chart');
|
const logger = new Logger('chart');
|
||||||
|
|
||||||
|
@ -346,3 +347,18 @@ export default abstract class Chart<T extends Obj> {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertLog(logSchema: Schema): Schema {
|
||||||
|
const v: Schema = JSON.parse(JSON.stringify(logSchema)); // copy
|
||||||
|
if (v.type === 'number') {
|
||||||
|
v.type = 'array';
|
||||||
|
v.items = {
|
||||||
|
type: 'number'
|
||||||
|
};
|
||||||
|
} else if (v.type === 'object') {
|
||||||
|
for (const k of Object.keys(v.properties)) {
|
||||||
|
v.properties[k] = convertLog(v.properties[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
|
@ -2,48 +2,61 @@ import autobind from 'autobind-decorator';
|
||||||
import Chart, { Obj } from '.';
|
import Chart, { Obj } from '.';
|
||||||
import Note, { INote } from '../../models/note';
|
import Note, { INote } from '../../models/note';
|
||||||
import { isLocalUser } from '../../models/user';
|
import { isLocalUser } from '../../models/user';
|
||||||
|
import { SchemaType } from '../../prelude/schema';
|
||||||
|
|
||||||
/**
|
const logSchema = {
|
||||||
* 投稿に関するチャート
|
total: {
|
||||||
*/
|
type: 'number' as 'number',
|
||||||
type NotesLog = {
|
description: '集計期間時点での、全投稿数'
|
||||||
local: {
|
},
|
||||||
/**
|
|
||||||
* 集計期間時点での、全投稿数
|
|
||||||
*/
|
|
||||||
total: number;
|
|
||||||
|
|
||||||
/**
|
inc: {
|
||||||
* 増加した投稿数
|
type: 'number' as 'number',
|
||||||
*/
|
description: '増加した投稿数'
|
||||||
inc: number;
|
},
|
||||||
|
|
||||||
/**
|
dec: {
|
||||||
* 減少した投稿数
|
type: 'number' as 'number',
|
||||||
*/
|
description: '減少した投稿数'
|
||||||
dec: number;
|
},
|
||||||
|
|
||||||
diffs: {
|
diffs: {
|
||||||
/**
|
type: 'object' as 'object',
|
||||||
* 通常の投稿数の差分
|
properties: {
|
||||||
*/
|
normal: {
|
||||||
normal: number;
|
type: 'number' as 'number',
|
||||||
|
description: '通常の投稿数の差分'
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
reply: {
|
||||||
* リプライの投稿数の差分
|
type: 'number' as 'number',
|
||||||
*/
|
description: 'リプライの投稿数の差分'
|
||||||
reply: number;
|
},
|
||||||
|
|
||||||
/**
|
renote: {
|
||||||
* Renoteの投稿数の差分
|
type: 'number' as 'number',
|
||||||
*/
|
description: 'Renoteの投稿数の差分'
|
||||||
renote: number;
|
},
|
||||||
};
|
}
|
||||||
};
|
},
|
||||||
|
|
||||||
remote: NotesLog['local'];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const notesLogSchema = {
|
||||||
|
type: 'object' as 'object',
|
||||||
|
properties: {
|
||||||
|
local: {
|
||||||
|
type: 'object' as 'object',
|
||||||
|
properties: logSchema
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
type: 'object' as 'object',
|
||||||
|
properties: logSchema
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type NotesLog = SchemaType<typeof notesLogSchema>;
|
||||||
|
|
||||||
class NotesChart extends Chart<NotesLog> {
|
class NotesChart extends Chart<NotesLog> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('notes');
|
super('notes');
|
||||||
|
|
Loading…
Reference in a new issue