Improve type definitions

This commit is contained in:
syuilo 2019-02-24 09:45:27 +09:00
parent 76edcdbe45
commit b679163d01
No known key found for this signature in database
GPG key ID: BDC4C49D06AB9D69
7 changed files with 110 additions and 73 deletions

38
src/prelude/schema.ts Normal file
View 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;
}

View file

@ -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;
/** /**
* *

View file

@ -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) => {

View file

@ -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': '作成した投稿'
}
} }
} }
}, },

View file

@ -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,

View file

@ -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;
}

View file

@ -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');