Create Mika
This commit is contained in:
		
							parent
							
								
									6683d50bae
								
							
						
					
					
						commit
						9d328784ec
					
				
					 5 changed files with 382 additions and 16 deletions
				
			
		| 
						 | 
					@ -68,6 +68,7 @@ src ... ソースコード
 | 
				
			||||||
	server ... Webサーバー
 | 
						server ... Webサーバー
 | 
				
			||||||
	client ... クライアント
 | 
						client ... クライアント
 | 
				
			||||||
	mfm ... MFM
 | 
						mfm ... MFM
 | 
				
			||||||
 | 
						sanctuary ... TypeScriptの制約を強くしたエリア ~~乃々の聖域ではない~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test ... テスト
 | 
					test ... テスト
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,9 @@ import * as fs from 'fs';
 | 
				
			||||||
import { URL } from 'url';
 | 
					import { URL } from 'url';
 | 
				
			||||||
import * as yaml from 'js-yaml';
 | 
					import * as yaml from 'js-yaml';
 | 
				
			||||||
import { Source, Mixin } from './types';
 | 
					import { Source, Mixin } from './types';
 | 
				
			||||||
 | 
					import Mika, { optional } from '../sanctuary/mika';
 | 
				
			||||||
import * as pkg from '../../package.json';
 | 
					import * as pkg from '../../package.json';
 | 
				
			||||||
 | 
					import Logger from '../misc/logger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Path of configuration directory
 | 
					 * Path of configuration directory
 | 
				
			||||||
| 
						 | 
					@ -21,27 +23,138 @@ const path = process.env.NODE_ENV == 'test'
 | 
				
			||||||
	: `${dir}/default.yml`;
 | 
						: `${dir}/default.yml`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function load() {
 | 
					export default function load() {
 | 
				
			||||||
	const config = yaml.safeLoad(fs.readFileSync(path, 'utf-8')) as Source;
 | 
						const config: Source = yaml.safeLoad(fs.readFileSync(path, 'utf-8'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const mixin = {} as Mixin;
 | 
						const logger = new Logger('config');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const errors = new Mika({
 | 
				
			||||||
 | 
							repository_url: 'string!?',
 | 
				
			||||||
 | 
							feedback_url: 'string!?',
 | 
				
			||||||
 | 
							url: 'string!',
 | 
				
			||||||
 | 
							port: 'number!',
 | 
				
			||||||
 | 
							https: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								key: 'string!',
 | 
				
			||||||
 | 
								cert: 'string!'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							disableHsts: 'boolean!?',
 | 
				
			||||||
 | 
							mongodb: {
 | 
				
			||||||
 | 
								host: 'string!',
 | 
				
			||||||
 | 
								port: 'number!',
 | 
				
			||||||
 | 
								db: 'string!',
 | 
				
			||||||
 | 
								user: 'string!?',
 | 
				
			||||||
 | 
								pass: 'string!?'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							elasticsearch: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								host: 'string!',
 | 
				
			||||||
 | 
								port: 'number!',
 | 
				
			||||||
 | 
								pass: 'string!'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							drive: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								storage: 'string!?'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							redis: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								host: 'string!',
 | 
				
			||||||
 | 
								port: 'number!',
 | 
				
			||||||
 | 
								pass: 'string!'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							autoAdmin: 'boolean!?',
 | 
				
			||||||
 | 
							proxy: 'string!?',
 | 
				
			||||||
 | 
							accesslog: 'string!?',
 | 
				
			||||||
 | 
							clusterLimit: 'number!?',
 | 
				
			||||||
 | 
							// The below properties are defined for backward compatibility.
 | 
				
			||||||
 | 
							name: 'string!?',
 | 
				
			||||||
 | 
							description: 'string!?',
 | 
				
			||||||
 | 
							localDriveCapacityMb: 'number!?',
 | 
				
			||||||
 | 
							remoteDriveCapacityMb: 'number!?',
 | 
				
			||||||
 | 
							preventCacheRemoteFiles: 'boolean!?',
 | 
				
			||||||
 | 
							recaptcha: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								enableRecaptcha: 'boolean!?',
 | 
				
			||||||
 | 
								recaptchaSiteKey: 'string!?',
 | 
				
			||||||
 | 
								recaptchaSecretKey: 'string!?'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							ghost: 'string!?',
 | 
				
			||||||
 | 
							maintainer: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								name: 'string!',
 | 
				
			||||||
 | 
								email: 'string!?'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							twitter: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								consumer_key: 'string!?',
 | 
				
			||||||
 | 
								consumer_secret: 'string!?'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							github: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								client_id: 'string!?',
 | 
				
			||||||
 | 
								client_secret: 'string!?'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							user_recommendation: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								engine: 'string!?',
 | 
				
			||||||
 | 
								timeout: 'number!?'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							sw: {
 | 
				
			||||||
 | 
								[optional]: true,
 | 
				
			||||||
 | 
								public_key: 'string!',
 | 
				
			||||||
 | 
								private_key: 'string!'
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}).validate(config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!errors && config.drive.storage === 'minio') {
 | 
				
			||||||
 | 
							const minioErrors = new Mika({
 | 
				
			||||||
 | 
								bucket: 'string!',
 | 
				
			||||||
 | 
								prefix: 'string!',
 | 
				
			||||||
 | 
								baseUrl: 'string!',
 | 
				
			||||||
 | 
								config: {
 | 
				
			||||||
 | 
									endPoint: 'string!',
 | 
				
			||||||
 | 
									accessKey: 'string!',
 | 
				
			||||||
 | 
									secretKey: 'string!',
 | 
				
			||||||
 | 
									useSSL: 'boolean!?',
 | 
				
			||||||
 | 
									port: 'number!?',
 | 
				
			||||||
 | 
									region: 'string!?',
 | 
				
			||||||
 | 
									transport: 'string!?',
 | 
				
			||||||
 | 
									sessionToken: 'string!?',
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}).validate(config.drive);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (minioErrors)
 | 
				
			||||||
 | 
								for (const error of minioErrors)
 | 
				
			||||||
 | 
									errors.push(error);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (errors) {
 | 
				
			||||||
 | 
							for (const { path, excepted, actual } of errors)
 | 
				
			||||||
 | 
								logger.error(`Invalid config value detected at ${path}: excepted type is ${typeof excepted === 'string' ? excepted : 'object'}, but actual value type is ${actual}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							throw 'The configuration is invalid. Check your .config/default.yml';
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const url = validateUrl(config.url);
 | 
						const url = validateUrl(config.url);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	config.url = normalizeUrl(config.url);
 | 
						config.url = normalizeUrl(config.url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mixin.host = url.host;
 | 
						const scheme = url.protocol.replace(/:$/, '');
 | 
				
			||||||
	mixin.hostname = url.hostname;
 | 
						const ws_scheme = scheme.replace('http', 'ws');
 | 
				
			||||||
	mixin.scheme = url.protocol.replace(/:$/, '');
 | 
					
 | 
				
			||||||
	mixin.ws_scheme = mixin.scheme.replace('http', 'ws');
 | 
						const mixin: Mixin = {
 | 
				
			||||||
	mixin.ws_url = `${mixin.ws_scheme}://${mixin.host}`;
 | 
							host: url.host,
 | 
				
			||||||
	mixin.api_url = `${mixin.scheme}://${mixin.host}/api`;
 | 
							hostname: url.hostname,
 | 
				
			||||||
	mixin.auth_url = `${mixin.scheme}://${mixin.host}/auth`;
 | 
							scheme,
 | 
				
			||||||
	mixin.dev_url = `${mixin.scheme}://${mixin.host}/dev`;
 | 
							ws_scheme,
 | 
				
			||||||
	mixin.docs_url = `${mixin.scheme}://${mixin.host}/docs`;
 | 
							ws_url: `${ws_scheme}://${url.host}`,
 | 
				
			||||||
	mixin.stats_url = `${mixin.scheme}://${mixin.host}/stats`;
 | 
							api_url: `${scheme}://${url.host}/api`,
 | 
				
			||||||
	mixin.status_url = `${mixin.scheme}://${mixin.host}/status`;
 | 
							auth_url: `${scheme}://${url.host}/auth`,
 | 
				
			||||||
	mixin.drive_url = `${mixin.scheme}://${mixin.host}/files`;
 | 
							dev_url: `${scheme}://${url.host}/dev`,
 | 
				
			||||||
	mixin.user_agent = `Misskey/${pkg.version} (${config.url})`;
 | 
							docs_url: `${scheme}://${url.host}/docs`,
 | 
				
			||||||
 | 
							stats_url: `${scheme}://${url.host}/stats`,
 | 
				
			||||||
 | 
							status_url: `${scheme}://${url.host}/status`,
 | 
				
			||||||
 | 
							drive_url: `${scheme}://${url.host}/files`,
 | 
				
			||||||
 | 
							user_agent: `Misskey/${pkg.version} (${config.url})`
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (config.autoAdmin == null) config.autoAdmin = false;
 | 
						if (config.autoAdmin == null) config.autoAdmin = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -115,3 +115,18 @@ export function cumulativeSum(xs: number[]): number[] {
 | 
				
			||||||
	for (let i = 1; i < ys.length; i++) ys[i] += ys[i - 1];
 | 
						for (let i = 1; i < ys.length; i++) ys[i] += ys[i - 1];
 | 
				
			||||||
	return ys;
 | 
						return ys;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function toEnglishString(x: string[], n = 'and'): string {
 | 
				
			||||||
 | 
						switch (x.length) {
 | 
				
			||||||
 | 
							case 0:
 | 
				
			||||||
 | 
								return '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							case 1:
 | 
				
			||||||
 | 
								return x[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								const y = [...x];
 | 
				
			||||||
 | 
								const z = y.pop();
 | 
				
			||||||
 | 
								return `${y.join(', ')}, ${n} ${z}`;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										230
									
								
								src/sanctuary/mika.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								src/sanctuary/mika.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,230 @@
 | 
				
			||||||
 | 
					/* tslint:enable:strict-type-predicates triple-equals */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { toEnglishString } from '../prelude/array';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* KEYWORD DEFINITION
 | 
				
			||||||
 | 
					 * { sakura: null } // 'sakura' is null.
 | 
				
			||||||
 | 
					 * { izumi: undefined } // 'izumi' is undefined.
 | 
				
			||||||
 | 
					 * {} // 'ako' is unprovided (not undefined in here).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Reason: The undefined is a type, so you can define undefined.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const additional = Symbol('Allows additional properties.');
 | 
				
			||||||
 | 
					export const optional = Symbol('Allows unprovided (not undefined).');
 | 
				
			||||||
 | 
					export const nullable = Symbol('Allows null.');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Type = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function';
 | 
				
			||||||
 | 
					type ExtendedType = Type | 'null' | 'unprovided';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Everything = string | number | bigint | boolean | symbol | undefined | object | Function | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Manifest
 | 
				
			||||||
 | 
					 * Information for
 | 
				
			||||||
 | 
					 * Reliance
 | 
				
			||||||
 | 
					 * Identify
 | 
				
			||||||
 | 
					 * Analysis
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					type Miria = {
 | 
				
			||||||
 | 
						/** STRING TYPED SYNTAX
 | 
				
			||||||
 | 
						 * type1       : Allows the type1 and null.
 | 
				
			||||||
 | 
						 * type1|type2 : Allows type1, type2, and null.
 | 
				
			||||||
 | 
						 * type1!      : Allows type1 only.
 | 
				
			||||||
 | 
						 * type1?      : Allows type1, null, and unprovided.
 | 
				
			||||||
 | 
						 * type1!?     : Allows type1, and unprovided.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * (! and ? are suffix.)
 | 
				
			||||||
 | 
						 * (The spaces(U+0020) are ignored.)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						[x: string]: string | Miria;
 | 
				
			||||||
 | 
						[additional]?: boolean;
 | 
				
			||||||
 | 
						[optional]?: boolean;
 | 
				
			||||||
 | 
						[nullable]?: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Reason
 | 
				
			||||||
 | 
					 * Information for
 | 
				
			||||||
 | 
					 * Keeping safety by
 | 
				
			||||||
 | 
					 * Analysis
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					type Rika = {
 | 
				
			||||||
 | 
						path: string;
 | 
				
			||||||
 | 
						excepted: string | Miria | null;
 | 
				
			||||||
 | 
						actual: ExtendedType;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MiriaInternal = {
 | 
				
			||||||
 | 
						[x: string]: string | MiriaInternal | undefined;
 | 
				
			||||||
 | 
						[additional]?: boolean;
 | 
				
			||||||
 | 
						[optional]?: boolean;
 | 
				
			||||||
 | 
						[nullable]?: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const $ = () => {}; // trash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ^ https://github.com/Microsoft/TypeScript/issues/7061
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Manifest based
 | 
				
			||||||
 | 
					 * Identify objects for
 | 
				
			||||||
 | 
					 * Keeping safety by this
 | 
				
			||||||
 | 
					 * Analyzer class
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export default class Mika {
 | 
				
			||||||
 | 
						private static readonly fuhihi = 'Miria'; // < https://github.com/Microsoft/TypeScript/issues/1579
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected static readonly types = ['string', 'number', 'bigint', 'boolean', 'symbol', 'undefined', 'object', 'function'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected static readonly syntax = new RegExp(`^\\s*(?:${Mika.types.join('|')})(?:\\s*\\|\\s*(?:${Mika.types.join('|')}))*\\s*!?\\s*\\??\\s*$`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static readonly additional = additional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static readonly optional = optional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static readonly nullable = nullable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(
 | 
				
			||||||
 | 
							readonly miria: Miria
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							Mika.ensure(miria, [], Object.keys({ miria })[0]);
 | 
				
			||||||
 | 
							//                     ^~~ #1579 (see above) ~~^
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static ensure(
 | 
				
			||||||
 | 
							source: Miria,
 | 
				
			||||||
 | 
							location: string[],
 | 
				
			||||||
 | 
							nameof: string
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							const errorMessage = `Specified ${nameof} is invalid.`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const [k, v] of Object.entries(source)) {
 | 
				
			||||||
 | 
								const invalidType = !['string', 'object'].includes(typeof v);
 | 
				
			||||||
 | 
								const header = () => `${errorMessage} ${[...location, k].join('.')} is`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (invalidType || v === null)
 | 
				
			||||||
 | 
									throw `${header()} ${invalidType ? `${typeof v === 'undefined' ? 'an' : 'a'} ${typeof v}, neither string or object(: ${Mika.fuhihi})` : 'is null'}.`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (typeof v === 'string' && !Mika.syntax.test(v))
 | 
				
			||||||
 | 
									throw `${header()} '${v}', neither ${toEnglishString([...Mika.types, 'combined theirs'], 'or')}.`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (typeof v === 'object')
 | 
				
			||||||
 | 
									this.ensure(v, [...location, k], errorMessage);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected static validateFromString(
 | 
				
			||||||
 | 
							target: Everything,
 | 
				
			||||||
 | 
							source: string,
 | 
				
			||||||
 | 
							location: string[] = []
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							const path = location.join('.');
 | 
				
			||||||
 | 
							const rikas: Rika[] = [];
 | 
				
			||||||
 | 
							const excepted = source.replace(/\s/, '');
 | 
				
			||||||
 | 
							/*let it move! v
 | 
				
			||||||
 | 
							*/let lastSpan = excepted.length - 1;
 | 
				
			||||||
 | 
							const optional = excepted[lastSpan] === '?';
 | 
				
			||||||
 | 
							const nullable = excepted[optional ? --lastSpan : lastSpan] !== '!';
 | 
				
			||||||
 | 
							const allowing = excepted.slice(0, nullable ? --lastSpan : lastSpan).split('|');
 | 
				
			||||||
 | 
							const pushRika = (actual: ExtendedType) => rikas.push({ path, excepted, actual });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (target === null)
 | 
				
			||||||
 | 
								(nullable || pushRika('null'), $)();
 | 
				
			||||||
 | 
							else if (!allowing.includes(typeof target))
 | 
				
			||||||
 | 
								pushRika(typeof target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return rikas;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected dive(
 | 
				
			||||||
 | 
							target: object,
 | 
				
			||||||
 | 
							source: MiriaInternal,
 | 
				
			||||||
 | 
							location: string[] = []
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							const rikas: Rika[] = [];
 | 
				
			||||||
 | 
							/** DEFINITION
 | 
				
			||||||
 | 
							 * |   values   | specs | given |
 | 
				
			||||||
 | 
							 * |-----------:|:------|:------|
 | 
				
			||||||
 | 
							 * |       true | true  | true  |
 | 
				
			||||||
 | 
							 * |      false | false | true  |
 | 
				
			||||||
 | 
							 * |       null | true  | false |
 | 
				
			||||||
 | 
							 * | unprovided | false | false |
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							const keys = Object.keys(source).reduce<Record<string, boolean | null>>((a, c) => (a[c] = null, a), {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const [k, v] of Object.entries(target) as [string, Everything][]) {
 | 
				
			||||||
 | 
								const inclusion = keys[k] !== undefined;
 | 
				
			||||||
 | 
								const x = source[k];
 | 
				
			||||||
 | 
								const miria = x as Miria;
 | 
				
			||||||
 | 
								const here = [...location, k];
 | 
				
			||||||
 | 
								const path = here.join('.');
 | 
				
			||||||
 | 
								const pushRika = (actual: ExtendedType, excepted?: string | Miria | null) => rikas.push({
 | 
				
			||||||
 | 
									path,
 | 
				
			||||||
 | 
									excepted: excepted === undefined ? miria : excepted,
 | 
				
			||||||
 | 
									actual
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								const pushRikas = (iterable: Iterable<Rika>) => {
 | 
				
			||||||
 | 
									for (const rika of iterable)
 | 
				
			||||||
 | 
										rikas.push(rika);
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								keys[k] = inclusion;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!inclusion && !source[additional])
 | 
				
			||||||
 | 
									pushRika(v === null ? 'null' : typeof v, null);
 | 
				
			||||||
 | 
								else if (typeof x === 'undefined')
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								else if (typeof x === 'string')
 | 
				
			||||||
 | 
									pushRikas(Mika.validateFromString(v, x, here));
 | 
				
			||||||
 | 
								else if (v === undefined)
 | 
				
			||||||
 | 
									(x[optional] || pushRika(inclusion ? 'undefined' : 'unprovided'), $)();
 | 
				
			||||||
 | 
								else if (v === null)
 | 
				
			||||||
 | 
									(x[nullable] || pushRika('null'), $)();
 | 
				
			||||||
 | 
								else if (typeof v === 'object')
 | 
				
			||||||
 | 
									pushRikas(this.dive(v, x, here));
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									pushRika(typeof v);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const [k, v] of Object.entries(source as Miria).filter(([k]) => keys[k] === null)) {
 | 
				
			||||||
 | 
								const rika: Rika = {
 | 
				
			||||||
 | 
									path: [...location, k].join('.'),
 | 
				
			||||||
 | 
									excepted: v,
 | 
				
			||||||
 | 
									actual: 'unprovided'
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (typeof v === 'string')
 | 
				
			||||||
 | 
									(v.endsWith('?') || rikas.push(rika), $)();
 | 
				
			||||||
 | 
								else if (!v[optional])
 | 
				
			||||||
 | 
									rikas.push(rika);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return rikas;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Validates object.
 | 
				
			||||||
 | 
						 * @param x The source object.
 | 
				
			||||||
 | 
						 * @returns The difference points when the source object fails validation, otherwise null.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public validate(
 | 
				
			||||||
 | 
							x: object
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							const root = (actual: ExtendedType): Rika[] => [{
 | 
				
			||||||
 | 
								path: '',
 | 
				
			||||||
 | 
								excepted: this.miria,
 | 
				
			||||||
 | 
								actual
 | 
				
			||||||
 | 
							}];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (typeof x !== 'object')
 | 
				
			||||||
 | 
								return root(typeof x);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (x === null)
 | 
				
			||||||
 | 
								return this.miria[nullable] ? null : root('null');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const rikas = this.dive(x, this.miria);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return rikas.length ? rikas : null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/sanctuary/tsconfig.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/sanctuary/tsconfig.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						"extends": "../../tsconfig.json",
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
							"target": "esnext",
 | 
				
			||||||
 | 
							"strictNullChecks": true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue