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 * as path from 'path'; | ||||
| import * as glob from 'glob'; | ||||
| import { Schema } from '../../prelude/schema'; | ||||
| 
 | ||||
| export type Param = { | ||||
| 	validator: Context<any>; | ||||
|  | @ -29,7 +30,7 @@ export interface IEndpointMeta { | |||
| 		}; | ||||
| 	}; | ||||
| 
 | ||||
| 	res?: any; | ||||
| 	res?: Schema; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * このエンドポイントにリクエストするのにユーザー情報が必須か否か | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import $ from 'cafy'; | ||||
| 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 = { | ||||
| 	stability: 'stable', | ||||
|  | @ -28,12 +29,7 @@ export const meta = { | |||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	res: { | ||||
| 		type: 'array', | ||||
| 		items: { | ||||
| 			type: 'object', | ||||
| 		}, | ||||
| 	}, | ||||
| 	res: convertLog(notesLogSchema), | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, async (ps) => { | ||||
|  |  | |||
|  | @ -175,12 +175,10 @@ export const meta = { | |||
| 
 | ||||
| 	res: { | ||||
| 		type: 'object', | ||||
| 		props: { | ||||
| 		properties: { | ||||
| 			createdNote: { | ||||
| 				type: 'Note', | ||||
| 				desc: { | ||||
| 					'ja-JP': '作成した投稿' | ||||
| 				} | ||||
| 				description: '作成した投稿' | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import config from '../../../config'; | |||
| import { errors as basicErrors } from './errors'; | ||||
| import { schemas } from './schemas'; | ||||
| import { description } from './description'; | ||||
| import { convertOpenApiSchema } from '../../../prelude/schema'; | ||||
| 
 | ||||
| export function genOpenapiSpec(lang = 'ja-JP') { | ||||
| 	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 resSchema = endpoint.meta.res ? renderType(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 resSchema = endpoint.meta.res ? convertOpenApiSchema(endpoint.meta.res) : {}; | ||||
| 
 | ||||
| 		const info = { | ||||
| 			operationId: endpoint.name, | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import * as mongo from 'mongodb'; | |||
| import db from '../../db/mongodb'; | ||||
| import { ICollection } from 'monk'; | ||||
| import Logger from '../../misc/logger'; | ||||
| import { Schema } from '../../prelude/schema'; | ||||
| 
 | ||||
| const logger = new Logger('chart'); | ||||
| 
 | ||||
|  | @ -346,3 +347,18 @@ export default abstract class Chart<T extends Obj> { | |||
| 		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 Note, { INote } from '../../models/note'; | ||||
| import { isLocalUser } from '../../models/user'; | ||||
| import { SchemaType } from '../../prelude/schema'; | ||||
| 
 | ||||
| /** | ||||
|  * 投稿に関するチャート | ||||
|  */ | ||||
| type NotesLog = { | ||||
| 	local: { | ||||
| 		/** | ||||
| 		 * 集計期間時点での、全投稿数 | ||||
| 		 */ | ||||
| 		total: number; | ||||
| const logSchema = { | ||||
| 	total: { | ||||
| 		type: 'number' as 'number', | ||||
| 		description: '集計期間時点での、全投稿数' | ||||
| 	}, | ||||
| 
 | ||||
| 		/** | ||||
| 		 * 増加した投稿数 | ||||
| 		 */ | ||||
| 		inc: number; | ||||
| 	inc: { | ||||
| 		type: 'number' as 'number', | ||||
| 		description: '増加した投稿数' | ||||
| 	}, | ||||
| 
 | ||||
| 		/** | ||||
| 		 * 減少した投稿数 | ||||
| 		 */ | ||||
| 		dec: number; | ||||
| 	dec: { | ||||
| 		type: 'number' as 'number', | ||||
| 		description: '減少した投稿数' | ||||
| 	}, | ||||
| 
 | ||||
| 		diffs: { | ||||
| 			/** | ||||
| 			 * 通常の投稿数の差分 | ||||
| 			 */ | ||||
| 			normal: number; | ||||
| 	diffs: { | ||||
| 		type: 'object' as 'object', | ||||
| 		properties: { | ||||
| 			normal: { | ||||
| 				type: 'number' as 'number', | ||||
| 				description: '通常の投稿数の差分' | ||||
| 			}, | ||||
| 
 | ||||
| 			/** | ||||
| 			 * リプライの投稿数の差分 | ||||
| 			 */ | ||||
| 			reply: number; | ||||
| 			reply: { | ||||
| 				type: 'number' as 'number', | ||||
| 				description: 'リプライの投稿数の差分' | ||||
| 			}, | ||||
| 
 | ||||
| 			/** | ||||
| 			 * Renoteの投稿数の差分 | ||||
| 			 */ | ||||
| 			renote: number; | ||||
| 		}; | ||||
| 	}; | ||||
| 
 | ||||
| 	remote: NotesLog['local']; | ||||
| 			renote: { | ||||
| 				type: 'number' as 'number', | ||||
| 				description: 'Renoteの投稿数の差分' | ||||
| 			}, | ||||
| 		} | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| 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> { | ||||
| 	constructor() { | ||||
| 		super('notes'); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue