mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-04-10 08:23:16 +00:00
feat: improve monorepo dev experience
This commit is contained in:
@@ -43,7 +43,7 @@
|
||||
"@types/cli-progress": "3.11.0",
|
||||
"@types/express": "4.17.17",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/json-schema": "7.0.11",
|
||||
"@types/json-schema": "7.0.14",
|
||||
"@types/junit-report-builder": "3.0.0",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/node": "18.15.3",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@openstapps/logger": "workspace:*",
|
||||
"@types/body-parser": "1.19.2",
|
||||
"@types/express": "4.17.17",
|
||||
"@types/json-schema": "7.0.11",
|
||||
"@types/json-schema": "7.0.14",
|
||||
"@types/morgan": "1.9.4",
|
||||
"body-parser": "1.20.2",
|
||||
"express": "4.18.2",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"@openstapps/easy-ast": "workspace:*",
|
||||
"@openstapps/logger": "workspace:*",
|
||||
"ajv": "8.12.0",
|
||||
"ajv-formats": "2.1.1",
|
||||
"better-ajv-errors": "1.2.0",
|
||||
"commander": "10.0.0",
|
||||
"deepmerge": "4.3.1",
|
||||
@@ -60,11 +61,10 @@
|
||||
"humanize-string": "3.0.0",
|
||||
"json-schema": "0.4.0",
|
||||
"mustache": "4.2.0",
|
||||
"openapi-types": "12.1.0",
|
||||
"openapi-types": "12.1.3",
|
||||
"plantuml-encoder": "1.4.0",
|
||||
"re2": "1.18.2",
|
||||
"toposort": "2.0.2",
|
||||
"ts-json-schema-generator": "1.2.0"
|
||||
"ts-json-schema-generator": "1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openstapps/eslint-config": "workspace:*",
|
||||
@@ -73,7 +73,7 @@
|
||||
"@types/chai": "4.3.5",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/glob": "8.0.1",
|
||||
"@types/json-schema": "7.0.11",
|
||||
"@types/json-schema": "7.0.14",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/mustache": "4.2.2",
|
||||
"@types/node": "18.15.3",
|
||||
|
||||
@@ -17,15 +17,17 @@ import {Command} from 'commander';
|
||||
import {existsSync, readFileSync, writeFileSync} from 'fs';
|
||||
import path from 'path';
|
||||
import {lightweightDefinitionsFromPath, lightweightProjectFromPath} from '@openstapps/easy-ast';
|
||||
import {openapi3Template} from './resources/openapi-303-template.js';
|
||||
import {gatherRouteInformation, generateOpenAPIForRoute} from './routes.js';
|
||||
import {Converter, getValidatableTypesInPath} from './schema.js';
|
||||
import {openapi3Template} from '../../openapi-generator/src/openapi-303-template.js';
|
||||
import {
|
||||
gatherRouteInformation,
|
||||
generateOpenAPIForRoute,
|
||||
} from '../../openapi-generator/src/generator/routes.js';
|
||||
import {Converter, getValidatableTypesInPath, mergeSchemas} from './schema.js';
|
||||
import {createDiagram, createDiagramFromString} from './uml/create-diagram.js';
|
||||
import {UMLConfig} from './uml/uml-config.js';
|
||||
import {capitalize} from './util/string.js';
|
||||
import {validateFiles, writeReport} from './validate.js';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {mkdir, readFile} from 'fs/promises';
|
||||
import {readFile} from 'fs/promises';
|
||||
|
||||
// handle unhandled promise rejections
|
||||
process.on('unhandledRejection', async (reason: unknown) => {
|
||||
@@ -52,56 +54,10 @@ commander.command('prototype <srcBundle> <out>').action(async (sourcePath, out)
|
||||
|
||||
commander
|
||||
.command('openapi <srcPath> <outDirPath>')
|
||||
.action(async (relativeSourceBundlePath, relativeOutDirectoryPath) => {
|
||||
// get absolute paths
|
||||
const sourcePath = path.resolve(relativeSourceBundlePath);
|
||||
const outDirectoryPath = path.resolve(relativeOutDirectoryPath);
|
||||
const outDirectorySchemasPath = path.join(outDirectoryPath, 'schema');
|
||||
.action(async (relativeSourceBundlePath, relativeOutDirectoryPath) => {});
|
||||
|
||||
// get information about routes
|
||||
const routes = await gatherRouteInformation(sourcePath);
|
||||
routes.sort((a, b) => a.route.urlPath.localeCompare(b.route.urlPath));
|
||||
|
||||
// change url path parameters to openapi notation
|
||||
for (const routeWithMetaInformation of routes) {
|
||||
routeWithMetaInformation.route.urlPath = routeWithMetaInformation.route.urlPath.replaceAll(
|
||||
/:\w+/g,
|
||||
(match: string) => `{${match.replace(':', '')}}`,
|
||||
);
|
||||
}
|
||||
|
||||
// keep openapi tags for routes that actually share url fragments
|
||||
let tagsToKeep = routes.map(routeWithMetaInformation =>
|
||||
capitalize(routeWithMetaInformation.route.urlPath.split('/')[1]),
|
||||
);
|
||||
tagsToKeep = tagsToKeep.filter(
|
||||
(element, i, array) => array.indexOf(element) === i && array.lastIndexOf(element) !== i,
|
||||
);
|
||||
|
||||
// initialize json output
|
||||
const output = openapi3Template;
|
||||
|
||||
// generate documentation for all routes
|
||||
for (const routeWithMetaInformation of routes) {
|
||||
routeWithMetaInformation.tags = [capitalize(routeWithMetaInformation.route.urlPath.split('/')[1])];
|
||||
|
||||
output.paths[routeWithMetaInformation.route.urlPath] = generateOpenAPIForRoute(
|
||||
routeWithMetaInformation,
|
||||
path.relative(relativeOutDirectoryPath, outDirectorySchemasPath),
|
||||
tagsToKeep,
|
||||
);
|
||||
}
|
||||
|
||||
// write openapi object to file (prettified)
|
||||
writeFileSync(path.join(outDirectoryPath, 'openapi.json'), JSON.stringify(output, undefined, 2));
|
||||
|
||||
Logger.ok(`OpenAPI representation resources written to ${outDirectoryPath} .`);
|
||||
});
|
||||
|
||||
commander.command('schema <srcPath> <schemaPath>').action(async (relativeSourcePath, relativeSchemaPath) => {
|
||||
// get absolute paths
|
||||
commander.command('schema <srcPath> <schemaPath>').action(async (relativeSourcePath, schemaPath) => {
|
||||
const absoluteSourcePath = path.resolve(relativeSourcePath);
|
||||
const schemaPath = path.resolve(relativeSchemaPath);
|
||||
|
||||
// initialize new core converter
|
||||
const coreConverter = new Converter(absoluteSourcePath);
|
||||
@@ -111,10 +67,6 @@ commander.command('schema <srcPath> <schemaPath>').action(async (relativeSourceP
|
||||
|
||||
Logger.info(`Found ${validatableTypes.length} type(s) to generate schemas for.`);
|
||||
|
||||
await mkdir(schemaPath, {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
Logger.info(`Trying to find a package.json for ${absoluteSourcePath}.`);
|
||||
|
||||
let packagePath = absoluteSourcePath;
|
||||
@@ -134,53 +86,12 @@ commander.command('schema <srcPath> <schemaPath>').action(async (relativeSourceP
|
||||
Logger.log(`Using ${coreVersion} as version for schemas.`);
|
||||
|
||||
// generate and write JSONSchema files for validatable types
|
||||
for (const type of validatableTypes) {
|
||||
const schema = coreConverter.getSchema(type, coreVersion);
|
||||
|
||||
const stringifiedSchema = JSON.stringify(schema, undefined, 2);
|
||||
|
||||
const file = path.join(schemaPath, `${type}.json`);
|
||||
|
||||
// write schema to file
|
||||
writeFileSync(file, stringifiedSchema);
|
||||
|
||||
Logger.info(`Generated schema for ${type} and saved to ${file}.`);
|
||||
}
|
||||
const schema = mergeSchemas(validatableTypes.map(type => coreConverter.getSchema(type, coreVersion)));
|
||||
writeFileSync(schemaPath, JSON.stringify(schema, undefined, 2));
|
||||
|
||||
Logger.ok(`Generated schemas for ${validatableTypes.length} type(s).`);
|
||||
});
|
||||
|
||||
commander
|
||||
.command('validate <schemaPath> <testPath> [reportPath]')
|
||||
.action(async (relativeSchemaPath, relativeTestPath, relativeReportPath) => {
|
||||
// get absolute paths
|
||||
const schemaPath = path.resolve(relativeSchemaPath);
|
||||
const testPath = path.resolve(relativeTestPath);
|
||||
|
||||
const errorsPerFile = await validateFiles(schemaPath, testPath);
|
||||
|
||||
let unexpected = false;
|
||||
for (const file in errorsPerFile) {
|
||||
if (!errorsPerFile.hasOwnProperty(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unexpected = unexpected || errorsPerFile[file].some(error => !error.expected);
|
||||
}
|
||||
|
||||
if (relativeReportPath !== undefined) {
|
||||
const reportPath = path.resolve(relativeReportPath);
|
||||
await writeReport(reportPath, errorsPerFile);
|
||||
}
|
||||
|
||||
if (unexpected) {
|
||||
await Logger.error('Unexpected errors occurred during validation');
|
||||
process.exit(1);
|
||||
} else {
|
||||
Logger.ok('Successfully finished validation.');
|
||||
}
|
||||
});
|
||||
|
||||
commander
|
||||
.command('plantuml <srcPath> <plantumlserver>')
|
||||
.option('--definitions <definitions>', 'Shows these specific definitions (class, interface or enum)', it =>
|
||||
|
||||
26
packages/core-tools/src/better-ajv-errors.d.ts
vendored
26
packages/core-tools/src/better-ajv-errors.d.ts
vendored
@@ -1,26 +0,0 @@
|
||||
declare module 'better-ajv-errors' {
|
||||
import type {ErrorObject} from 'ajv';
|
||||
|
||||
export interface IOutputError {
|
||||
start: {line: number; column: number; offset: number};
|
||||
// Optional for required
|
||||
end?: {line: number; column: number; offset: number};
|
||||
error: string;
|
||||
suggestion?: string;
|
||||
}
|
||||
|
||||
export interface IInputOptions {
|
||||
format?: 'cli' | 'js';
|
||||
indent?: number | null;
|
||||
|
||||
/** Raw JSON used when highlighting error location */
|
||||
json?: string | null;
|
||||
}
|
||||
|
||||
export default function <S, T, Options extends IInputOptions>(
|
||||
schema: S,
|
||||
data: T,
|
||||
errors: Array<ErrorObject>,
|
||||
options?: Options,
|
||||
): Options extends {format: 'js'} ? Array<IOutputError> : string;
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
export * from './validate.js';
|
||||
export * from './types/validator.js';
|
||||
|
||||
export * from './uml/uml-config.js';
|
||||
export * from './uml/create-diagram.js';
|
||||
|
||||
export * from './routes.js';
|
||||
export * from './types/routes.js';
|
||||
export * from '../../openapi-generator/src/generator/routes.js';
|
||||
export * from '../../openapi-generator/src/generator/types/routes.js';
|
||||
|
||||
export * from './schema.js';
|
||||
export * from './types/schema.js';
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {OpenAPIV3} from 'openapi-types';
|
||||
import {
|
||||
isLightweightClass,
|
||||
lightweightProjectFromPath,
|
||||
LightweightProjectWithIndex,
|
||||
} from '@openstapps/easy-ast';
|
||||
import {RouteInstanceWithMeta, RouteWithMetaInformation} from './types/routes.js';
|
||||
import {rejectNil} from './util/collections.js';
|
||||
import {capitalize} from './util/string.js';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Gather relevant information of routes
|
||||
*
|
||||
* This gathers the information for all routes that implement the abstract class SCAbstractRoute.
|
||||
* Furthermore it instantiates every route and adds it to the information.
|
||||
*/
|
||||
export async function gatherRouteInformation(path: string): Promise<RouteWithMetaInformation[]> {
|
||||
const project = new LightweightProjectWithIndex(lightweightProjectFromPath(path));
|
||||
|
||||
// find all classes that implement the SCAbstractRoute
|
||||
return rejectNil(
|
||||
await Promise.all(
|
||||
Object.values(project.definitions)
|
||||
.filter(isLightweightClass)
|
||||
.map(async node => {
|
||||
if (!node.extendedDefinitions?.some(it => it.referenceName === 'SCAbstractRoute')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const instantiatedRoute = (await project.instantiateDefinitionByName(
|
||||
node.name,
|
||||
)) as RouteInstanceWithMeta;
|
||||
// instantiate all errors
|
||||
instantiatedRoute.errors = await Promise.all(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
instantiatedRoute.errorNames.map(async (error: any) =>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
Object.assign((await project.instantiateDefinitionByName(error.name)) as object, {
|
||||
name: error.name,
|
||||
}),
|
||||
),
|
||||
);
|
||||
instantiatedRoute.responseBodyDescription =
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
||||
project.definitions[instantiatedRoute.responseBodyName]?.comment?.shortSummary!;
|
||||
instantiatedRoute.requestBodyDescription =
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
||||
project.definitions[instantiatedRoute.requestBodyName]?.comment?.shortSummary!;
|
||||
|
||||
return {
|
||||
description: {
|
||||
shortText: node.comment?.shortSummary,
|
||||
text: node.comment?.description,
|
||||
},
|
||||
name: node.name!,
|
||||
route: instantiatedRoute,
|
||||
};
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate documentation snippet for one route
|
||||
* @param routeWithInfo A route instance with its meta information
|
||||
* @param outDirectorySchemasPath Path to directory that will contain relevant schemas for the route
|
||||
* @param tagsToKeep Tags / keywords that can be used for grouping routes
|
||||
*/
|
||||
export function generateOpenAPIForRoute(
|
||||
routeWithInfo: RouteWithMetaInformation,
|
||||
outDirectorySchemasPath: string,
|
||||
tagsToKeep: string[],
|
||||
): OpenAPIV3.PathItemObject {
|
||||
const route = routeWithInfo.route;
|
||||
const openapiPath: OpenAPIV3.PathItemObject = {};
|
||||
|
||||
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods] = {
|
||||
summary: capitalize(routeWithInfo.description.shortText?.replace(/(Route to |Route for )/gim, '')),
|
||||
description: routeWithInfo.description.text,
|
||||
requestBody: {
|
||||
description: route.responseBodyDescription ?? undefined,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: path.join(outDirectorySchemasPath, `${route.requestBodyName}.json`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
parameters: [
|
||||
{
|
||||
name: 'X-StApps-Version',
|
||||
in: 'header',
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: '2.0.0',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
responses: {},
|
||||
tags: routeWithInfo.tags?.filter(value => tagsToKeep.includes(value)),
|
||||
};
|
||||
|
||||
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]!.responses![route.statusCodeSuccess] = {
|
||||
description: route.responseBodyDescription,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: path.join(outDirectorySchemasPath, `${route.responseBodyName}.json`),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const error of route.errors) {
|
||||
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]!.responses![error.statusCode] = {
|
||||
description:
|
||||
error.message ?? capitalize(error.name.replaceAll(/([A-Z][a-z])/g, ' $1').replace('SC ', '')),
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: path.join(outDirectorySchemasPath, `${error.name}.json`),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof route.obligatoryParameters === 'object') {
|
||||
for (const [parameter, schemaDefinition] of Object.entries(route.obligatoryParameters)) {
|
||||
const openapiParameter: OpenAPIV3.ParameterObject = {
|
||||
in: 'path',
|
||||
name: parameter,
|
||||
required: true,
|
||||
schema: {
|
||||
// TODO make this less of a hack and search copied schemas for the first occurring definition
|
||||
$ref: `schema/SCSearchResponse.json#/definitions/${schemaDefinition}`,
|
||||
},
|
||||
};
|
||||
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]?.parameters?.push(openapiParameter);
|
||||
}
|
||||
}
|
||||
|
||||
return openapiPath;
|
||||
}
|
||||
@@ -12,17 +12,18 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import Ajv from 'ajv';
|
||||
import {JSONSchema7 as JSONSchema} from 'json-schema';
|
||||
import {Config, DEFAULT_CONFIG, Definition, SchemaGenerator} from 'ts-json-schema-generator';
|
||||
import {createFormatter} from 'ts-json-schema-generator';
|
||||
import {createParser} from 'ts-json-schema-generator';
|
||||
import {createProgram} from 'ts-json-schema-generator';
|
||||
import Ajv, {JSONSchemaType} from 'ajv';
|
||||
import {
|
||||
Config,
|
||||
DEFAULT_CONFIG,
|
||||
SchemaGenerator,
|
||||
createParser,
|
||||
createFormatter,
|
||||
createProgram,
|
||||
} from 'ts-json-schema-generator';
|
||||
import {getTsconfigPath} from './common.js';
|
||||
import {definitionsOf, lightweightProjectFromPath} from '@openstapps/easy-ast';
|
||||
import {isSchemaWithDefinitions} from './util/guards.js';
|
||||
import path from 'path';
|
||||
import re2 from 're2';
|
||||
|
||||
/**
|
||||
* StAppsCore converter
|
||||
@@ -30,74 +31,35 @@ import re2 from 're2';
|
||||
* Converts TypeScript source files to JSON schema files
|
||||
*/
|
||||
export class Converter {
|
||||
/**
|
||||
* Generator instance
|
||||
*/
|
||||
private readonly generator: SchemaGenerator;
|
||||
|
||||
/**
|
||||
* Schema validator instance
|
||||
*/
|
||||
private readonly schemaValidator: Ajv.default;
|
||||
|
||||
/**
|
||||
* Create a new converter
|
||||
* @param projectPath Path to the project
|
||||
* @param sourcePath Path to optionally point to a different directory of / or single source file
|
||||
*/
|
||||
constructor(projectPath: string, sourcePath?: string) {
|
||||
// set config for schema generator
|
||||
const config: Config = {
|
||||
...DEFAULT_CONFIG,
|
||||
path: sourcePath,
|
||||
sortProps: true,
|
||||
topRef: false,
|
||||
tsconfig: path.join(getTsconfigPath(projectPath), 'tsconfig.json'),
|
||||
type: 'SC',
|
||||
};
|
||||
|
||||
// create TypeScript program from config
|
||||
const program = createProgram(config);
|
||||
|
||||
// create generator
|
||||
this.generator = new SchemaGenerator(program, createParser(program, config), createFormatter(config));
|
||||
|
||||
// create Ajv instance
|
||||
this.schemaValidator = new Ajv.default({code: {regExp: re2 as never}});
|
||||
this.schemaValidator = new Ajv.default();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema for specific StAppsCore type
|
||||
* @param type Type to get the schema for
|
||||
* @param version Version to set for the schema
|
||||
* @param _version Version to set for the schema
|
||||
* @returns Generated schema
|
||||
*/
|
||||
getSchema(type: string, version: string): JSONSchema {
|
||||
// generate schema for this file/type
|
||||
const schema: JSONSchema = this.generator.createSchema(type);
|
||||
|
||||
// set id of schema
|
||||
schema.$id = `https://core.stapps.tu-berlin.de/v${version}/lib/schema/${type}.json`;
|
||||
|
||||
if (isSchemaWithDefinitions(schema)) {
|
||||
const selfReference = {
|
||||
...schema,
|
||||
};
|
||||
|
||||
delete selfReference.$schema;
|
||||
delete selfReference.definitions;
|
||||
delete selfReference.$id;
|
||||
|
||||
// add self reference to definitions
|
||||
schema.definitions![`SC${type}`] = {
|
||||
...(selfReference as unknown as Definition),
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.schemaValidator.validateSchema(schema)) {
|
||||
throw new Error(`Generated schema for ${type} is invalid!`);
|
||||
}
|
||||
|
||||
getSchema(type: string, _version: string): JSONSchemaType<unknown> {
|
||||
const schema = this.generator.createSchema(type) as JSONSchemaType<unknown>;
|
||||
this.schemaValidator.validateSchema(schema, true);
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
@@ -110,3 +72,14 @@ export function getValidatableTypesInPath(path: string): string[] {
|
||||
.filter(type => !!type.comment?.tags?.find(it => it.name === 'validatable'))
|
||||
.map(type => type.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge multiple schemas
|
||||
*/
|
||||
export function mergeSchemas(schemas: JSONSchemaType<unknown>[]): JSONSchemaType<unknown> {
|
||||
const completeSchema = {definitions: {}} as JSONSchemaType<unknown>;
|
||||
for (const schema of schemas) {
|
||||
Object.assign(completeSchema.definitions!, schema.definitions);
|
||||
}
|
||||
return completeSchema;
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type SCRoute = any;
|
||||
|
||||
export interface RouteInstanceWithMeta extends SCRoute {
|
||||
/**
|
||||
* Possible errors on a route
|
||||
*/
|
||||
errors: SCErrorResponse[];
|
||||
|
||||
/**
|
||||
* Description of the request body
|
||||
*/
|
||||
requestBodyDescription: string;
|
||||
|
||||
/**
|
||||
* Description of the response body
|
||||
*/
|
||||
responseBodyDescription: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A route instance with its relevant meta information
|
||||
*/
|
||||
export interface RouteWithMetaInformation {
|
||||
/**
|
||||
* Description of the route
|
||||
*/
|
||||
description: {
|
||||
/**
|
||||
* Short text of the description - title
|
||||
*/
|
||||
shortText?: string;
|
||||
/**
|
||||
* Text of the description
|
||||
*/
|
||||
text?: string;
|
||||
};
|
||||
/**
|
||||
* Name of the route
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Instance of the route
|
||||
*/
|
||||
route: RouteInstanceWithMeta;
|
||||
|
||||
/**
|
||||
* Possible tags/keywords the route can be associated with
|
||||
*/
|
||||
tags?: [string];
|
||||
}
|
||||
|
||||
/**
|
||||
* A node with its relevant meta information
|
||||
*/
|
||||
export interface NodeWithMetaInformation {
|
||||
/**
|
||||
* Module the node belongs to
|
||||
*/
|
||||
module: string;
|
||||
|
||||
/**
|
||||
* Type of the node
|
||||
*/
|
||||
type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic error that can be returned by the backend if somethings fails during the processing of a request
|
||||
*/
|
||||
export interface SCErrorResponse extends Error {
|
||||
/**
|
||||
* Additional data that describes the error
|
||||
*/
|
||||
additionalData?: unknown;
|
||||
|
||||
/**
|
||||
* HTTP status code to return this error with
|
||||
*/
|
||||
statusCode: number;
|
||||
}
|
||||
@@ -12,9 +12,3 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Creates sentence cased string
|
||||
*/
|
||||
export function capitalize(string?: string): string {
|
||||
return `${string?.charAt(0).toUpperCase()}${string?.slice(1).toLowerCase()}`;
|
||||
}
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import Ajv from 'ajv';
|
||||
import betterAjvErrors, {IOutputError} from 'better-ajv-errors';
|
||||
import type {PathLike} from 'fs';
|
||||
import {readFile, writeFile} from 'fs/promises';
|
||||
import {JSONSchema7} from 'json-schema';
|
||||
import mustache from 'mustache';
|
||||
import {Schema} from 'ts-json-schema-generator';
|
||||
import {ExpectedValidationErrors, ValidationError, ValidationResult} from './types/validator.js';
|
||||
import {isThingWithType} from './util/guards.js';
|
||||
import path from 'path';
|
||||
import re2 from 're2';
|
||||
import {glob} from 'glob';
|
||||
import {fileURLToPath} from 'url';
|
||||
|
||||
/**
|
||||
* StAppsCore validator
|
||||
*/
|
||||
export class Validator {
|
||||
/**
|
||||
* JSON Schema Validator
|
||||
*/
|
||||
private readonly ajv = new Ajv.default({
|
||||
verbose: true,
|
||||
allowUnionTypes: true,
|
||||
code: {regExp: re2 as never},
|
||||
});
|
||||
|
||||
/**
|
||||
* Map of schema names to schemas
|
||||
*/
|
||||
private readonly schemas: {[type: string]: Schema} = {};
|
||||
|
||||
/**
|
||||
* A wrapper function for Ajv that transforms the error into the compatible old error
|
||||
* @param schema the schema that will be validated against
|
||||
* @param instance the instance that will be validated
|
||||
*/
|
||||
private ajvValidateWrapper(schema: JSONSchema7, instance: unknown): ValidationResult {
|
||||
return fromAjvResult(this.ajv.validate(schema, instance), schema, instance, this.ajv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed the schema files to the validator
|
||||
* @param schemaDirectory Path to directory that contains schema files
|
||||
*/
|
||||
public async addSchemas(schemaDirectory: string): Promise<string[]> {
|
||||
const searchGlob = path.posix.join(schemaDirectory.replaceAll(path.sep, path.posix.sep), '*.json');
|
||||
const schemaFiles = await glob(searchGlob);
|
||||
|
||||
if (schemaFiles.length === 0) {
|
||||
throw new Error(`No schema files in ${schemaDirectory.toString()}!`);
|
||||
}
|
||||
|
||||
Logger.log(`Adding schemas from ${schemaDirectory} to validator.`);
|
||||
|
||||
await Promise.all(
|
||||
schemaFiles.map(async (file: string) => {
|
||||
// read schema file
|
||||
const buffer = await readFile(file);
|
||||
|
||||
// add schema to map
|
||||
this.schemas[path.basename(file, '.json')] = JSON.parse(buffer.toString());
|
||||
|
||||
Logger.info(`Added ${file} to validator.`);
|
||||
}),
|
||||
);
|
||||
|
||||
return schemaFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates anything against a given schema name or infers schema name from object
|
||||
* @param instance Instance to validate
|
||||
* @param schema Name of schema to validate instance against or the schema itself
|
||||
*/
|
||||
public validate(instance: unknown, schema?: string | Schema): ValidationResult {
|
||||
if (schema === undefined) {
|
||||
if (isThingWithType(instance)) {
|
||||
// schema name can be inferred from type string
|
||||
const schemaSuffix = (instance as {type: string}).type
|
||||
.split(' ')
|
||||
.map((part: string) => {
|
||||
return part.slice(0, 1).toUpperCase() + part.slice(1);
|
||||
})
|
||||
.join('');
|
||||
const schemaName = `SC${schemaSuffix}`;
|
||||
|
||||
return this.validate(instance, schemaName);
|
||||
}
|
||||
throw new Error('Instance.type does not exist.');
|
||||
}
|
||||
if (typeof schema === 'string') {
|
||||
// if you want to access a schema that is contained in the validator object
|
||||
if (typeof this.schemas[schema] !== 'object') {
|
||||
throw new TypeError(`No schema available for ${schema}.`);
|
||||
}
|
||||
|
||||
// schema will be cached
|
||||
return this.ajvValidateWrapper(this.schemas[schema], instance);
|
||||
}
|
||||
|
||||
return this.ajvValidateWrapper(schema, instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ValidationResult from ajv
|
||||
*
|
||||
* Implemented for compatibility purposes
|
||||
* @param result the result, now a ValidationResult
|
||||
* @param schema the schema that has been validated against
|
||||
* @param instance the data that has been validated
|
||||
* @param ajvInstance the ajv instance with which the validation was done
|
||||
*/
|
||||
function fromAjvResult(
|
||||
result: boolean | PromiseLike<unknown>,
|
||||
schema: JSONSchema7,
|
||||
instance: unknown,
|
||||
ajvInstance: Ajv.default,
|
||||
): ValidationResult {
|
||||
const betterErrorObject: IOutputError[] | undefined = betterAjvErrors(
|
||||
schema,
|
||||
instance,
|
||||
ajvInstance.errors ?? [],
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
{format: 'js', indent: null},
|
||||
);
|
||||
|
||||
return {
|
||||
errors:
|
||||
ajvInstance.errors?.map((ajvError, index) => {
|
||||
const error: ValidationError = {
|
||||
dataPath: ajvError.instancePath,
|
||||
instance: instance,
|
||||
message: betterErrorObject?.[index]?.error ?? ajvError.message,
|
||||
name: ajvError.keyword,
|
||||
schemaPath: ajvError.schemaPath,
|
||||
suggestion: betterErrorObject?.[index]?.suggestion,
|
||||
};
|
||||
// (validationError as ValidationError).humanReadableError = betterErrorCLI?.[index] as unknown as string;
|
||||
|
||||
return error;
|
||||
}) ?? [],
|
||||
valid: typeof result === 'boolean' ? result : false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all test files in the given resources directory against schema files in the given (schema) directory
|
||||
* @param schemaDirectory The directory where the JSON schema files are
|
||||
* @param resourcesDirectory The directory where the test files are
|
||||
*/
|
||||
export async function validateFiles(
|
||||
schemaDirectory: string,
|
||||
resourcesDirectory: string,
|
||||
): Promise<ExpectedValidationErrors> {
|
||||
// instantiate new validator
|
||||
const v = new Validator();
|
||||
await v.addSchemas(schemaDirectory);
|
||||
|
||||
// get a list of files to test
|
||||
const testFiles = await glob(
|
||||
path.posix.join(resourcesDirectory.replaceAll(path.sep, path.posix.sep), '*.json'),
|
||||
{absolute: true},
|
||||
);
|
||||
|
||||
if (testFiles.length === 0) {
|
||||
throw new Error(`No test files in ${resourcesDirectory}!`);
|
||||
}
|
||||
|
||||
Logger.log(`Found ${testFiles.length} file(s) to test.`);
|
||||
|
||||
// map of errors per file
|
||||
const errors: ExpectedValidationErrors = {};
|
||||
|
||||
await Promise.all(
|
||||
testFiles.map(async (testFile: string) => {
|
||||
const testFileName = path.basename(testFile);
|
||||
|
||||
const buffer = await readFile(testFile);
|
||||
|
||||
// read test description from file
|
||||
const testDescription = JSON.parse(buffer.toString());
|
||||
|
||||
// validate instance from test description
|
||||
const result = v.validate(testDescription.instance, testDescription.schema);
|
||||
|
||||
// list of expected errors for this test description
|
||||
const expectedErrors: string[] = [...testDescription.errorNames];
|
||||
|
||||
// number of unexpected errors
|
||||
let unexpectedErrors = 0;
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
errors[testFileName] = [];
|
||||
|
||||
// iterate over errors
|
||||
for (const error of result.errors) {
|
||||
const errorIndex = expectedErrors.indexOf(error.name);
|
||||
let expected = false;
|
||||
|
||||
if (errorIndex >= 0) {
|
||||
expectedErrors.splice(errorIndex, 1);
|
||||
expected = true;
|
||||
} else {
|
||||
unexpectedErrors++;
|
||||
await Logger.error(`Unexpected error ${error.name} in ${testFile}`);
|
||||
}
|
||||
|
||||
// add error to list of errors
|
||||
errors[testFileName].push({
|
||||
...error,
|
||||
expected,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (expectedErrors.length > 0) {
|
||||
for (const error of expectedErrors) {
|
||||
await Logger.error(`Extraneous expected error '${error}' in ${testFile}.`);
|
||||
|
||||
errors[testFileName].push({
|
||||
dataPath: 'undefined',
|
||||
expected: false,
|
||||
instance: undefined,
|
||||
// instance: testDescription.instance,
|
||||
message: 'undefined',
|
||||
name: `expected ${error}`,
|
||||
schemaPath: 'undefined',
|
||||
suggestion: 'undefined',
|
||||
});
|
||||
}
|
||||
} else if (unexpectedErrors === 0) {
|
||||
Logger.info(`Successfully validated ${testFile}.`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a report for errors that occurred in validation
|
||||
* @param reportPath Path to write report to
|
||||
* @param errors Errors that occurred in validation
|
||||
*/
|
||||
export async function writeReport(reportPath: PathLike, errors: ExpectedValidationErrors): Promise<void> {
|
||||
let buffer = await readFile(
|
||||
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'resources', 'file.html.mustache'),
|
||||
);
|
||||
const fileTemplate = buffer.toString();
|
||||
|
||||
buffer = await readFile(
|
||||
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'resources', 'error.html.mustache'),
|
||||
);
|
||||
const errorTemplate = buffer.toString();
|
||||
|
||||
let output = '';
|
||||
|
||||
for (const fileName in errors) {
|
||||
if (!errors.hasOwnProperty(fileName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fileOutput = '';
|
||||
|
||||
for (const [index, error] of errors[fileName].entries()) {
|
||||
fileOutput += mustache.render(errorTemplate, {
|
||||
idx: index + 1,
|
||||
instance: JSON.stringify(error.instance, undefined, 2),
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
schemaPath: error.schemaPath,
|
||||
status: error.expected ? 'alert-success' : 'alert-danger',
|
||||
suggestion: error.suggestion,
|
||||
});
|
||||
}
|
||||
|
||||
output += mustache.render(fileTemplate, {
|
||||
errors: fileOutput,
|
||||
testFile: fileName,
|
||||
});
|
||||
}
|
||||
|
||||
buffer = await readFile(
|
||||
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'resources', 'report.html.mustache'),
|
||||
);
|
||||
const reportTemplate = buffer.toString();
|
||||
|
||||
await writeFile(
|
||||
reportPath,
|
||||
mustache.render(reportTemplate, {
|
||||
report: output,
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
|
||||
Logger.ok(`Wrote report to ${reportPath}.`);
|
||||
}
|
||||
199
packages/core-validator/README.md
Normal file
199
packages/core-validator/README.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# @openstapps/core-tools
|
||||
|
||||
[](https://gitlab.com/openstapps/core-tools/commits/master)
|
||||
[](https://npmjs.com/package/@openstapps/core-tools)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
[](https://openstapps.gitlab.io/core-tools)
|
||||
|
||||
Tools to convert and validate StAppsCore
|
||||
|
||||
## What are the tools for?
|
||||
|
||||
The StAppsCore Converter is a tool for converting SC-types (TypeScript) into JSON schema files.
|
||||
|
||||
JSON schema files are needed for run-time validation of SC-type objects, as this is a tedious task to do using SC-types defined in TypeScript (not possible without additional coding). That said, StAppsCore Converter practically prepares SC-types to be used for object validation (determining whether a JavaScript/JSON object is a valid object of the corresponding SC-type) using StAppsCore Validator.
|
||||
|
||||
The StAppsCore Validator is a tool for run-time validation of objects (determining whether a JavaScript/JSON object is a valid object of the corresponding SC-type. It consumes JSON schema files from StAppsCore as the definitions of SC-types against which are validated concrete (actual) objects (as an example SCDish object in the example below).
|
||||
|
||||
## Installation
|
||||
|
||||
Installation of the npm package (using `npm install`) makes the tool available as an executable with the name `openstapps-core-tools`.
|
||||
|
||||
## How to use the converter?
|
||||
|
||||
Add `@validatable` to the Typedoc comment of the types that you want to convert to JSONSchema.
|
||||
|
||||
The command `openstapps-core-tools` can then be called using these arguments:
|
||||
|
||||
```shell
|
||||
openstapps-core-tools schema <srcPath> <schemaPath>
|
||||
```
|
||||
|
||||
where:
|
||||
|
||||
- `<srcPath>` is path to the project (where used `*.ts` files are, e.g. `src/core`,
|
||||
- `<schemaPath>` is directory to save output files to, e.g. `lib/schema`.
|
||||
|
||||
Complete command with the example arguments is then:
|
||||
|
||||
```shell
|
||||
openstapps-core-tools schema src/core lib/schema
|
||||
```
|
||||
|
||||
Inside of a script in `package.json` or if the npm package is installed globally, the tool `stapps-convert` can be called without its local path (`node_modules/.bin`):
|
||||
|
||||
```shell
|
||||
openstapps-core-tools schema src/core lib/schema
|
||||
```
|
||||
|
||||
## How to use the validator?
|
||||
|
||||
### Using the validator programatically
|
||||
|
||||
```typescript
|
||||
import {Validator} from '@openstapps/core-tools/lib/validate';
|
||||
import {SCDish, SCThingType} from '@openstapps/core';
|
||||
import {ValidatorResult} from 'jsonschema';
|
||||
import {join} from 'path';
|
||||
|
||||
const objectToValidate: SCDish = {
|
||||
type: SCThingType.Dish,
|
||||
// more properties
|
||||
};
|
||||
|
||||
// instantiate a new validator
|
||||
const validator = new Validator();
|
||||
|
||||
// make the validator read the schema files
|
||||
validator.addSchemas(join('node_modules', '@openstapps', 'core', 'lib', 'schema')).then(() => {
|
||||
// validate an object
|
||||
const result: ValidatorResult = validator.validate(objectToValidate, 'SCDish');
|
||||
});
|
||||
```
|
||||
|
||||
#### Using validateFiles function
|
||||
|
||||
The JSON files passed to the validateFiles method have an added layer.
|
||||
That layer encapsulates the actual JSON data of the object to be verified and adds a property to enable true negative testing.
|
||||
|
||||
Your basic JSON object:
|
||||
|
||||
```json
|
||||
{
|
||||
"property1": "value1",
|
||||
"property2": "value2",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
JSON for validateFiles:
|
||||
|
||||
```json
|
||||
{
|
||||
"errorNames": [],
|
||||
"instance": {
|
||||
"property1": "value1",
|
||||
"property2": "value2",
|
||||
...
|
||||
},
|
||||
"schema": "NameOfSchema"
|
||||
}
|
||||
```
|
||||
|
||||
Where `errorNames` holds the string values of the name property of the expected ValidationErrors from JSON Schema. Empty array means no errors are expected.
|
||||
|
||||
`schema` holds the name of the schema to validate the instance against.
|
||||
|
||||
### How to use validator as a CLI tool (executable)?
|
||||
|
||||
The command `openstapps-core-tools` can then be called using these arguments:
|
||||
|
||||
```shell
|
||||
openstapps-core-tools validate <schemaPath> <testPath> [reportPath]
|
||||
```
|
||||
|
||||
where:
|
||||
|
||||
- `<schemaPath>` is a directory where JSON schema files are, e.g. `lib/schema`,
|
||||
- `<testPath>` is a directory where test files are, e.g. `src/test/resources`,
|
||||
- `[reportPath]` is a file where the HTML report of the validation will be saved to, e.g. `report.html` (optional argument - if it's not provided no report will be written).
|
||||
|
||||
Command with the example arguments is then for example:
|
||||
|
||||
```shell
|
||||
openstapps-core-tools validate lib/schema src/test/resources
|
||||
```
|
||||
|
||||
Inside of a script in `package.json` or if the npm package is installed globally, the tool `openstapps-validate` can be called without its local path (`node_modules/.bin`):
|
||||
|
||||
```shell
|
||||
openstapps-core-tools validate lib/schema src/test/resources report.html
|
||||
```
|
||||
|
||||
## Generate openapi JSON file for routes
|
||||
|
||||
To generate a openapi JSON file that represents the routes according to openapi version 3.0.3 use the following command.
|
||||
|
||||
```shell
|
||||
openstapps-core-tools openapi PATH/TO/CORE/lib PATH/TO/PUT/FILES/TO
|
||||
```
|
||||
|
||||
## How to use the UML generator
|
||||
|
||||
The UML Generator generates PlantUML from the project reflection of the source files. By default it will include externals, which will take considerably longer to execute, you can disable this behaviour via an option. It can help you to visually explore the data model or document a specific part.
|
||||
|
||||
You can either use the public PlantUML-server or start your own local instance. To run, restart or stop the container use the scripts provided in the `package.json`.
|
||||
|
||||
### Generating from source-files
|
||||
|
||||
```shell
|
||||
openstapps-core-tools plantuml PATH/TO/SOURCEFILES http://PLANTUMLSERVER
|
||||
```
|
||||
|
||||
Executing this command will generate a `.svg` file in your current working directory.
|
||||
|
||||
Multiple options can be set to enhance the diagram. By default all additional information other than the definitions are disabled. You can use:
|
||||
|
||||
- `--showProperties` to show all mandatory attributes of the classes and interfaces.
|
||||
- `--showOptionalProperties` to show all mandatory attributes of the classes and interfaces. `--showProperties` must be set!
|
||||
- `--showInheritedProperties` to show all inherited attributes of the classes and interfaces. `--showProperties` must be set!
|
||||
- `--showEnumValues` to show all enumeration and type (enumeration-like) values
|
||||
- `--showInheritance` to show the hierarchy of the classes and interfaces. Inherited attributes will only be shown in their parent.
|
||||
- `--showAssociations` to show all references of classes and interfaces between one another
|
||||
- `--excludeExternals` to exclude external definitions
|
||||
- `--definitions <definitons>` to show only specific definitions to reduce the output of the diagram. `<definitions>` is a comma seperated list of definitions.
|
||||
- `--outputFileName <fileName>` for a custom file name, the file extension will be added automatically (.svg). Otherwise a generic file with a timestamp will be generated into the execution directory. If a file with the same name already exists it will be overwritten!
|
||||
|
||||
The best way to explore models is to enable `--showInheritance` and `--showAssociations`. Start with just one definition in your `--definition <definitions>`-list, generate the diagram, look at it, add a new definition that you have seen to your command and generate anew.
|
||||
|
||||
#### Examples
|
||||
|
||||
Show the class hierarchy of the whole project:
|
||||
|
||||
```shell
|
||||
openstapps-core-tools plantuml PATH/TO/SRCDIR http://PLANTUMLSERVER --showInheritance
|
||||
```
|
||||
|
||||
Show the dish-module:
|
||||
|
||||
```shell
|
||||
openstapps-core-tools plantuml ../core http://localhost:8080 --showProperties --showOptionalProperties --showInheritance --showAssociations --showEnumValues --definitions SCDish,SCThingThatCanBeOfferedOffer
|
||||
```
|
||||
|
||||
### Generating from existing file
|
||||
|
||||
The plantuml code is persisted inside the generated file at the very bottom. You can tweak the model by using the function to generate UML from a PlantUML-file(simple text file). Extract the code (starting from `@startuml` to `@enduml`), edit it manually and execute this function.
|
||||
|
||||
```shell
|
||||
openstapps-core-tools plantuml-file /PATH/TO/Project.plantuml http://PLANTUMLSERVER OptionalCustomFileName
|
||||
```
|
||||
|
||||
Example-File-Content of Project.plantuml
|
||||
|
||||
```
|
||||
@startuml
|
||||
interface MyClass{
|
||||
myProperty: string
|
||||
}
|
||||
@enduml
|
||||
```
|
||||
14
packages/core-validator/compiler/append-schema-map.js
Normal file
14
packages/core-validator/compiler/append-schema-map.js
Normal file
@@ -0,0 +1,14 @@
|
||||
// @ts-check
|
||||
|
||||
import {writeFile, readFile} from 'fs/promises';
|
||||
|
||||
const schemaNames = Object.keys(
|
||||
JSON.parse(await readFile('schema/core.schema.json', 'utf8')).definitions,
|
||||
).filter(it => /^[a-z][0-9a-z<>]*$/i.test(it));
|
||||
const source =
|
||||
"import type * as core from '@openstapps/core';\n\n" +
|
||||
'export interface SchemaMap {\n' +
|
||||
schemaNames.map(name => ` '${name}': core.${name.replaceAll('<', '<core.')};`).join('\n') +
|
||||
'\n}\n';
|
||||
|
||||
await writeFile('schema/core.schema.d.ts', source, 'utf8');
|
||||
80
packages/core-validator/package.json
Normal file
80
packages/core-validator/package.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "@openstapps/core-validator",
|
||||
"description": "Validator for @openstapps/core",
|
||||
"version": "3.0.0",
|
||||
"type": "module",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": "git@gitlab.com:openstapps/openstapps.git",
|
||||
"author": "Thea Schöbl <dev@theaninova.de>",
|
||||
"keywords": [
|
||||
"StApps",
|
||||
"StAppsCore",
|
||||
"converter",
|
||||
"core",
|
||||
"validator"
|
||||
],
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"schema",
|
||||
"Dockerfile",
|
||||
"README.md",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup-node --dts",
|
||||
"docs": "typedoc --json ./docs/docs.json --options ../../typedoc.base.json src/index.ts",
|
||||
"format": "prettier . -c --ignore-path ../../.gitignore",
|
||||
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint --ext .ts src/",
|
||||
"lint:fix": "eslint --fix --ext .ts src/",
|
||||
"test": "c8 mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openstapps/core": "workspace:*",
|
||||
"ajv": "8.12.0",
|
||||
"ajv-formats": "2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openstapps/core-tools": "workspace:*",
|
||||
"@openstapps/eslint-config": "workspace:*",
|
||||
"@openstapps/prettier-config": "workspace:*",
|
||||
"@openstapps/tsconfig": "workspace:*",
|
||||
"@types/chai": "4.3.5",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/glob": "8.0.1",
|
||||
"@types/json-schema": "7.0.14",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/mustache": "4.2.2",
|
||||
"@types/node": "18.15.3",
|
||||
"c8": "7.14.0",
|
||||
"chai": "4.3.7",
|
||||
"mocha": "10.2.0",
|
||||
"mocha-junit-reporter": "2.2.0",
|
||||
"nock": "13.3.1",
|
||||
"ts-node": "10.9.1",
|
||||
"tsup": "6.7.0",
|
||||
"typedoc": "0.24.8",
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"tsup": {
|
||||
"entry": [
|
||||
"src/app.ts",
|
||||
"src/index.ts"
|
||||
],
|
||||
"sourcemap": true,
|
||||
"clean": true,
|
||||
"format": "esm",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"prettier": "@openstapps/prettier-config",
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"@openstapps"
|
||||
]
|
||||
},
|
||||
"eslintIgnore": [
|
||||
"resources"
|
||||
]
|
||||
}
|
||||
2
packages/core-validator/schema/.gitignore
vendored
Normal file
2
packages/core-validator/schema/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
core.schema.json
|
||||
core.schema.d.ts
|
||||
48
packages/core-validator/src/index.ts
Normal file
48
packages/core-validator/src/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import Ajv, {AnySchema} from 'ajv';
|
||||
import addFormats from 'ajv-formats';
|
||||
import schema from '../schema/core.schema.json';
|
||||
import {SchemaMap} from '../schema/core.schema.js';
|
||||
|
||||
export type RemoveNeverProperties<T> = {
|
||||
[K in Exclude<
|
||||
keyof T,
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
[P in keyof T]: T[P] extends Function ? P : never;
|
||||
}[keyof T]
|
||||
>]: T[K];
|
||||
};
|
||||
|
||||
export type IncludeProperty<T extends object, E> = RemoveNeverProperties<{
|
||||
[K in keyof T]: T[K] extends E ? T[K] : never;
|
||||
}>;
|
||||
|
||||
type NameOf<I extends SchemaMap[keyof SchemaMap]> = keyof IncludeProperty<SchemaMap, I>;
|
||||
|
||||
/**
|
||||
* StAppsCore validator
|
||||
*/
|
||||
export class Validator {
|
||||
private readonly ajv: Ajv.default;
|
||||
|
||||
constructor(additionalSchemas: AnySchema[] = []) {
|
||||
this.ajv = new Ajv.default({
|
||||
schemas: [schema, ...additionalSchemas],
|
||||
verbose: true,
|
||||
allowUnionTypes: true,
|
||||
});
|
||||
addFormats.default(this.ajv, {
|
||||
formats: ['date-time', 'time', 'uuid', 'duration'],
|
||||
mode: 'fast',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates anything against a given schema name or infers schema name from object
|
||||
* @param instance Instance to validate
|
||||
* @param schema Name of schema to validate instance against or the schema itself
|
||||
*/
|
||||
public validate<T>(instance: unknown, schema: NameOf<T>): instance is T {
|
||||
return this.ajv.validate(schema as string, instance);
|
||||
}
|
||||
}
|
||||
7
packages/core-validator/tsconfig.json
Normal file
7
packages/core-validator/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "@openstapps/tsconfig",
|
||||
"compilerOptions": {
|
||||
"noUnusedLocals": false,
|
||||
"stripInternal": true
|
||||
}
|
||||
}
|
||||
@@ -32,15 +32,12 @@
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup-node --dts && pnpm run mappings && pnpm run schema && pnpm run openapi && cp api-doc.html lib/api-doc.html",
|
||||
"build": "tsup-node --dts && cp api-doc.html lib/api-doc.html",
|
||||
"docs": "typedoc --json ./docs/docs.json --options ../../typedoc.base.json src/index.ts",
|
||||
"format": "prettier . -c --ignore-path ../../.gitignore",
|
||||
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint --ext .ts src/",
|
||||
"lint:fix": "eslint --fix --ext .ts src/",
|
||||
"mappings": "openstapps-es-mapping-generator mapping ../core/src -i minlength,pattern,see,tjs-format -m lib/mappings/mappings.json -a lib/mappings/aggregations.json",
|
||||
"openapi": "openstapps-core-tools openapi lib lib && node -e \"assert(JSON.parse(require('fs').readFileSync('lib/openapi.json', 'utf8')).paths['/search'] !== undefined)\"",
|
||||
"schema": "node --max-old-space-size=8192 --stack-size=10240 ./node_modules/@openstapps/core-tools/lib/app.js schema src lib/schema",
|
||||
"test": "c8 mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -56,12 +53,14 @@
|
||||
"@openstapps/easy-ast": "workspace:*",
|
||||
"@openstapps/es-mapping-generator": "workspace:*",
|
||||
"@openstapps/eslint-config": "workspace:*",
|
||||
"@openstapps/json-schema-generator": "workspace:*",
|
||||
"@openstapps/logger": "workspace:*",
|
||||
"@openstapps/openapi-generator": "workspace:*",
|
||||
"@openstapps/prettier-config": "workspace:*",
|
||||
"@openstapps/tsconfig": "workspace:*",
|
||||
"@types/chai": "4.3.5",
|
||||
"@types/json-patch": "0.0.30",
|
||||
"@types/json-schema": "7.0.11",
|
||||
"@types/json-schema": "7.0.14",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/node": "18.15.3",
|
||||
"c8": "7.14.0",
|
||||
@@ -97,21 +96,11 @@
|
||||
{
|
||||
"definedTags": [
|
||||
"internal",
|
||||
"aggregatable",
|
||||
"float",
|
||||
"indexable",
|
||||
"integer",
|
||||
"keyword",
|
||||
"sortable",
|
||||
"text",
|
||||
"date",
|
||||
"validatable",
|
||||
"filterable",
|
||||
"inheritTags",
|
||||
"elasticsearch",
|
||||
"minLength",
|
||||
"pattern",
|
||||
"typeparam",
|
||||
"TJS-format"
|
||||
"integer",
|
||||
"format"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
*/
|
||||
import {Polygon} from 'geojson';
|
||||
import {SCTranslations} from '../general/i18n.js';
|
||||
import {SCMap} from '../general/map.js';
|
||||
import {SCLanguageSetting, SCSetting, SCUserGroupSetting} from '../things/setting.js';
|
||||
import {SCAuthorizationProviderType} from './authorization.js';
|
||||
import {SCFeatureConfiguration} from './feature.js';
|
||||
@@ -89,7 +88,7 @@ export interface SCAppConfiguration {
|
||||
*
|
||||
* Mapping route -> page config
|
||||
*/
|
||||
aboutPages: SCMap<SCAboutPage>;
|
||||
aboutPages: Record<string, SCAboutPage>;
|
||||
|
||||
/**
|
||||
* Polygon that encapsulates the main campus
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {SCMap, SCRestrictedMap} from '../general/map.js';
|
||||
import {SCUuid} from '../general/uuid.js';
|
||||
import {SCSearchSortType} from '../protocol/search/sort.js';
|
||||
import {SCThingType} from '../things/abstract/thing.js';
|
||||
@@ -105,7 +104,7 @@ export type SCSearchContext = 'default' | 'dining' | 'place';
|
||||
/**
|
||||
* A boosting configuration for one context
|
||||
*/
|
||||
export type SCBackendConfigurationSearchBoostingContext = SCRestrictedMap<
|
||||
export type SCBackendConfigurationSearchBoostingContext = Record<
|
||||
SCSearchContext,
|
||||
SCBackendConfigurationSearchBoostingType[]
|
||||
>;
|
||||
@@ -128,10 +127,11 @@ export interface SCBackendConfigurationSearchBoostingType {
|
||||
* Value of the field that should be boosted by the given number
|
||||
* For example `"SS 2019": 2`
|
||||
*/
|
||||
fields?: SCMap<SCMap<number>>;
|
||||
fields?: Record<string, Record<string, number>>;
|
||||
|
||||
/**
|
||||
* Type of things the factor should be applied to
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType;
|
||||
}
|
||||
@@ -184,7 +184,7 @@ export interface SCBackendInternalConfiguration {
|
||||
/**
|
||||
* Configuration of the database
|
||||
*/
|
||||
export interface SCBackendConfigurationDatabaseConfiguration extends SCMap<unknown> {
|
||||
export interface SCBackendConfigurationDatabaseConfiguration extends Record<string, unknown> {
|
||||
/**
|
||||
* Name of the database used by the backend
|
||||
*/
|
||||
|
||||
@@ -12,20 +12,18 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {SCMap} from '../general/map.js';
|
||||
import {SCAuthorizationProviderType} from './authorization.js';
|
||||
|
||||
export interface SCFeatureConfiguration {
|
||||
/**
|
||||
* Map of extern services mapped by their name (statically)
|
||||
*/
|
||||
extern?: SCMap<SCFeatureConfigurationExtern>;
|
||||
extern?: Record<string, SCFeatureConfigurationExtern>;
|
||||
|
||||
/**
|
||||
* Map of plugins registered with the backend mapped by their name.
|
||||
*/
|
||||
plugins?: SCMap<SCFeatureConfigurationPlugin>;
|
||||
plugins?: Record<string, SCFeatureConfigurationPlugin>;
|
||||
}
|
||||
|
||||
export interface SCFeatureConfigurationPlugin {
|
||||
|
||||
@@ -96,7 +96,8 @@ export interface SCMonitoringMinimumLengthCondition {
|
||||
length: number;
|
||||
|
||||
/**
|
||||
* Type of the condition
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: 'MinimumLength';
|
||||
}
|
||||
@@ -111,7 +112,8 @@ export interface SCMonitoringMaximumLengthCondition {
|
||||
length: number;
|
||||
|
||||
/**
|
||||
* Type of the condition
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: 'MaximumLength';
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
export interface SCLanguage {
|
||||
/**
|
||||
* The two letter ISO 639-1 Code of the Language
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
code: SCLanguageCode;
|
||||
|
||||
/**
|
||||
* The Fulltext name of the Language
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
name: SCLanguageName;
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2022 Open StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Capsulation for a map with a string as key with values of type `T`
|
||||
*
|
||||
* !!! BEWARE !!!
|
||||
* Can't be refactored to a `Map<K, V>`, because it can't be serialized via JSON.stringify(map)
|
||||
* @typeparam T Can be any type.
|
||||
*/
|
||||
export interface SCMap<T> {
|
||||
/**
|
||||
* One value for each key
|
||||
*/
|
||||
[key: string]: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricted map with keys, limited to values of `U`, and corresponding values of type `T`
|
||||
*
|
||||
* !!! BEWARE !!!
|
||||
* Can't be refactored to a `Map<K, V>`, because it can't be serialized via JSON.stringify(map)
|
||||
* Also note, that this is a type not an interface
|
||||
* @typeparam U Must be a type the `in` operator can be applied to and contains only strings or numbers
|
||||
* @typeparam T Can be any type
|
||||
*/
|
||||
export type SCRestrictedMap<U extends string | number, T> = {
|
||||
/**
|
||||
* One value for each key
|
||||
*/
|
||||
[key in U]: T;
|
||||
};
|
||||
@@ -14,21 +14,18 @@
|
||||
*/
|
||||
/**
|
||||
* An ISO8601 date
|
||||
* @pattern ^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])(T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])([\.,][0-9]{0,7})?(Z|[+-](?:2[0-3]|[01][0-9])(:?[0-5][0-9])?)?)?$
|
||||
* @see https://gist.github.com/philipashlock/8830168
|
||||
* @date
|
||||
* @format date-time
|
||||
*/
|
||||
export type SCISO8601Date = string;
|
||||
|
||||
/**
|
||||
* An ISO8601 duration
|
||||
* @pattern ^(R\d*\/)?P(?:\d+(?:\.\d+)?Y)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?W)?(?:\d+(?:\.\d+)?D)?(?:T(?:\d+(?:\.\d+)?H)?(?:\d+(?:\.\d+)?M)?(?:\d+(?:\.\d+)?S)?)?$
|
||||
* @see https://gist.github.com/philipashlock/8830168
|
||||
* @format duration
|
||||
*/
|
||||
export type SCISO8601Duration = string;
|
||||
|
||||
/**
|
||||
* An ISO8601 time
|
||||
* @pattern ^(2[0-3]|[01][0-9]):?([0-5][0-9]):?([0-5][0-9])$
|
||||
* @format time
|
||||
*/
|
||||
export type SCISO8601Time = string;
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
*/
|
||||
/**
|
||||
* Universally unique identifier of the thing
|
||||
* @filterable
|
||||
* @pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
|
||||
* @see http://stackoverflow.com/questions/7905929/how-to-test-valid-uuid-guid
|
||||
* @elasticsearch filterable
|
||||
* @format uuid
|
||||
*/
|
||||
export type SCUuid = string;
|
||||
|
||||
@@ -11,7 +11,6 @@ export * from './config/monitoring.js';
|
||||
export * from './config/user.js';
|
||||
|
||||
export * from './general/i18n.js';
|
||||
export * from './general/map.js';
|
||||
export * from './general/namespaces.js';
|
||||
export * from './general/time.js';
|
||||
export * from './general/uuid.js';
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {SCMap} from '../general/map.js';
|
||||
import {SCErrorResponse} from './error.js';
|
||||
import {SCIndexRequest, SCIndexResponse, SCIndexRoute} from './routes/index.js';
|
||||
import {
|
||||
@@ -61,7 +60,7 @@ export interface SCRoute {
|
||||
/**
|
||||
* Map of obligatory parameters and their type that have to be set via the requested path
|
||||
*/
|
||||
obligatoryParameters?: SCMap<string>;
|
||||
obligatoryParameters?: Record<string, string>;
|
||||
|
||||
/**
|
||||
* Name of the type of the request body
|
||||
@@ -101,7 +100,7 @@ export abstract class SCAbstractRoute implements SCRoute {
|
||||
/**
|
||||
* @see SCRoute.obligatoryParameters
|
||||
*/
|
||||
obligatoryParameters?: SCMap<string>;
|
||||
obligatoryParameters?: Record<string, string>;
|
||||
|
||||
/**
|
||||
* @see SCRoute.requestBodyName
|
||||
@@ -127,7 +126,7 @@ export abstract class SCAbstractRoute implements SCRoute {
|
||||
* Get "compiled" URL path
|
||||
* @param parameters Parameters to compile URL path with
|
||||
*/
|
||||
public getUrlPath(parameters: SCMap<string> = {}): string {
|
||||
public getUrlPath(parameters: Record<string, string> = {}): string {
|
||||
let obligatoryParameters: string[] = [];
|
||||
|
||||
if (typeof this.obligatoryParameters === 'object') {
|
||||
|
||||
@@ -53,8 +53,8 @@ export interface SCBulkParameters {
|
||||
source: string;
|
||||
|
||||
/**
|
||||
* Type of things that are indexed in this bulk.
|
||||
*
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {StatusCodes} from 'http-status-codes';
|
||||
import {SCMap} from '../../general/map.js';
|
||||
import {SCInternalServerErrorResponse} from '../errors/internal-server-error.js';
|
||||
import {SCMethodNotAllowedErrorResponse} from '../errors/method-not-allowed.js';
|
||||
import {SCRequestBodyTooLargeErrorResponse} from '../errors/request-body-too-large.js';
|
||||
@@ -33,7 +32,7 @@ import {SCSearchResult} from '../search/result.js';
|
||||
* **CAUTION: This is limited to an amount of queries. Currently this limit is 5.**
|
||||
* @validatable
|
||||
*/
|
||||
export type SCMultiSearchRequest = SCMap<SCSearchQuery>;
|
||||
export type SCMultiSearchRequest = Record<string, SCSearchQuery>;
|
||||
|
||||
/**
|
||||
* A multi search response
|
||||
@@ -41,7 +40,7 @@ export type SCMultiSearchRequest = SCMap<SCSearchQuery>;
|
||||
* This is a map of [[SCSearchResponse]]s indexed by name
|
||||
* @validatable
|
||||
*/
|
||||
export type SCMultiSearchResponse = SCMap<SCSearchResult>;
|
||||
export type SCMultiSearchResponse = Record<string, SCSearchResult>;
|
||||
|
||||
/**
|
||||
* Route for submission of multiple search requests at once
|
||||
|
||||
@@ -12,10 +12,6 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {SCMap} from '../../general/map.js';
|
||||
/**
|
||||
* All available filter types
|
||||
*/
|
||||
import {SCSearchAvailabilityFilter} from './filters/availability.js';
|
||||
import {SCSearchBooleanFilter} from './filters/boolean.js';
|
||||
import {SCSearchDistanceFilter} from './filters/distance.js';
|
||||
@@ -53,7 +49,7 @@ export interface SCSearchAbstractFilter<T extends SCSearchAbstractFilterArgument
|
||||
/**
|
||||
* Arguments for the filter instruction
|
||||
*/
|
||||
export type SCSearchAbstractFilterArguments = SCMap<unknown>;
|
||||
export type SCSearchAbstractFilterArguments = Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Available filter instructions
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {SCMap} from '../../general/map.js';
|
||||
import {SCThingsField} from '../../meta.js';
|
||||
import {SCDistanceSort} from './sorts/distance.js';
|
||||
import {SCDucetSort} from './sorts/ducet.js';
|
||||
@@ -42,7 +41,7 @@ export interface SCSearchAbstractSort<T extends SCSearchAbstractSortArguments> {
|
||||
/**
|
||||
* Map of arguments for the sort instruction
|
||||
*/
|
||||
export interface SCSearchAbstractSortArguments extends SCMap<unknown> {
|
||||
export interface SCSearchAbstractSortArguments extends Record<string, unknown> {
|
||||
/**
|
||||
* Field to sort by
|
||||
*/
|
||||
|
||||
@@ -21,8 +21,7 @@ import {SCThing, SCThingMeta, SCThingWithoutReferences} from './thing.js';
|
||||
export interface SCAcademicDegreeWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* The achievable academic degree
|
||||
* @filterable
|
||||
* @sortable ducet
|
||||
* @elasticsearch filterable sortable:ducet
|
||||
*/
|
||||
academicDegree: string;
|
||||
|
||||
|
||||
@@ -22,33 +22,31 @@ import {SCThing, SCThingMeta, SCThingWithoutReferences} from './thing.js';
|
||||
export interface SCAcademicTermWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* Short name of the academic term, using the given pattern
|
||||
* @aggregatable
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch aggregatable filterable
|
||||
*/
|
||||
acronym: string;
|
||||
|
||||
/**
|
||||
* End date of the academic term
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
endDate: SCISO8601Date;
|
||||
|
||||
/**
|
||||
* End date of lectures in the academic term
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
eventsEndDate?: SCISO8601Date;
|
||||
|
||||
/**
|
||||
* Start date of lectures in the academic term
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
eventsStartDate?: SCISO8601Date;
|
||||
|
||||
/**
|
||||
* Start date of the academic term
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
startDate: SCISO8601Date;
|
||||
}
|
||||
|
||||
@@ -35,33 +35,28 @@ export interface SCCreativeWorkWithoutReferences extends SCThingWithoutReference
|
||||
|
||||
/**
|
||||
* Edition of a creative work (e.g. the book edition or edition of an article)
|
||||
* @keyword
|
||||
*/
|
||||
edition?: string;
|
||||
|
||||
/**
|
||||
* Date (in text form) the creative work was published for the first time
|
||||
* @keyword
|
||||
*/
|
||||
firstPublished?: string;
|
||||
|
||||
/**
|
||||
* Languages this creative work is written/recorded/... in
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
inLanguage?: SCLanguageCode;
|
||||
|
||||
/**
|
||||
* Keywords of the creative work
|
||||
* @aggregatable
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch aggregatable filterable
|
||||
*/
|
||||
keywords?: string[];
|
||||
|
||||
/**
|
||||
* Date (in text form) the creative work was most recently
|
||||
* @keyword
|
||||
*/
|
||||
lastPublished?: string;
|
||||
|
||||
@@ -112,7 +107,6 @@ export interface SCCreativeWork extends SCCreativeWorkWithoutReferences, SCThing
|
||||
export interface SCCreativeWorkTranslatableProperties extends SCThingTranslatableProperties {
|
||||
/**
|
||||
* Translation of the keywords of the creative work
|
||||
* @keyword
|
||||
*/
|
||||
keywords?: string[];
|
||||
}
|
||||
|
||||
@@ -39,37 +39,37 @@ export interface SCGeoInformation {
|
||||
export interface SCPostalAddress {
|
||||
/**
|
||||
* Country of the address
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
addressCountry: string;
|
||||
|
||||
/**
|
||||
* City of the address
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
addressLocality: string;
|
||||
|
||||
/**
|
||||
* State of the address
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
addressRegion?: string;
|
||||
|
||||
/**
|
||||
* Zip code of the address
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
postalCode: string;
|
||||
|
||||
/**
|
||||
* Optional post box number
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
postOfficeBoxNumber?: string;
|
||||
|
||||
/**
|
||||
* Street of the address - with house number!
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
streetAddress: string;
|
||||
}
|
||||
@@ -94,7 +94,6 @@ export interface SCPlaceWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* Opening hours of the place
|
||||
* @see http://wiki.openstreetmap.org/wiki/Key:opening_hours/specification
|
||||
* @keyword
|
||||
*/
|
||||
openingHours?: string;
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ import {SCISO8601Date} from '../../general/time.js';
|
||||
|
||||
/**
|
||||
* Date Range
|
||||
*
|
||||
* CAUTION: Changing the name requires changes in the core-tools premaps
|
||||
*/
|
||||
export type SCISO8601DateRange = SCRange<SCISO8601Date>;
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ export interface SCSaveableThing extends SCSaveableThingWithoutReferences, SCThi
|
||||
*/
|
||||
data: SCIndexableThings;
|
||||
/**
|
||||
* Type of the origin
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
origin: SCThingUserOrigin;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export type SCThingThatAcceptsPaymentsAcceptedPayments = 'cash' | 'credit' | 'ca
|
||||
export interface SCThingThatAcceptsPaymentsWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* Accepted payments of the place
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
paymentsAccepted?: SCThingThatAcceptsPaymentsAcceptedPayments[];
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@ import {SCThing, SCThingMeta, SCThingTranslatableProperties, SCThingWithoutRefer
|
||||
export interface SCPriceGroup {
|
||||
/**
|
||||
* Default price of the thing
|
||||
* @sortable price
|
||||
* @float
|
||||
* @elasticsearch sortable:price
|
||||
*/
|
||||
default: number;
|
||||
}
|
||||
@@ -37,22 +36,19 @@ export interface SCPriceGroup {
|
||||
export interface SCAcademicPriceGroup extends SCPriceGroup {
|
||||
/**
|
||||
* Price for employees
|
||||
* @sortable price
|
||||
* @float
|
||||
* @elasticsearch sortable:price
|
||||
*/
|
||||
employee?: number;
|
||||
|
||||
/**
|
||||
* Price for guests
|
||||
* @sortable price
|
||||
* @float
|
||||
* @elasticsearch sortable:price
|
||||
*/
|
||||
guest?: number;
|
||||
|
||||
/**
|
||||
* Price for students
|
||||
* @sortable price
|
||||
* @float
|
||||
* @elasticsearch sortable:price
|
||||
*/
|
||||
student?: number;
|
||||
}
|
||||
@@ -115,7 +111,6 @@ export interface SCThingThatCanBeOfferedOffer<T extends SCPriceGroup> extends SC
|
||||
export interface SCThingThatCanBeOfferedTranslatableProperties extends SCThingTranslatableProperties {
|
||||
/**
|
||||
* Availability of an offer
|
||||
* @keyword
|
||||
*/
|
||||
'offers[].availability'?: string;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {SCMetaTranslations, SCTranslations} from '../../general/i18n.js';
|
||||
import {SCMap} from '../../general/map.js';
|
||||
import {SCThing, SCThingMeta, SCThingTranslatableProperties, SCThingWithoutReferences} from './thing.js';
|
||||
|
||||
/**
|
||||
@@ -26,9 +25,7 @@ export interface SCThingWithCategoriesWithoutReferences<T, U extends SCThingWith
|
||||
extends SCThingWithoutReferences {
|
||||
/**
|
||||
* Categories of a thing with categories
|
||||
* @sortable ducet
|
||||
* @aggregatable
|
||||
* @filterable
|
||||
* @elasticsearch aggregatable sortable:ducet filterable
|
||||
*/
|
||||
categories: T[];
|
||||
|
||||
@@ -37,7 +34,7 @@ export interface SCThingWithCategoriesWithoutReferences<T, U extends SCThingWith
|
||||
*
|
||||
* A map from categories to their specific values.
|
||||
*/
|
||||
categorySpecificValues?: SCMap<U>;
|
||||
categorySpecificValues?: Record<string, U>;
|
||||
|
||||
/**
|
||||
* Translated fields of a thing with categories
|
||||
@@ -62,46 +59,42 @@ export interface SCThingWithCategories<T, U extends SCThingWithCategoriesSpecifi
|
||||
*/
|
||||
export interface SCThingWithCategoriesTranslatableProperties extends SCThingTranslatableProperties {
|
||||
/**
|
||||
* translations of the categories of a thing with categories
|
||||
* @sortable ducet
|
||||
* translations of the categories for a thing with categories
|
||||
* @elasticsearch filterable sortable:ducet
|
||||
*/
|
||||
categories?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Category specific values of a thing with categories
|
||||
* Category-specific values of a thing with categories
|
||||
*
|
||||
* This interface contains properties that can be specific to a certain category.
|
||||
*/
|
||||
export interface SCThingWithCategoriesSpecificValues {
|
||||
/**
|
||||
* Category specific alternate names of a thing
|
||||
* @keyword
|
||||
* Category-specific alternate names of a thing
|
||||
*/
|
||||
alternateNames?: string[];
|
||||
|
||||
/**
|
||||
* Category specific description of a thing
|
||||
* @text
|
||||
* Category-specific description of a thing
|
||||
* @elasticsearch text
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* URL of a category specific image of a thing
|
||||
* @keyword
|
||||
* URL of a category-specific image of a thing
|
||||
*/
|
||||
image?: string;
|
||||
|
||||
/**
|
||||
* Category specific name of a thing
|
||||
* @sortable ducet
|
||||
* @text
|
||||
* @elasticsearch text sortable:ducet
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* Category specific URL of a thing
|
||||
* @keyword
|
||||
* Category-specific URL of a thing
|
||||
*/
|
||||
url?: string;
|
||||
}
|
||||
@@ -130,7 +123,7 @@ export class SCThingWithCategoriesWithoutReferencesMeta<T, U extends SCThingWith
|
||||
};
|
||||
|
||||
/**
|
||||
* Translations of values of fields
|
||||
* Translations of field values
|
||||
*/
|
||||
fieldValueTranslations = {
|
||||
de: {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {SCMetaTranslations, SCTranslations} from '../../general/i18n.js';
|
||||
import {SCMap} from '../../general/map.js';
|
||||
import {SCISO8601Date} from '../../general/time.js';
|
||||
import {SCUuid} from '../../general/uuid.js';
|
||||
import {SCOrganizationWithoutReferences} from '../organization.js';
|
||||
@@ -62,37 +61,33 @@ export enum SCThingType {
|
||||
export interface SCThingWithoutReferences {
|
||||
/**
|
||||
* Alternate names of the thing
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
alternateNames?: string[];
|
||||
|
||||
/**
|
||||
* Description of the thing
|
||||
* @minLength 1
|
||||
* @text
|
||||
* @elasticsearch text
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* The identifier property represents any kind of additional identifier for any kind of SCThing
|
||||
*
|
||||
* E.g. GTIN codes, UUIDs, Database IDs etc.
|
||||
* E.g., GTIN codes, UUIDs, Database IDs, etc.
|
||||
*/
|
||||
identifiers?: SCMap<string>;
|
||||
identifiers?: Record<string, string>;
|
||||
|
||||
/**
|
||||
* URL of an image of the thing
|
||||
* @keyword
|
||||
* URL to an image of the thing
|
||||
*/
|
||||
image?: string;
|
||||
|
||||
/**
|
||||
* Name of the thing
|
||||
* @filterable
|
||||
* @minLength 1
|
||||
* @sortable ducet
|
||||
* @text
|
||||
* @elasticsearch text filterable sortable:ducet
|
||||
*/
|
||||
name: string;
|
||||
|
||||
@@ -111,10 +106,8 @@ export interface SCThingWithoutReferences {
|
||||
translations?: SCTranslations<SCThingTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the thing
|
||||
* @sortable ducet
|
||||
* @filterable
|
||||
* @aggregatable global
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType;
|
||||
|
||||
@@ -159,7 +152,8 @@ export interface SCThingOrigin {
|
||||
modified?: SCISO8601Date;
|
||||
|
||||
/**
|
||||
* Type of the origin
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingOriginType;
|
||||
}
|
||||
@@ -175,7 +169,7 @@ export interface SCThingRemoteOrigin extends SCThingOrigin {
|
||||
|
||||
/**
|
||||
* Name of the origin
|
||||
* @text
|
||||
* @elasticsearch text
|
||||
*/
|
||||
name: string;
|
||||
|
||||
@@ -192,7 +186,8 @@ export interface SCThingRemoteOrigin extends SCThingOrigin {
|
||||
responsibleEntity?: SCPersonWithoutReferences | SCOrganizationWithoutReferences;
|
||||
|
||||
/**
|
||||
* Type of the origin
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingOriginType.Remote;
|
||||
|
||||
@@ -217,7 +212,8 @@ export interface SCThingUserOrigin extends SCThingOrigin {
|
||||
deleted?: boolean;
|
||||
|
||||
/**
|
||||
* Type of the origin
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingOriginType.User;
|
||||
|
||||
@@ -233,13 +229,12 @@ export interface SCThingUserOrigin extends SCThingOrigin {
|
||||
export interface SCThingTranslatableProperties {
|
||||
/**
|
||||
* Translation of the description of the thing
|
||||
* @text
|
||||
* @elasticsearch text
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* Translation of the name of the thing
|
||||
* @sortable ducet
|
||||
* @text
|
||||
* @elasticsearch text sortable:ducet
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
@@ -254,7 +249,7 @@ export interface SCThingTranslatableProperties {
|
||||
export interface SCThingTranslatablePropertyOrigin {
|
||||
/**
|
||||
* Translation of the name of the origin
|
||||
* @text
|
||||
* @elasticsearch text
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
@@ -31,16 +31,13 @@ export interface SCAcademicEventWithoutReferences
|
||||
SCThingWithCategoriesWithoutReferences<SCAcademicEventCategories, SCThingWithCategoriesSpecificValues> {
|
||||
/**
|
||||
* Majors of the academic event that this event belongs to
|
||||
* @aggregatable
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable aggregatable
|
||||
*/
|
||||
majors?: string[];
|
||||
|
||||
/**
|
||||
* Original unmapped category from the source of the academic event
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
originalCategory?: string;
|
||||
|
||||
@@ -50,7 +47,8 @@ export interface SCAcademicEventWithoutReferences
|
||||
translations?: SCTranslations<SCAcademicEventTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of an academic event
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.AcademicEvent;
|
||||
}
|
||||
@@ -58,7 +56,7 @@ export interface SCAcademicEventWithoutReferences
|
||||
/**
|
||||
* An academic event
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCAcademicEvent
|
||||
extends SCEvent,
|
||||
@@ -70,7 +68,8 @@ export interface SCAcademicEvent
|
||||
translations?: SCTranslations<SCAcademicEventTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of an academic event
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.AcademicEvent;
|
||||
}
|
||||
@@ -101,13 +100,11 @@ export type SCAcademicEventCategories =
|
||||
export interface SCAcademicEventTranslatableProperties extends SCThingWithCategoriesTranslatableProperties {
|
||||
/**
|
||||
* Translations of the majors of the academic event that this event belongs to
|
||||
* @keyword
|
||||
*/
|
||||
majors?: string[];
|
||||
|
||||
/**
|
||||
* Translation of the original unmapped category from the source of the academic event
|
||||
* @keyword
|
||||
*/
|
||||
originalCategory?: string;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export interface SCArticleWithoutReferences
|
||||
SCThingWithCategoriesWithoutReferences<SCArticleCategories, SCThingWithCategoriesSpecificValues> {
|
||||
/**
|
||||
* Article itself as markdown
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
articleBody?: string;
|
||||
|
||||
@@ -64,7 +64,8 @@ export interface SCArticleWithoutReferences
|
||||
translations?: SCTranslations<SCArticleTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of an article
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Article;
|
||||
}
|
||||
@@ -72,7 +73,7 @@ export interface SCArticleWithoutReferences
|
||||
/**
|
||||
* An article
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCArticle
|
||||
extends SCCreativeWork,
|
||||
@@ -93,7 +94,8 @@ export interface SCArticle
|
||||
translations?: SCTranslations<SCArticleTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of an article
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Article;
|
||||
}
|
||||
@@ -107,7 +109,7 @@ export interface SCArticleTranslatableProperties
|
||||
SCCreativeWorkTranslatableProperties {
|
||||
/**
|
||||
* Translation of the article itself as markdown
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
articleBody?: string[];
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ export interface SCAssessmentWithoutReferences
|
||||
|
||||
/**
|
||||
* ECTS (credit-points)
|
||||
* @float
|
||||
*/
|
||||
ects?: number;
|
||||
|
||||
@@ -72,7 +71,8 @@ export interface SCAssessmentWithoutReferences
|
||||
translations?: SCTranslations<SCAssessmentTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of an assessment
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Assessment;
|
||||
}
|
||||
@@ -101,7 +101,8 @@ export interface SCAssessment
|
||||
translations?: SCTranslations<SCAssessmentTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of an assessment
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Assessment;
|
||||
}
|
||||
|
||||
@@ -73,8 +73,7 @@ export interface SCBookWithoutReferences
|
||||
|
||||
/**
|
||||
* ISBNs of a book
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
ISBNs?: string[];
|
||||
|
||||
@@ -90,7 +89,8 @@ export interface SCBookWithoutReferences
|
||||
translations?: SCTranslations<SCBookTranslatableFields>;
|
||||
|
||||
/**
|
||||
* Type of a book
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Book;
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export interface SCBookWithoutReferences
|
||||
/**
|
||||
* A book
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCBook
|
||||
extends SCCreativeWork,
|
||||
@@ -110,7 +110,8 @@ export interface SCBook
|
||||
translations?: SCTranslations<SCBookTranslatableFields>;
|
||||
|
||||
/**
|
||||
* Type of a book
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Book;
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@ export interface SCBuildingWithoutReferences
|
||||
SCPlaceWithoutReferences {
|
||||
/**
|
||||
* List of floor names of the place
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
floors?: string[];
|
||||
|
||||
@@ -54,7 +53,8 @@ export interface SCBuildingWithoutReferences
|
||||
translations?: SCTranslations<SCBuildingTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the building
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Building;
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export interface SCBuildingWithoutReferences
|
||||
/**
|
||||
* A building
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCBuilding
|
||||
extends SCBuildingWithoutReferences,
|
||||
@@ -74,7 +74,8 @@ export interface SCBuilding
|
||||
translations?: SCTranslations<SCBuildingTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the building
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Building;
|
||||
}
|
||||
|
||||
@@ -32,13 +32,14 @@ export interface SCCatalogWithoutReferences
|
||||
* Level of the catalog (0 for 'root catalog', 1 for its subcatalog, 2 for its subcatalog etc.)
|
||||
*
|
||||
* Needed for keeping order in catalog inheritance array.
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
* @integer
|
||||
*/
|
||||
level: number;
|
||||
|
||||
/**
|
||||
* Type of a catalog
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Catalog;
|
||||
}
|
||||
@@ -46,7 +47,7 @@ export interface SCCatalogWithoutReferences
|
||||
/**
|
||||
* A catalog
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCCatalog
|
||||
extends SCCatalogWithoutReferences,
|
||||
@@ -73,7 +74,8 @@ export interface SCCatalog
|
||||
translations?: SCTranslations<SCThingWithCategoriesTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a catalog
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Catalog;
|
||||
}
|
||||
|
||||
@@ -39,13 +39,14 @@ export interface SCCertificationWithoutReferences
|
||||
translations?: SCTranslations<SCCertificationTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of certification
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Certification;
|
||||
}
|
||||
|
||||
/**
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
* @validatable
|
||||
*/
|
||||
export interface SCCertification
|
||||
@@ -63,7 +64,8 @@ export interface SCCertification
|
||||
translations?: SCTranslations<SCCertificationTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of certification
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Certification;
|
||||
}
|
||||
|
||||
@@ -23,37 +23,33 @@ import {SCRoomWithoutReferences} from './room.js';
|
||||
export interface SCContactPointWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* E-mail at the work location
|
||||
* @keyword
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* Fax number at the work location
|
||||
* @keyword
|
||||
*/
|
||||
faxNumber?: string;
|
||||
|
||||
/**
|
||||
* Office hours for contacting someone at the work location
|
||||
* @see http://wiki.openstreetmap.org/wiki/Key:opening_hours/specification
|
||||
* @keyword
|
||||
*/
|
||||
officeHours?: string;
|
||||
|
||||
/**
|
||||
* Contact number at the work location
|
||||
* @keyword
|
||||
*/
|
||||
telephone?: string;
|
||||
|
||||
/**
|
||||
* Type of a contact point
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.ContactPoint;
|
||||
|
||||
/**
|
||||
* URL at the work location
|
||||
* @keyword
|
||||
*/
|
||||
url?: string;
|
||||
}
|
||||
@@ -62,7 +58,7 @@ export interface SCContactPointWithoutReferences extends SCThingWithoutReference
|
||||
* A contact point
|
||||
* @see http://schema.org/ContactPoint
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCContactPoint extends SCContactPointWithoutReferences, SCThing {
|
||||
/**
|
||||
@@ -71,7 +67,8 @@ export interface SCContactPoint extends SCContactPointWithoutReferences, SCThing
|
||||
areaServed?: SCRoomWithoutReferences;
|
||||
|
||||
/**
|
||||
* Type of a contact point
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.ContactPoint;
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@ export interface SCCourseOfStudyWithoutReferences
|
||||
|
||||
/**
|
||||
* The modes the course of study is offered in
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
mode?: SCCourseOfStudyMode;
|
||||
|
||||
/**
|
||||
* The time modes the course of study is offered in
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
timeMode?: SCCourseOfStudyTimeMode;
|
||||
|
||||
@@ -59,7 +59,8 @@ export interface SCCourseOfStudyWithoutReferences
|
||||
translations?: SCTranslations<SCCourseOfStudyTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the course of study
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.CourseOfStudy;
|
||||
}
|
||||
@@ -67,7 +68,7 @@ export interface SCCourseOfStudyWithoutReferences
|
||||
/**
|
||||
* A course of study
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCCourseOfStudy
|
||||
extends SCCourseOfStudyWithoutReferences,
|
||||
@@ -95,7 +96,8 @@ export interface SCCourseOfStudy
|
||||
translations?: SCTranslations<SCCourseOfStudyTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the course of study
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.CourseOfStudy;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import {SCSportCourseWithoutReferences} from './sport-course.js';
|
||||
export interface SCSportCoursePriceGroup extends SCAcademicPriceGroup {
|
||||
/**
|
||||
* Price for alumnis
|
||||
* @float
|
||||
*/
|
||||
alumni?: number;
|
||||
}
|
||||
@@ -44,7 +43,7 @@ export interface SCSportCoursePriceGroup extends SCAcademicPriceGroup {
|
||||
export interface SCDateSeriesWithoutReferences extends SCThingThatCanBeOfferedWithoutReferences {
|
||||
/**
|
||||
* Dates of the date series that are initially planned to be held
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
dates: SCISO8601Date[];
|
||||
|
||||
@@ -60,7 +59,7 @@ export interface SCDateSeriesWithoutReferences extends SCThingThatCanBeOfferedWi
|
||||
|
||||
/**
|
||||
* Frequency of the date series
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
repeatFrequency?: SCISO8601Duration;
|
||||
|
||||
@@ -70,7 +69,8 @@ export interface SCDateSeriesWithoutReferences extends SCThingThatCanBeOfferedWi
|
||||
translations?: SCTranslations<SCDateSeriesTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a date series
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.DateSeries;
|
||||
}
|
||||
@@ -78,7 +78,7 @@ export interface SCDateSeriesWithoutReferences extends SCThingThatCanBeOfferedWi
|
||||
/**
|
||||
* A date series
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCDateSeries
|
||||
extends SCDateSeriesWithoutReferences,
|
||||
@@ -100,7 +100,8 @@ export interface SCDateSeries
|
||||
translations?: SCTranslations<SCDateSeriesTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a date series
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.DateSeries;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ export interface SCDiffWithoutReferences extends SCThingWithoutReferences {
|
||||
dateCreated: SCISO8601Date;
|
||||
|
||||
/**
|
||||
* Type of a diff
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Diff;
|
||||
}
|
||||
@@ -54,7 +55,8 @@ export interface SCDiff extends SCDiffWithoutReferences, SCThing {
|
||||
object: SCIndexableThings;
|
||||
|
||||
/**
|
||||
* Type of a diff
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Diff;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,7 @@ export interface SCDishWithoutReferences
|
||||
SCThingWithCategoriesWithoutReferences<SCDishCategories, SCThingWithCategoriesSpecificValues> {
|
||||
/**
|
||||
* Additives of the dish
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
additives?: string[];
|
||||
|
||||
@@ -64,7 +63,8 @@ export interface SCDishWithoutReferences
|
||||
translations?: SCTranslations<SCDishTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a dish
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Dish;
|
||||
}
|
||||
@@ -72,7 +72,7 @@ export interface SCDishWithoutReferences
|
||||
/**
|
||||
* A dish
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCDish
|
||||
extends SCDishWithoutReferences,
|
||||
@@ -94,7 +94,8 @@ export interface SCDish
|
||||
translations?: SCTranslations<SCDishTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a dish
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Dish;
|
||||
}
|
||||
@@ -104,8 +105,7 @@ export interface SCDishTranslatableProperties
|
||||
SCThingThatCanBeOfferedTranslatableProperties {
|
||||
/**
|
||||
* Additives of the dish
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
additives?: string[];
|
||||
/**
|
||||
@@ -120,14 +120,12 @@ export interface SCDishTranslatableProperties
|
||||
export interface SCDishCharacteristic {
|
||||
/**
|
||||
* URL of an image of the characteristic
|
||||
* @keyword
|
||||
*/
|
||||
image?: string;
|
||||
|
||||
/**
|
||||
* Name of the characteristic
|
||||
* @filterable
|
||||
* @text
|
||||
* @elasticsearch text filterable
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
@@ -144,43 +142,36 @@ export type SCDishCategories = 'appetizer' | 'salad' | 'main dish' | 'dessert' |
|
||||
export interface SCNutritionInformation {
|
||||
/**
|
||||
* Number of calories contained (in kcal)
|
||||
* @float
|
||||
*/
|
||||
calories?: number;
|
||||
|
||||
/**
|
||||
* Content of carbohydrates (in grams)
|
||||
* @float
|
||||
*/
|
||||
carbohydrateContent?: number;
|
||||
|
||||
/**
|
||||
* Content of fat (in grams)
|
||||
* @float
|
||||
*/
|
||||
fatContent?: number;
|
||||
|
||||
/**
|
||||
* Content of proteins (in grams)
|
||||
* @float
|
||||
*/
|
||||
proteinContent?: number;
|
||||
|
||||
/**
|
||||
* Content of salt (in grams)
|
||||
* @float
|
||||
*/
|
||||
saltContent?: number;
|
||||
|
||||
/**
|
||||
* Content of saturated fat (in grams)
|
||||
* @float
|
||||
*/
|
||||
saturatedFatContent?: number;
|
||||
|
||||
/**
|
||||
* Content of sugar (in grams)
|
||||
* @float
|
||||
*/
|
||||
sugarContent?: number;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ import {SCThingMeta, SCThingType} from './abstract/thing.js';
|
||||
*/
|
||||
export interface SCFavoriteWithoutReferences extends SCSaveableThingWithoutReferences {
|
||||
/**
|
||||
* Type of a favorite
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Favorite;
|
||||
}
|
||||
@@ -31,7 +32,8 @@ export interface SCFavoriteWithoutReferences extends SCSaveableThingWithoutRefer
|
||||
*/
|
||||
export interface SCFavorite extends SCSaveableThing {
|
||||
/**
|
||||
* Type of a favorite
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Favorite;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import {SCRoomWithoutReferences} from './room.js';
|
||||
export interface SCFloorWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* Floor name in the place it is in e.g. "first floor", "ground floor". This doesn't reference the building name.
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
floorName: string;
|
||||
|
||||
@@ -45,7 +45,8 @@ export interface SCFloorWithoutReferences extends SCThingWithoutReferences {
|
||||
translations?: SCTranslations<SCFloorTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a floor
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Floor;
|
||||
}
|
||||
@@ -53,7 +54,7 @@ export interface SCFloorWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* A floor
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCFloor extends SCFloorWithoutReferences, SCThingInPlace {
|
||||
/**
|
||||
@@ -62,7 +63,8 @@ export interface SCFloor extends SCFloorWithoutReferences, SCThingInPlace {
|
||||
translations?: SCTranslations<SCFloorTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a floor
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Floor;
|
||||
}
|
||||
@@ -95,7 +97,7 @@ export interface SCFloorFeatureWithPlace<T extends GeometryObject>
|
||||
export interface SCFloorTranslatableProperties extends SCThingTranslatableProperties {
|
||||
/**
|
||||
* Translation of the floor name
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
floorName?: string;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface SCIdCardWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* A message
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCIdCard extends SCIdCardWithoutReferences, SCThing {
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,8 @@ export interface SCJobPostingWithoutReferences
|
||||
extends SCThingWithCategoriesWithoutReferences<SCJobCategories, SCThingWithCategoriesSpecificValues>,
|
||||
SCInPlace {
|
||||
/**
|
||||
* Type of a job posting
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.JobPosting;
|
||||
}
|
||||
@@ -36,14 +37,14 @@ export interface SCJobPostingWithoutReferences
|
||||
/**
|
||||
* A JobPosting
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCJobPosting
|
||||
extends SCThingWithCategories<SCJobCategories, SCThingWithCategoriesSpecificValues>,
|
||||
SCJobPostingWithoutReferences {
|
||||
/**
|
||||
* A description of the employer
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
employerOverview?: SCOrganizationWithoutReferences;
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export interface SCMessageWithoutReferences
|
||||
|
||||
/**
|
||||
* Roles for which the message is intended
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
audiences: SCUserGroup[];
|
||||
|
||||
@@ -59,13 +59,13 @@ export interface SCMessageWithoutReferences
|
||||
|
||||
/**
|
||||
* When the message was created
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
dateCreated?: SCISO8601Date;
|
||||
|
||||
/**
|
||||
* Message itself
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
messageBody: string;
|
||||
|
||||
@@ -80,7 +80,8 @@ export interface SCMessageWithoutReferences
|
||||
translations?: SCTranslations<SCMessageTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a message
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Message;
|
||||
}
|
||||
@@ -88,7 +89,7 @@ export interface SCMessageWithoutReferences
|
||||
/**
|
||||
* A message
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCMessage extends SCCreativeWork, SCMessageWithoutReferences {
|
||||
/**
|
||||
@@ -97,7 +98,8 @@ export interface SCMessage extends SCCreativeWork, SCMessageWithoutReferences {
|
||||
translations?: SCTranslations<SCMessageTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a message
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Message;
|
||||
}
|
||||
@@ -110,7 +112,7 @@ export interface SCMessageTranslatableProperties
|
||||
SCThingThatCanBeOfferedTranslatableProperties {
|
||||
/**
|
||||
* Message itself
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
messageBody?: string;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import {SCContactPointWithoutReferences} from './contact-point.js';
|
||||
*/
|
||||
export interface SCOrganizationWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* Type of an organization
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Organization;
|
||||
}
|
||||
@@ -30,7 +31,7 @@ export interface SCOrganizationWithoutReferences extends SCThingWithoutReference
|
||||
/**
|
||||
* An organization
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCOrganization extends SCOrganizationWithoutReferences, SCThingInPlace {
|
||||
/**
|
||||
@@ -39,7 +40,8 @@ export interface SCOrganization extends SCOrganizationWithoutReferences, SCThing
|
||||
contactPoints?: SCContactPointWithoutReferences[];
|
||||
|
||||
/**
|
||||
* Type of an organization
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Organization;
|
||||
}
|
||||
|
||||
@@ -52,8 +52,7 @@ export interface SCPeriodicalWithoutReferences
|
||||
categories: SCPeriodicalCategories[];
|
||||
/**
|
||||
* A list of ISSNs of a periodical
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
ISSNs?: string[];
|
||||
|
||||
@@ -63,7 +62,8 @@ export interface SCPeriodicalWithoutReferences
|
||||
translations?: SCTranslations<SCPeriodicalTranslatableFields>;
|
||||
|
||||
/**
|
||||
* Type of a periodical
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Periodical;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export interface SCPeriodicalWithoutReferences
|
||||
/**
|
||||
* A publication published at regular intervals (e.g. a magazine or newspaper)
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCPeriodical
|
||||
extends SCCreativeWork,
|
||||
@@ -83,7 +83,8 @@ export interface SCPeriodical
|
||||
translations?: SCTranslations<SCPeriodicalTranslatableFields>;
|
||||
|
||||
/**
|
||||
* Type of a periodical
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Periodical;
|
||||
}
|
||||
|
||||
@@ -27,77 +27,67 @@ import {SCRoomWithoutReferences} from './room.js';
|
||||
export interface SCPersonWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* Additional first names of the person.
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
additionalName?: string;
|
||||
|
||||
/**
|
||||
* The birth date of the person.
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
birthDate?: SCISO8601Date;
|
||||
|
||||
/**
|
||||
* The private email address of the person.
|
||||
* @TJS-format email
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* The family name of the person.
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
familyName?: string;
|
||||
|
||||
/**
|
||||
* The private fax number of the person.
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
faxNumber?: string;
|
||||
|
||||
/**
|
||||
* The gender of the person.
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
gender?: SCPersonGender;
|
||||
|
||||
/**
|
||||
* The first name of the person.
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
givenName?: string;
|
||||
|
||||
/**
|
||||
* Honorific prefix of the person.
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
honorificPrefix?: string;
|
||||
|
||||
/**
|
||||
* Honorific suffix of the person.
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
honorificSuffix?: string;
|
||||
|
||||
/**
|
||||
* Titles of jobs that the person has.
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
jobTitles?: string[];
|
||||
|
||||
/**
|
||||
* The complete name of the person combining all the parts of the name into one.
|
||||
* @filterable
|
||||
* @text
|
||||
* @elasticsearch text filterable
|
||||
*/
|
||||
name: string;
|
||||
|
||||
@@ -108,12 +98,12 @@ export interface SCPersonWithoutReferences extends SCThingWithoutReferences {
|
||||
|
||||
/**
|
||||
* The private telephone number of the person.
|
||||
* @keyword
|
||||
*/
|
||||
telephone?: string;
|
||||
|
||||
/**
|
||||
* Type of a person
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Person;
|
||||
}
|
||||
@@ -121,7 +111,7 @@ export interface SCPersonWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* A person
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCPerson extends SCPersonWithoutReferences, SCThing {
|
||||
/**
|
||||
@@ -137,7 +127,8 @@ export interface SCPerson extends SCPersonWithoutReferences, SCThing {
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type of a person
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Person;
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ export interface SCPointOfInterestWithoutReferences
|
||||
translations?: SCTranslations<SCThingWithCategoriesTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a point of interest
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.PointOfInterest;
|
||||
}
|
||||
@@ -47,7 +48,7 @@ export interface SCPointOfInterestWithoutReferences
|
||||
/**
|
||||
* A point of interest
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCPointOfInterest
|
||||
extends SCPointOfInterestWithoutReferences,
|
||||
@@ -60,7 +61,8 @@ export interface SCPointOfInterest
|
||||
translations?: SCTranslations<SCThingWithCategoriesTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of a point of interest
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.PointOfInterest;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ export interface SCPublicationEventWithoutReferences extends SCEventWithoutRefer
|
||||
translations?: SCTranslations<SCPublicationEventTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of an publication event
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.PublicationEvent;
|
||||
}
|
||||
@@ -44,7 +45,7 @@ export interface SCPublicationEventWithoutReferences extends SCEventWithoutRefer
|
||||
/**
|
||||
* An publication event
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCPublicationEvent extends SCEvent, SCPublicationEventWithoutReferences {
|
||||
/**
|
||||
@@ -53,7 +54,8 @@ export interface SCPublicationEvent extends SCEvent, SCPublicationEventWithoutRe
|
||||
translations?: SCTranslations<SCPublicationEventTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of an publication event
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.PublicationEvent;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {SCMetaTranslations, SCTranslations} from '../general/i18n.js';
|
||||
import {SCMap} from '../general/map.js';
|
||||
import {SCPlace, SCPlaceWithoutReferences, SCPlaceWithoutReferencesMeta} from './abstract/place.js';
|
||||
|
||||
import {SCThingMeta, SCThingType} from './abstract/thing.js';
|
||||
@@ -58,15 +57,14 @@ export interface SCRoomWithoutReferences
|
||||
SCThingWithCategoriesWithoutReferences<SCRoomCategories, SCRoomSpecificValues> {
|
||||
/**
|
||||
* The name of the floor in which the room is in.
|
||||
* @filterable
|
||||
* @text
|
||||
* @elasticsearch text filterable
|
||||
*/
|
||||
floorName?: string;
|
||||
|
||||
/**
|
||||
* The inventory of the place/room as a list of items and their quantity.
|
||||
*/
|
||||
inventory?: SCMap<number>;
|
||||
inventory?: Record<string, number>;
|
||||
|
||||
/**
|
||||
* Translations of specific values of the object
|
||||
@@ -76,7 +74,8 @@ export interface SCRoomWithoutReferences
|
||||
translations?: SCTranslations<SCThingWithCategoriesTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the room
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Room;
|
||||
}
|
||||
@@ -84,7 +83,7 @@ export interface SCRoomWithoutReferences
|
||||
/**
|
||||
* A room
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCRoom
|
||||
extends SCRoomWithoutReferences,
|
||||
@@ -100,7 +99,8 @@ export interface SCRoom
|
||||
translations?: SCTranslations<SCThingWithCategoriesTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the room
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Room;
|
||||
}
|
||||
@@ -111,14 +111,12 @@ export interface SCRoom
|
||||
export interface SCRoomSpecificValues extends SCThingWithCategoriesSpecificValues {
|
||||
/**
|
||||
* Category specific opening hours of the room
|
||||
* @keyword
|
||||
*/
|
||||
openingHours?: string;
|
||||
|
||||
/**
|
||||
* Category specific service hours of the room (e.g. cooked food serving hours)
|
||||
* @see http://wiki.openstreetmap.org/wiki/Key:opening_hours/specification
|
||||
* @keyword
|
||||
*/
|
||||
serviceHours?: string;
|
||||
}
|
||||
|
||||
@@ -26,14 +26,13 @@ import {SCThingMeta, SCThingType} from './abstract/thing.js';
|
||||
export interface SCSemesterWithoutReferences extends SCAcademicTermWithoutReferences {
|
||||
/**
|
||||
* The short name of the semester, using the given pattern.
|
||||
* @filterable
|
||||
* @pattern ^(WS|SS|WiSe|SoSe) [0-9]{4}(/[0-9]{2})?$
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
acronym: string;
|
||||
|
||||
/**
|
||||
* Type of the semester
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Semester;
|
||||
}
|
||||
@@ -41,11 +40,12 @@ export interface SCSemesterWithoutReferences extends SCAcademicTermWithoutRefere
|
||||
/**
|
||||
* A semester
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCSemester extends SCSemesterWithoutReferences, SCAcademicTerm {
|
||||
/**
|
||||
* Type of the semester
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Semester;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,6 @@ export type SCSettingValues = SCSettingValue[];
|
||||
export interface SCSettingValueTranslatableProperties extends SCThingWithCategoriesTranslatableProperties {
|
||||
/**
|
||||
* The translations of the possible values of a setting
|
||||
* @keyword
|
||||
*/
|
||||
values?: string[];
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import {SCThingMeta, SCThingType} from './abstract/thing.js';
|
||||
*/
|
||||
export interface SCSportCourseWithoutReferences extends SCEventWithoutReferences {
|
||||
/**
|
||||
* Type of a sport course
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.SportCourse;
|
||||
}
|
||||
@@ -29,11 +30,12 @@ export interface SCSportCourseWithoutReferences extends SCEventWithoutReferences
|
||||
/**
|
||||
* A sport course
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCSportCourse extends SCEvent, SCSportCourseWithoutReferences {
|
||||
/**
|
||||
* Type of a sport course
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.SportCourse;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {SCLanguage, SCMetaTranslations, SCTranslations} from '../general/i18n.js';
|
||||
import {SCMap} from '../general/map.js';
|
||||
import {SCThingMeta, SCThingType} from './abstract/thing.js';
|
||||
import {
|
||||
SCAcademicPriceGroup,
|
||||
@@ -32,7 +31,6 @@ import {SCPersonWithoutReferences} from './person.js';
|
||||
export interface SCStudyModuleWithoutReferences extends SCThingThatCanBeOfferedWithoutReferences {
|
||||
/**
|
||||
* ECTS points (European Credit Transfer System)
|
||||
* @float
|
||||
*/
|
||||
ects: number;
|
||||
|
||||
@@ -43,15 +41,14 @@ export interface SCStudyModuleWithoutReferences extends SCThingThatCanBeOfferedW
|
||||
|
||||
/**
|
||||
* Majors that this study module is meant for
|
||||
* @filterable
|
||||
* @keyword
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
majors: string[];
|
||||
|
||||
/**
|
||||
* Represents the modules necessity for each given major (of the major property)
|
||||
*/
|
||||
necessity: SCMap<SCStudyModuleNecessity>;
|
||||
necessity: Record<string, SCStudyModuleNecessity>;
|
||||
|
||||
/**
|
||||
* Translated fields of a study module
|
||||
@@ -59,7 +56,9 @@ export interface SCStudyModuleWithoutReferences extends SCThingThatCanBeOfferedW
|
||||
translations?: SCTranslations<SCStudyModuleTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the study module
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.StudyModule;
|
||||
}
|
||||
@@ -67,7 +66,7 @@ export interface SCStudyModuleWithoutReferences extends SCThingThatCanBeOfferedW
|
||||
/**
|
||||
* A study module
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCStudyModule
|
||||
extends SCStudyModuleWithoutReferences,
|
||||
@@ -104,7 +103,8 @@ export interface SCStudyModule
|
||||
translations?: SCTranslations<SCStudyModuleTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the study module
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.StudyModule;
|
||||
}
|
||||
@@ -112,14 +112,13 @@ export interface SCStudyModule
|
||||
export interface SCStudyModuleTranslatableProperties extends SCThingThatCanBeOfferedTranslatableProperties {
|
||||
/**
|
||||
* Translations of the majors that this study module is meant for
|
||||
* @keyword
|
||||
*/
|
||||
majors?: string[];
|
||||
|
||||
/**
|
||||
* Translations of the modules necessity for each given major (of the major property)
|
||||
*/
|
||||
necessity: SCMap<SCStudyModuleNecessity>;
|
||||
necessity: Record<string, SCStudyModuleNecessity>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,6 @@ export interface SCTicketWithoutReferences extends SCThingWithoutReferences {
|
||||
|
||||
/**
|
||||
* Waiting number of the ticket
|
||||
* @keyword
|
||||
*/
|
||||
currentTicketNumber: string;
|
||||
|
||||
@@ -38,7 +37,8 @@ export interface SCTicketWithoutReferences extends SCThingWithoutReferences {
|
||||
serviceType: string;
|
||||
|
||||
/**
|
||||
* Type of a ticket
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Ticket;
|
||||
}
|
||||
@@ -46,11 +46,12 @@ export interface SCTicketWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* A ticket
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCTicket extends SCTicketWithoutReferences, SCThingInPlace {
|
||||
/**
|
||||
* Type of a ticket
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Ticket;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export interface SCToDoWithoutReferences
|
||||
|
||||
/**
|
||||
* A date when the "to do" is due
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
dueDate?: SCISO8601Date;
|
||||
|
||||
@@ -45,7 +45,8 @@ export interface SCToDoWithoutReferences
|
||||
priority: SCToDoPriority;
|
||||
|
||||
/**
|
||||
* Type of the "to do"
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.ToDo;
|
||||
}
|
||||
@@ -53,7 +54,7 @@ export interface SCToDoWithoutReferences
|
||||
/**
|
||||
* A "to do"
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCToDo
|
||||
extends SCToDoWithoutReferences,
|
||||
@@ -65,7 +66,8 @@ export interface SCToDo
|
||||
translations?: SCTranslations<SCThingWithCategoriesTranslatableProperties>;
|
||||
|
||||
/**
|
||||
* Type of the "to do"
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.ToDo;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {SCThing, SCThingMeta, SCThingType, SCThingWithoutReferences} from './abs
|
||||
export interface SCTourWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* Init script for the tour
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
init?: string;
|
||||
|
||||
@@ -31,8 +31,8 @@ export interface SCTourWithoutReferences extends SCThingWithoutReferences {
|
||||
steps: SCTourStep[];
|
||||
|
||||
/**
|
||||
* Type of a tour
|
||||
* @keyword
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Tour;
|
||||
}
|
||||
@@ -40,11 +40,12 @@ export interface SCTourWithoutReferences extends SCThingWithoutReferences {
|
||||
/**
|
||||
* A tour
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCTour extends SCTourWithoutReferences, SCThing {
|
||||
/**
|
||||
* Type of a tour
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Tour;
|
||||
}
|
||||
@@ -95,12 +96,12 @@ export type SCTourStep = SCTourStepMenu | SCTourStepLocation | SCTourStepTooltip
|
||||
export interface SCTourStepLocation {
|
||||
/**
|
||||
* Location to go to
|
||||
* @keyword
|
||||
*/
|
||||
location: string;
|
||||
|
||||
/**
|
||||
* Type of the step
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: 'location';
|
||||
}
|
||||
@@ -116,7 +117,6 @@ export interface SCTourStepTooltip {
|
||||
|
||||
/**
|
||||
* Element that the tooltip shall be pointing at or a list of elements to try in the specified order
|
||||
* @keyword
|
||||
*/
|
||||
element: string | string[];
|
||||
|
||||
@@ -132,7 +132,7 @@ export interface SCTourStepTooltip {
|
||||
|
||||
/**
|
||||
* Text that the tooltip shall contain
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
text: string;
|
||||
|
||||
@@ -143,7 +143,8 @@ export interface SCTourStepTooltip {
|
||||
tries?: number;
|
||||
|
||||
/**
|
||||
* Type of the step
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: 'tooltip';
|
||||
}
|
||||
@@ -174,7 +175,6 @@ export interface SCTourStepMenu {
|
||||
export interface SCTourResolvedElement {
|
||||
/**
|
||||
* Element name
|
||||
* @keyword
|
||||
*/
|
||||
element: string;
|
||||
}
|
||||
@@ -185,7 +185,6 @@ export interface SCTourResolvedElement {
|
||||
export interface SCTourResolvedEvent {
|
||||
/**
|
||||
* Event name
|
||||
* @keyword
|
||||
*/
|
||||
event: string;
|
||||
}
|
||||
@@ -206,7 +205,6 @@ export interface SCTourResolvedLocation {
|
||||
export interface SCTourResolvedLocationTypeIs {
|
||||
/**
|
||||
* Specific location name
|
||||
* @keyword
|
||||
*/
|
||||
is: string;
|
||||
}
|
||||
@@ -217,7 +215,6 @@ export interface SCTourResolvedLocationTypeIs {
|
||||
export interface SCTourResolvedLocationTypeMatch {
|
||||
/**
|
||||
* Regex location name
|
||||
* @keyword
|
||||
*/
|
||||
match: string;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ export interface SCVideoWithoutReferences
|
||||
|
||||
/**
|
||||
* URLs to a thumbnails for the Video
|
||||
* @keyword
|
||||
*/
|
||||
thumbnails?: string[];
|
||||
|
||||
@@ -59,7 +58,7 @@ export interface SCVideoWithoutReferences
|
||||
|
||||
/**
|
||||
* A Transcript of the Video
|
||||
* @text
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
transcript?: string;
|
||||
|
||||
@@ -69,7 +68,8 @@ export interface SCVideoWithoutReferences
|
||||
translations?: SCTranslations<SCVideoTranslatableFields>;
|
||||
|
||||
/**
|
||||
* Type of an Video
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Video;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export interface SCVideoSource {
|
||||
|
||||
/**
|
||||
* MIME-Type of the source File
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
mimeType: SCVideoMimeType;
|
||||
|
||||
@@ -95,7 +95,6 @@ export interface SCVideoSource {
|
||||
|
||||
/**
|
||||
* URL to the Video File
|
||||
* @keyword
|
||||
*/
|
||||
url: string;
|
||||
|
||||
@@ -114,13 +113,12 @@ export interface SCVideoTrack {
|
||||
|
||||
/**
|
||||
* Content Type of the Track File
|
||||
* @filterable
|
||||
* @elasticsearch filterable
|
||||
*/
|
||||
type: SCVideoTrackTypes;
|
||||
|
||||
/**
|
||||
* URL to the Track File
|
||||
* @keyword
|
||||
*/
|
||||
url: string;
|
||||
}
|
||||
@@ -128,7 +126,7 @@ export interface SCVideoTrack {
|
||||
/**
|
||||
* A video
|
||||
* @validatable
|
||||
* @indexable
|
||||
* @elasticsearch indexable
|
||||
*/
|
||||
export interface SCVideo
|
||||
extends SCCreativeWork,
|
||||
@@ -145,7 +143,8 @@ export interface SCVideo
|
||||
translations?: SCTranslations<SCVideoTranslatableFields>;
|
||||
|
||||
/**
|
||||
* Type of a video
|
||||
* Type discriminator
|
||||
* @elasticsearch type
|
||||
*/
|
||||
type: SCThingType.Video;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import {validateFiles, writeReport} from '@openstapps/core-tools';
|
||||
import {expect} from 'chai';
|
||||
import {mkdir} from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
describe('Schema', function () {
|
||||
this.timeout(15_000);
|
||||
this.slow(10_000);
|
||||
|
||||
it('should validate against test files', async function () {
|
||||
// TODO
|
||||
/*it('should validate against test files', async function () {
|
||||
const errorsPerFile = await validateFiles(
|
||||
path.resolve('lib', 'schema'),
|
||||
path.resolve('test', 'resources'),
|
||||
@@ -21,5 +17,5 @@ describe('Schema', function () {
|
||||
expect(error.expected).to.be.true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});*/
|
||||
});
|
||||
|
||||
16
packages/core/tsup.config.ts
Normal file
16
packages/core/tsup.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {defineConfig} from 'tsup';
|
||||
import {jsonSchemaPlugin} from '@openstapps/json-schema-generator';
|
||||
import {openapiPlugin} from '@openstapps/openapi-generator';
|
||||
import {elasticsearchMappingGenerator} from '@openstapps/es-mapping-generator';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
format: 'esm',
|
||||
outDir: 'lib',
|
||||
plugins: [
|
||||
jsonSchemaPlugin('index.schema.json', elasticsearchMappingGenerator('elasticsearch.json')),
|
||||
openapiPlugin('openapi.json', 'index.schema.json'),
|
||||
],
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
require('./lib/app.js');
|
||||
@@ -2,14 +2,11 @@
|
||||
"name": "@openstapps/es-mapping-generator",
|
||||
"description": "Tool to convert TypeScript Interfaces to Elasticsearch Mappings",
|
||||
"version": "3.0.0",
|
||||
"type": "commonjs",
|
||||
"type": "module",
|
||||
"license": "GPL-3.0-only",
|
||||
"author": "Thea Schöbl <dev@theaninova.de>",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"bin": {
|
||||
"openstapps-es-mapping-generator": "app.js"
|
||||
},
|
||||
"files": [
|
||||
"app.js",
|
||||
"lib",
|
||||
@@ -18,7 +15,8 @@
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rimraf lib && tsc",
|
||||
"build": "tsup-node --dts",
|
||||
"dev": "tsup --watch --onSuccess \"node lib/index.js\"",
|
||||
"format": "prettier . -c --ignore-path ../../.gitignore",
|
||||
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint --ext .ts src/ test/",
|
||||
@@ -26,28 +24,33 @@
|
||||
"test": "c8 mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elastic/elasticsearch": "8.4.0",
|
||||
"commander": "10.0.0",
|
||||
"deepmerge": "4.3.1",
|
||||
"flatted": "3.2.7",
|
||||
"typedoc": "0.18.0",
|
||||
"typescript": "3.8.3"
|
||||
"@elastic/elasticsearch": "8.10.0",
|
||||
"@openstapps/json-schema-generator": "workspace:*",
|
||||
"@openstapps/tsup-plugin": "workspace:*",
|
||||
"@types/json-schema": "7.0.14",
|
||||
"ajv": "8.12.0",
|
||||
"better-ajv-errors": "1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openstapps/eslint-config": "workspace:*",
|
||||
"@openstapps/prettier-config": "workspace:*",
|
||||
"@testdeck/mocha": "0.3.3",
|
||||
"@openstapps/tsconfig": "workspace:*",
|
||||
"@types/chai": "4.3.5",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/glob": "8.0.1",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/node": "14.18.38",
|
||||
"@types/rimraf": "3.0.2",
|
||||
"@types/mustache": "4.2.2",
|
||||
"@types/node": "18.15.3",
|
||||
"c8": "7.14.0",
|
||||
"chai": "4.3.7",
|
||||
"esbuild": "0.17.19",
|
||||
"mocha": "10.2.0",
|
||||
"mocha-junit-reporter": "2.2.0",
|
||||
"nock": "13.3.1",
|
||||
"rimraf": "5.0.0",
|
||||
"ts-node": "10.9.1"
|
||||
"ts-node": "10.9.1",
|
||||
"tsup": "6.7.0",
|
||||
"typedoc": "0.24.8",
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"prettier": "@openstapps/prettier-config",
|
||||
"eslintConfig": {
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Command} from 'commander';
|
||||
import {copyFileSync, mkdirSync, readFileSync, writeFileSync} from 'fs';
|
||||
import path from 'path';
|
||||
import {generateTemplate} from './mapping';
|
||||
import {getProjectReflection} from './project-reflection';
|
||||
|
||||
// handle unhandled promise rejections
|
||||
process.on('unhandledRejection', async (reason: unknown) => {
|
||||
if (reason instanceof Error) {
|
||||
await console.error(reason.message);
|
||||
console.info(reason.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const commander = new Command('openstapps-core-tools');
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
commander.version(JSON.parse(readFileSync(path.resolve(__dirname, '..', 'package.json')).toString()).version);
|
||||
|
||||
commander
|
||||
.command('mapping <relativeSrcPath>')
|
||||
.option('-m, --mappingPath <relativeMappingPath>', 'Mapping Path')
|
||||
.option('-i, --ignoredTags <ignoredTags>', 'Ignored Tags (comma-separated)')
|
||||
.option('-a, --aggPath <relativeAggregationPath>', 'Aggregations Path')
|
||||
.option('-e, --errorPath <relativeErrorPath>', 'Error Path')
|
||||
.action(async (relativeSourcePath, options) => {
|
||||
// get absolute paths
|
||||
const sourcePath = path.resolve(relativeSourcePath);
|
||||
|
||||
let ignoredTagsList: string[] = [];
|
||||
if (typeof options.ignoredTags === 'string') {
|
||||
ignoredTagsList = options.ignoredTags.split(',');
|
||||
}
|
||||
|
||||
// get project reflection
|
||||
const projectReflection = getProjectReflection(sourcePath);
|
||||
|
||||
const result = generateTemplate(projectReflection, ignoredTagsList, true);
|
||||
if (result.errors.length > 0) {
|
||||
await console.error('Mapping generated with errors!');
|
||||
} else {
|
||||
console.log('Mapping generated without errors!');
|
||||
}
|
||||
|
||||
// write documentation to file
|
||||
if (options.aggPath !== undefined) {
|
||||
const aggPath = path.resolve(options.aggPath);
|
||||
mkdirSync(path.dirname(aggPath), {recursive: true});
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
writeFileSync(aggPath, JSON.stringify(result.aggregations, null, 2));
|
||||
copyFileSync(
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
require.resolve('../schema/aggregations.d.ts'),
|
||||
path.join(path.dirname(aggPath), 'aggregations.json.d.ts'),
|
||||
);
|
||||
console.log(`Elasticsearch aggregations written to ${aggPath}.`);
|
||||
}
|
||||
if (options.mappingPath !== undefined) {
|
||||
const mappingPath = path.resolve(options.mappingPath);
|
||||
mkdirSync(path.dirname(mappingPath), {recursive: true});
|
||||
writeFileSync(mappingPath, JSON.stringify(result.mappings, null, 2));
|
||||
copyFileSync(
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
require.resolve('../schema/mappings.d.ts'),
|
||||
path.join(path.dirname(mappingPath), 'mappings.json.d.ts'),
|
||||
);
|
||||
console.log(`Elasticsearch mappings written to ${mappingPath}.`);
|
||||
}
|
||||
if (options.errorPath !== undefined) {
|
||||
const errorPath = path.resolve(options.errorPath);
|
||||
mkdirSync(path.dirname(errorPath), {recursive: true});
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
writeFileSync(errorPath, JSON.stringify(result.errors, null, 2));
|
||||
console.log(`Mapping errors written to ${errorPath}.`);
|
||||
} else if (result.errors.length > 0) {
|
||||
for (const error of result.errors) {
|
||||
await console.error(error);
|
||||
}
|
||||
|
||||
throw new Error('Mapping generation failed');
|
||||
}
|
||||
});
|
||||
|
||||
commander.parse(process.argv);
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {MappingProperty} from '@elastic/elasticsearch/lib/api/types';
|
||||
import type {ElasticsearchFieldmap, SimpleType} from '../../schema/mappings.js';
|
||||
|
||||
const ducetSort = {
|
||||
type: 'icu_collation_keyword',
|
||||
language: 'de',
|
||||
country: 'DE',
|
||||
variant: '@collation=phonebook',
|
||||
};
|
||||
|
||||
const keyword: MappingProperty['type'] = 'keyword';
|
||||
|
||||
export const fieldmap: ElasticsearchFieldmap = {
|
||||
aggregatable: {
|
||||
default: {
|
||||
raw: {
|
||||
ignore_above: 10_000,
|
||||
type: keyword,
|
||||
},
|
||||
},
|
||||
ignore: ['global'],
|
||||
},
|
||||
sortable: {
|
||||
default: {
|
||||
sort: ducetSort,
|
||||
},
|
||||
ducet: {
|
||||
sort: ducetSort,
|
||||
},
|
||||
ignore: ['price'],
|
||||
},
|
||||
};
|
||||
|
||||
export const filterableTagName = 'filterable';
|
||||
|
||||
export const filterableMap: Record<string, SimpleType> = {
|
||||
date: 'keyword',
|
||||
keyword: 'keyword',
|
||||
text: 'keyword',
|
||||
integer: 'integer',
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {MappingProperty} from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
export const premaps: Record<string, MappingProperty> = {
|
||||
'CoordinateReferenceSystem': {
|
||||
dynamic: true,
|
||||
properties: {
|
||||
type: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
'LineString': {
|
||||
type: 'geo_shape',
|
||||
},
|
||||
'Point': {
|
||||
properties: {
|
||||
type: {
|
||||
type: 'keyword',
|
||||
},
|
||||
coordinates: {
|
||||
type: 'geo_point',
|
||||
},
|
||||
},
|
||||
dynamic: 'strict',
|
||||
},
|
||||
'Polygon': {
|
||||
type: 'geo_shape',
|
||||
},
|
||||
'SCISO8601DateRange': {
|
||||
type: 'date_range',
|
||||
},
|
||||
'jsonpatch.OpPatch': {
|
||||
dynamic: 'strict',
|
||||
properties: {
|
||||
from: {
|
||||
type: 'keyword',
|
||||
},
|
||||
op: {
|
||||
type: 'keyword',
|
||||
},
|
||||
path: {
|
||||
type: 'keyword',
|
||||
},
|
||||
value: {
|
||||
// this is actually an 'any' type; however, ES does not really support that.
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {MappingFloatNumberProperty} from '@elastic/elasticsearch/lib/api/types';
|
||||
import type {ElasticsearchTypemap} from '../../schema/mappings';
|
||||
|
||||
export const PARSE_ERROR = 'PARSE_ERROR' as MappingFloatNumberProperty['type'];
|
||||
export const MISSING_PREMAP = 'MISSING_PREMAP' as MappingFloatNumberProperty['type'];
|
||||
export const TYPE_CONFLICT = 'TYPE_CONFLICT' as MappingFloatNumberProperty['type'];
|
||||
|
||||
export const typemap: ElasticsearchTypemap = {
|
||||
boolean: {
|
||||
default: 'boolean',
|
||||
},
|
||||
false: {
|
||||
default: 'boolean',
|
||||
},
|
||||
number: {
|
||||
default: 'integer',
|
||||
float: 'float',
|
||||
integer: 'integer',
|
||||
date: 'date',
|
||||
},
|
||||
string: {
|
||||
default: 'text',
|
||||
keyword: 'keyword',
|
||||
text: 'text',
|
||||
date: 'date',
|
||||
},
|
||||
stringLiteral: {
|
||||
default: 'keyword',
|
||||
},
|
||||
true: {
|
||||
default: 'boolean',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* If the string is a tag type
|
||||
*/
|
||||
export function isTagType(string_: string): boolean {
|
||||
for (const key in typemap) {
|
||||
if (typemap.hasOwnProperty(key) && typemap[key][string_] !== undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export const dynamicTypes = ['any', 'unknown'];
|
||||
60
packages/es-mapping-generator/src/dsl/schema.ts
Normal file
60
packages/es-mapping-generator/src/dsl/schema.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {MappingProperty, SearchRequest} from '@elastic/elasticsearch/lib/api/types.js';
|
||||
import Ajv from 'ajv';
|
||||
import {readFile} from 'fs/promises';
|
||||
import {fileURLToPath} from 'url';
|
||||
import path from 'path';
|
||||
import {Context} from '../generator/context.js';
|
||||
|
||||
/**
|
||||
* @validatable
|
||||
*/
|
||||
export interface ElasticsearchOptionsDSL {
|
||||
/**
|
||||
* Mark an interface as indexable
|
||||
*/
|
||||
indexable?: true;
|
||||
/**
|
||||
* Inherit customization options from another item
|
||||
*/
|
||||
extends?: string[];
|
||||
/**
|
||||
* Completely override the property
|
||||
*/
|
||||
override?: MappingProperty;
|
||||
/**
|
||||
* Merge property values
|
||||
*/
|
||||
merge?: MappingProperty;
|
||||
/**
|
||||
* Modify the search request
|
||||
*
|
||||
* Supports `{name}`, `{type}` and `{prop}` templates substitutions anywhere
|
||||
*/
|
||||
search?: Partial<SearchRequest>;
|
||||
}
|
||||
|
||||
const schema = JSON.parse(
|
||||
await readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), 'index.schema.json'), 'utf8'),
|
||||
);
|
||||
const ajv = new Ajv.default({schemas: [schema], allowUnionTypes: true});
|
||||
|
||||
/**
|
||||
* Validate that the options are valid
|
||||
*/
|
||||
export function validateElasticsearchOptionsDsl(
|
||||
context: Context,
|
||||
value: unknown,
|
||||
): value is ElasticsearchOptionsDSL {
|
||||
return ajv.validate('#/definitions/ElasticsearchOptionsDSL', value)
|
||||
? true
|
||||
: context.bail(JSON.stringify(ajv.errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the mapping result is correct
|
||||
*/
|
||||
export function validateMappingResult(context: Context, value: unknown): value is MappingProperty {
|
||||
return ajv.validate('#/definitions/MappingProperty', value)
|
||||
? true
|
||||
: context.bail(JSON.stringify(ajv.errors));
|
||||
}
|
||||
47
packages/es-mapping-generator/src/generator/base.ts
Normal file
47
packages/es-mapping-generator/src/generator/base.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {JSONSchema7} from 'json-schema';
|
||||
import {MappingProperty} from '@elastic/elasticsearch/lib/api/types.js';
|
||||
import {transformObject} from './transformers/object.js';
|
||||
import {transformString} from './transformers/string.js';
|
||||
import {Context} from './context.js';
|
||||
import {transformDefinition} from './definition.js';
|
||||
|
||||
/**
|
||||
* Transform JSONSchema without applying custom tag logic
|
||||
*/
|
||||
export function transformBase(context: Context, definition: JSONSchema7): MappingProperty {
|
||||
if (definition.anyOf) {
|
||||
return context.resolveUnion(definition.anyOf as JSONSchema7[]);
|
||||
}
|
||||
|
||||
switch (definition.type) {
|
||||
case 'array': {
|
||||
if (Array.isArray(definition.items)) {
|
||||
return context.resolveUnion(definition.items as JSONSchema7[]);
|
||||
} else if (typeof definition.items === 'object') {
|
||||
return transformDefinition(context, definition.items);
|
||||
} else {
|
||||
return context.bail(`Not implemented array type ${typeof definition.items}`);
|
||||
}
|
||||
}
|
||||
case 'object': {
|
||||
return transformObject(context, definition);
|
||||
}
|
||||
case 'string': {
|
||||
return transformString(definition);
|
||||
}
|
||||
case 'number': {
|
||||
return {type: 'float'};
|
||||
}
|
||||
case 'integer': {
|
||||
return {type: 'integer'};
|
||||
}
|
||||
case 'boolean': {
|
||||
return {type: 'boolean'};
|
||||
}
|
||||
default: {
|
||||
return {
|
||||
dynamic: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
115
packages/es-mapping-generator/src/generator/context.ts
Normal file
115
packages/es-mapping-generator/src/generator/context.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import {JSONSchema7} from 'json-schema';
|
||||
import {transformDefinition} from './definition.js';
|
||||
import crypto from 'crypto';
|
||||
import {
|
||||
MappingProperty,
|
||||
MappingDynamicProperty,
|
||||
SearchRequest,
|
||||
} from '@elastic/elasticsearch/lib/api/types.js';
|
||||
import deepmerge from 'deepmerge';
|
||||
import {MappingGenerator, sanitizeTypeName} from './mapping-generator.js';
|
||||
import {renderTemplate} from '../template.js';
|
||||
|
||||
/**
|
||||
* Get the name from a $ref such as `#/definitions/SCThing`
|
||||
*/
|
||||
function getNameFromRef(ref: string): string {
|
||||
return decodeURI(ref).replace(/^#\/definitions\//, '');
|
||||
}
|
||||
|
||||
export class Context {
|
||||
constructor(
|
||||
readonly generator: MappingGenerator,
|
||||
readonly thingType: string,
|
||||
readonly path: string[],
|
||||
readonly propertyPath: string[],
|
||||
readonly dependencies: Map<string, Set<string>>,
|
||||
) {}
|
||||
|
||||
resolveReference(reference: string): MappingProperty {
|
||||
return this.deriveContext(reference)[1];
|
||||
}
|
||||
|
||||
resolveUnion(types: JSONSchema7[]): MappingDynamicProperty {
|
||||
for (const type of types) {
|
||||
const [name] = this.deriveContext(type.$ref ?? type);
|
||||
this.addDependency(name, this.propertyPath.join('.'));
|
||||
}
|
||||
|
||||
return {
|
||||
type: '{dynamic_property}',
|
||||
};
|
||||
}
|
||||
|
||||
private deriveContext(reference: string | JSONSchema7): [dependencyName: string, mapping: MappingProperty] {
|
||||
let referenceName = typeof reference === 'string' ? getNameFromRef(reference) : undefined;
|
||||
let definition = typeof reference === 'string' ? undefined : reference;
|
||||
|
||||
if (!definition && !this.generator.cache.has(referenceName!)) {
|
||||
definition = this.generator.project.definitions![referenceName!] as JSONSchema7;
|
||||
if (typeof definition !== 'object') this.bail(`Invalid path ${referenceName!}`);
|
||||
}
|
||||
|
||||
if (!referenceName || !this.generator.cache.has(referenceName)) {
|
||||
const derivedContext = new Context(
|
||||
this.generator,
|
||||
this.thingType,
|
||||
referenceName ? [referenceName] : [],
|
||||
this.propertyPath,
|
||||
new Map(),
|
||||
);
|
||||
const result = transformDefinition(derivedContext, definition);
|
||||
referenceName ??= crypto.createHash('md5').update(JSON.stringify(result)).digest('hex');
|
||||
|
||||
this.generator.cache.set(referenceName, {mapping: result, dependencies: derivedContext.dependencies});
|
||||
}
|
||||
|
||||
const {mapping, dependencies} = this.generator.cache.get(referenceName)!;
|
||||
for (const [name, paths] of dependencies) {
|
||||
for (const path of paths) {
|
||||
this.addDependency(name, [...this.propertyPath.slice(0, -1), path].join('.'));
|
||||
}
|
||||
}
|
||||
return [referenceName, mapping];
|
||||
}
|
||||
|
||||
registerSearchMod(modification: Partial<SearchRequest>) {
|
||||
this.generator.searchMods.mods = deepmerge<Partial<SearchRequest>>(
|
||||
this.generator.searchMods.mods,
|
||||
renderTemplate(modification, [
|
||||
['{name}', this.path[0]],
|
||||
['{prop}', this.propertyPath.join('.')],
|
||||
['{type}', this.thingType],
|
||||
['{_type}', sanitizeTypeName(this.thingType)],
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
private addDependency(name: string, path: string) {
|
||||
if (!this.dependencies.has(name)) {
|
||||
this.dependencies.set(name, new Set([path]));
|
||||
} else {
|
||||
this.dependencies.get(name)!.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Step down into a property
|
||||
*/
|
||||
step(property: string): Context {
|
||||
return new Context(
|
||||
this.generator,
|
||||
this.thingType,
|
||||
[...this.path, property],
|
||||
[...this.propertyPath, property],
|
||||
this.dependencies,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bail and throw
|
||||
*/
|
||||
bail(reason: string): never {
|
||||
throw new Error(`${this.path.join('.')} ${reason}`);
|
||||
}
|
||||
}
|
||||
32
packages/es-mapping-generator/src/generator/definition.ts
Normal file
32
packages/es-mapping-generator/src/generator/definition.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {JSONSchema7} from 'json-schema';
|
||||
import {MappingProperty} from '@elastic/elasticsearch/lib/api/types.js';
|
||||
import {transformBase} from './base.js';
|
||||
import {Context} from './context.js';
|
||||
import deepmerge from 'deepmerge';
|
||||
import {resolveDsl} from './dsl.js';
|
||||
import {getTags, INDEXABLE_TAG_NAME} from './tags.js';
|
||||
|
||||
/**
|
||||
* Transform JSONSchema
|
||||
*/
|
||||
export function transformDefinition(context: Context, definition: JSONSchema7): MappingProperty {
|
||||
if (definition.$ref) return context.resolveReference(definition.$ref);
|
||||
const tags = getTags(definition);
|
||||
tags.delete(INDEXABLE_TAG_NAME);
|
||||
let base = transformBase(context, definition);
|
||||
|
||||
if (tags.size > 0) {
|
||||
const options = resolveDsl(context, {extends: [...tags]});
|
||||
if (options.override) {
|
||||
base = options.override;
|
||||
}
|
||||
if (options.merge) {
|
||||
base = deepmerge<MappingProperty>(base, options.merge);
|
||||
}
|
||||
if (options.search) {
|
||||
context.registerSearchMod(options.search);
|
||||
}
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
48
packages/es-mapping-generator/src/generator/dsl.ts
Normal file
48
packages/es-mapping-generator/src/generator/dsl.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import {Context} from './context.js';
|
||||
import {ElasticsearchOptionsDSL} from '../dsl/schema.js';
|
||||
import deepmerge from 'deepmerge';
|
||||
|
||||
type ResolvedOptions = Omit<ElasticsearchOptionsDSL, 'extends'>;
|
||||
|
||||
/**
|
||||
* Resolve DSL inheritance
|
||||
*/
|
||||
export function resolveDsl(
|
||||
context: Context,
|
||||
{extends: parents, ...result}: ElasticsearchOptionsDSL,
|
||||
): ResolvedOptions {
|
||||
for (const reference of parents ?? []) {
|
||||
result = deepmerge<ResolvedOptions>(
|
||||
result,
|
||||
reference.startsWith('@')
|
||||
? resolveReferencePath(context, reference)
|
||||
: resolvePresetReference(context, reference),
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve preset references
|
||||
*/
|
||||
function resolvePresetReference(context: Context, reference: string): ResolvedOptions {
|
||||
if (!context.generator.presets.has(reference)) return context.bail(`Missing preset ${reference}`);
|
||||
return resolveDsl(context, context.generator.presets.get(reference)!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve @references
|
||||
*/
|
||||
function resolveReferencePath(context: Context, reference: string): ResolvedOptions {
|
||||
const [type, ...path] = reference.replace(/^@/, '').split('.');
|
||||
let declaration = context.resolveReference(type);
|
||||
while (path.length > 0) {
|
||||
const property = path.shift()!;
|
||||
if (!('properties' in declaration && declaration.properties && property in declaration.properties))
|
||||
context.bail(`Invalid reference ${reference}`);
|
||||
|
||||
declaration = declaration.properties[property];
|
||||
}
|
||||
return {override: declaration};
|
||||
}
|
||||
171
packages/es-mapping-generator/src/generator/index.ts
Normal file
171
packages/es-mapping-generator/src/generator/index.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import type {JSONSchema7} from 'json-schema';
|
||||
import {ElasticsearchOptionsDSL} from '../dsl/schema.js';
|
||||
import {IndicesPutTemplateRequest, MappingProperty} from '@elastic/elasticsearch/lib/api/types.js';
|
||||
import {MappingGenerator} from './mapping-generator.js';
|
||||
import {getTags, INDEXABLE_TAG_NAME} from './tags.js';
|
||||
|
||||
export interface GeneratorOptions {
|
||||
/**
|
||||
* Presets you can extend
|
||||
*/
|
||||
presets: Record<string, ElasticsearchOptionsDSL>;
|
||||
/**
|
||||
* Override specific types
|
||||
*/
|
||||
overrides: Record<string, MappingProperty>;
|
||||
/**
|
||||
* Template for the generated index request
|
||||
*
|
||||
* Supports `{type}` and `{sanitized_type}` (same as `{type}`, but no spaces) template substitutions
|
||||
*/
|
||||
template: Partial<IndicesPutTemplateRequest>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully transform a project
|
||||
*/
|
||||
export function transformProject(project: JSONSchema7) {
|
||||
const context = new MappingGenerator(project, OPTIONS);
|
||||
|
||||
const results = [];
|
||||
for (const name in project.definitions) {
|
||||
const definition = project.definitions[name];
|
||||
if (typeof definition !== 'object' || !getTags(definition).has(INDEXABLE_TAG_NAME)) continue;
|
||||
|
||||
results.push(context.buildTemplate(name));
|
||||
}
|
||||
return {
|
||||
mappings: results,
|
||||
search: context.searchMods.mods,
|
||||
};
|
||||
}
|
||||
|
||||
const OPTIONS: GeneratorOptions = {
|
||||
template: {
|
||||
name: 'template_{_type}',
|
||||
index_patterns: 'stapps_{_type}*',
|
||||
settings: {
|
||||
'mapping.total_fields.limit': 10_000,
|
||||
'max_result_window': 30_000,
|
||||
'number_of_replicas': 0,
|
||||
'number_of_shards': 1,
|
||||
},
|
||||
mappings: {
|
||||
date_detection: false,
|
||||
_source: {
|
||||
excludes: ['creation_date'],
|
||||
},
|
||||
properties: {
|
||||
creation_date: {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
overrides: {
|
||||
'CoordinateReferenceSystem': {
|
||||
dynamic: true,
|
||||
properties: {
|
||||
type: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
'LineString': {
|
||||
type: 'geo_shape',
|
||||
},
|
||||
'Point': {
|
||||
properties: {
|
||||
type: {
|
||||
type: 'keyword',
|
||||
},
|
||||
coordinates: {
|
||||
type: 'geo_point',
|
||||
},
|
||||
},
|
||||
dynamic: 'strict',
|
||||
},
|
||||
'Polygon': {
|
||||
type: 'geo_shape',
|
||||
},
|
||||
'SCISO8601DateRange': {
|
||||
type: 'date_range',
|
||||
},
|
||||
'jsonpatch.OpPatch': {
|
||||
dynamic: 'strict',
|
||||
properties: {
|
||||
from: {
|
||||
type: 'keyword',
|
||||
},
|
||||
op: {
|
||||
type: 'keyword',
|
||||
},
|
||||
path: {
|
||||
type: 'keyword',
|
||||
},
|
||||
value: {
|
||||
// this is actually an 'any' type; however, ES does not really support that.
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
presets: {
|
||||
'type': {extends: ['sortable:ducet', 'filterable', 'aggregatable:global']},
|
||||
'text': {
|
||||
merge: {type: 'text'},
|
||||
},
|
||||
'filterable': {
|
||||
merge: {
|
||||
fields: {
|
||||
raw: {type: 'keyword'},
|
||||
},
|
||||
},
|
||||
},
|
||||
'sortable:ducet': {
|
||||
merge: {
|
||||
fields: {
|
||||
sort: {
|
||||
type: 'icu_collation_keyword',
|
||||
language: 'de',
|
||||
country: 'DE',
|
||||
variant: '@collation=phonebook',
|
||||
} as never,
|
||||
},
|
||||
},
|
||||
},
|
||||
'sortable': {
|
||||
extends: ['sortable:ducet'],
|
||||
},
|
||||
'sortable:price': {},
|
||||
'aggregatable': {
|
||||
search: {
|
||||
aggs: {
|
||||
'{type}': {
|
||||
aggs: {
|
||||
'{prop}': {
|
||||
terms: {
|
||||
field: '{prop}.raw',
|
||||
size: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
filter: {term: {type: '{type}'}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'aggregatable:global': {
|
||||
search: {
|
||||
aggs: {
|
||||
'@all.{prop}': {
|
||||
terms: {
|
||||
field: '{prop}.raw',
|
||||
size: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import {GeneratorOptions} from './index.js';
|
||||
import {JSONSchema7} from 'json-schema';
|
||||
import {ElasticsearchOptionsDSL} from '../dsl/schema.js';
|
||||
import {
|
||||
IndicesPutTemplateRequest,
|
||||
MappingProperty,
|
||||
MappingTypeMapping,
|
||||
SearchRequest,
|
||||
} from '@elastic/elasticsearch/lib/api/types.js';
|
||||
import {Context} from './context.js';
|
||||
import deepmerge from 'deepmerge';
|
||||
import {renderTemplate} from '../template.js';
|
||||
|
||||
/**
|
||||
* Sanitize a type name
|
||||
*/
|
||||
export function sanitizeTypeName(typeName: string): string {
|
||||
return typeName.replaceAll(' ', '_');
|
||||
}
|
||||
|
||||
export class MappingGenerator {
|
||||
readonly presets: Map<string, ElasticsearchOptionsDSL>;
|
||||
|
||||
readonly cache: Map<string, {mapping: MappingProperty; dependencies: Map<string, Set<string>>}>;
|
||||
|
||||
readonly searchMods: {mods: Partial<SearchRequest>} = {mods: {}};
|
||||
|
||||
readonly template: Partial<IndicesPutTemplateRequest>;
|
||||
|
||||
constructor(readonly project: JSONSchema7, options: GeneratorOptions) {
|
||||
this.template = options.template ?? {};
|
||||
this.presets = new Map(Object.entries(options.presets));
|
||||
this.cache = new Map(
|
||||
Object.entries(options.overrides).map(([name, mapping]) => [
|
||||
name,
|
||||
{
|
||||
mapping,
|
||||
dependencies: new Map(),
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
buildTemplate(name: string): IndicesPutTemplateRequest {
|
||||
const thingType = ((this.project.definitions![name] as JSONSchema7).properties!.type as JSONSchema7)
|
||||
.const;
|
||||
if (typeof thingType !== 'string') {
|
||||
throw new TypeError(`${name} needs a valid thing type`);
|
||||
}
|
||||
const mappingContext = new Context(this, thingType, [name], [], new Map());
|
||||
const mappings = mappingContext.resolveReference(name);
|
||||
|
||||
const request: IndicesPutTemplateRequest = deepmerge(
|
||||
{mappings: mappings as MappingTypeMapping},
|
||||
renderTemplate(this.template, [
|
||||
['{name}', name],
|
||||
['{type}', thingType],
|
||||
['{_type}', sanitizeTypeName(thingType)],
|
||||
]),
|
||||
);
|
||||
|
||||
if (mappingContext.dependencies.size > 0) {
|
||||
request.mappings!.dynamic_templates = [];
|
||||
for (const [name, paths] of mappingContext.dependencies) {
|
||||
request.mappings!.dynamic_templates.push({
|
||||
[name]: {
|
||||
path_match: paths.size > 1 ? [...paths] : paths.values().next().value,
|
||||
mapping: mappingContext.resolveReference(name),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
13
packages/es-mapping-generator/src/generator/tags.ts
Normal file
13
packages/es-mapping-generator/src/generator/tags.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import {JSONSchema7} from 'json-schema';
|
||||
|
||||
export const INDEXABLE_TAG_NAME = 'indexable';
|
||||
|
||||
/**
|
||||
* Get elasticsearch tags
|
||||
*/
|
||||
export function getTags(definition: JSONSchema7): Set<string> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return new Set(
|
||||
(definition as {elasticsearch: string}).elasticsearch?.split(/\s/).map(it => it.trim()) ?? [],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {JSONSchema7} from 'json-schema';
|
||||
import {MappingProperty} from '@elastic/elasticsearch/lib/api/types.js';
|
||||
import {transformDefinition} from '../definition.js';
|
||||
import {Context} from '../context.js';
|
||||
|
||||
/**
|
||||
* Transform a JSON Schema with `object` type
|
||||
*/
|
||||
export function transformObject(context: Context, definition: JSONSchema7): MappingProperty {
|
||||
const value = {
|
||||
dynamic: definition.additionalProperties === true ? undefined : 'strict',
|
||||
properties: {} as Record<string, MappingProperty>,
|
||||
} satisfies MappingProperty;
|
||||
|
||||
for (const key in definition.properties!) {
|
||||
value.properties[key] = transformDefinition(context.step(key), definition.properties[key] as JSONSchema7);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import {JSONSchema7} from 'json-schema';
|
||||
import {MappingProperty} from '@elastic/elasticsearch/lib/api/types.js';
|
||||
|
||||
const stringFormats = new Map<string, MappingProperty>([
|
||||
['date', {type: 'date', format: 'date'}],
|
||||
['time', {type: 'date', format: 'time'}],
|
||||
['date-time', {type: 'date', format: 'date_optional_time'}],
|
||||
['ipv4', {type: 'ip'}],
|
||||
['ipv6', {type: 'ip'}],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Transform a JSON Schema with `string` type
|
||||
*/
|
||||
export function transformString(definition: JSONSchema7): MappingProperty {
|
||||
return definition.format && stringFormats.has(definition.format)
|
||||
? stringFormats.get(definition.format)!
|
||||
: {type: 'keyword'};
|
||||
}
|
||||
@@ -1,7 +1,16 @@
|
||||
export * from './mapping';
|
||||
export * from './project-reflection';
|
||||
import {transformProject} from './generator/index.js';
|
||||
import {SchemaConsumer} from '@openstapps/json-schema-generator';
|
||||
|
||||
export * from './config/premap';
|
||||
export * from './config/fieldmap';
|
||||
export * from './config/settings';
|
||||
export * from './config/typemap';
|
||||
/**
|
||||
* JSON Schema Generator plugin for Elasticsearch Mappings
|
||||
*/
|
||||
export function elasticsearchMappingGenerator(fileName: string): [string, SchemaConsumer] {
|
||||
return [
|
||||
'Elasticsearch-Mappings',
|
||||
function (schema) {
|
||||
return {
|
||||
[fileName]: JSON.stringify(transformProject(schema)),
|
||||
};
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,894 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019-2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {
|
||||
MappingDynamicTemplate,
|
||||
MappingObjectProperty,
|
||||
MappingProperty,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import merge from 'deepmerge';
|
||||
import {stringify} from 'flatted';
|
||||
import {DeclarationReflection, ProjectReflection, SignatureReflection} from 'typedoc';
|
||||
import {
|
||||
ArrayType,
|
||||
Comment,
|
||||
CommentTag,
|
||||
IntrinsicType,
|
||||
ReferenceType,
|
||||
ReflectionType,
|
||||
StringLiteralType,
|
||||
Type,
|
||||
TypeParameterType,
|
||||
UnionType,
|
||||
} from 'typedoc/dist/lib/models';
|
||||
import {fieldmap, filterableMap, filterableTagName} from './config/fieldmap';
|
||||
import {premaps} from './config/premap';
|
||||
import {settings} from './config/settings';
|
||||
import {dynamicTypes, isTagType, MISSING_PREMAP, PARSE_ERROR, TYPE_CONFLICT, typemap} from './config/typemap';
|
||||
import type {AggregationSchema, ESNestedAggregation} from '../schema/aggregations';
|
||||
import type {ElasticsearchTemplateCollection, MappingGenTemplate} from '../schema/mappings';
|
||||
import * as console from 'console';
|
||||
|
||||
let dynamicTemplates: Record<string, MappingDynamicTemplate>[] = [];
|
||||
let errors: string[] = [];
|
||||
let showErrors = true;
|
||||
|
||||
let aggregations: AggregationSchema = {};
|
||||
|
||||
const indexableTag = 'indexable';
|
||||
const aggregatableTag = 'aggregatable';
|
||||
const aggregatableTagParameterGlobal = 'global';
|
||||
const inheritTagsName = 'inherittags';
|
||||
|
||||
// clamp printed object to 1000 chars to keep error messages readable
|
||||
const maxErrorObjectChars = 1000;
|
||||
|
||||
let ignoredTagsList = ['indexable', 'validatable', inheritTagsName];
|
||||
let inheritTagsMap: {[path: string]: CommentTag[]} = {};
|
||||
|
||||
/**
|
||||
* Gets all interfaces that have an @indexable tag
|
||||
* @param projectReflection the project reflection from which to extract the indexable interfaces
|
||||
*/
|
||||
export function getAllIndexableInterfaces(projectReflection: ProjectReflection): DeclarationReflection[] {
|
||||
let indexableInterfaces: DeclarationReflection[] = [];
|
||||
|
||||
if (!Array.isArray(projectReflection.children) || projectReflection.children.length === 0) {
|
||||
throw new Error('No DeclarationReflections found. Please check your input path');
|
||||
}
|
||||
|
||||
// push all declaration reflections into one array
|
||||
for (const declarationReflection of projectReflection.children) {
|
||||
if (Array.isArray(declarationReflection.children)) {
|
||||
indexableInterfaces = [...indexableInterfaces, ...declarationReflection.children];
|
||||
}
|
||||
}
|
||||
|
||||
// filter all declaration reflections with an @indexable tag
|
||||
indexableInterfaces = indexableInterfaces.filter(declarationReflection => {
|
||||
if (declarationReflection.comment === undefined || declarationReflection.comment.tags === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return declarationReflection.comment.tags.some(commentTag => {
|
||||
return commentTag.tagName === indexableTag;
|
||||
});
|
||||
});
|
||||
|
||||
return indexableInterfaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes error messages, that are readable and contain a certain minimum of information
|
||||
* @param path the path where the error took place
|
||||
* @param topTypeName the name of the SCThingType
|
||||
* @param typeName the name of the object, with which something went wrong
|
||||
* @param object the object or name
|
||||
* @param message the error message
|
||||
*/
|
||||
function composeErrorMessage(
|
||||
path: string,
|
||||
topTypeName: string,
|
||||
typeName: string,
|
||||
object: string,
|
||||
message: string,
|
||||
) {
|
||||
const error = `At "${topTypeName}::${path.slice(
|
||||
0,
|
||||
Math.max(0, path.length - 1),
|
||||
)}" for ${typeName} "${trimString(object, maxErrorObjectChars)}": ${message}`;
|
||||
errors.push(error);
|
||||
if (showErrors) {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
void console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trims a string to a readable size and appends "..."
|
||||
* @param value the string to trim
|
||||
* @param maxLength the maximum allowed length before it is clamped
|
||||
*/
|
||||
function trimString(value: string, maxLength: number): string {
|
||||
return value.length > maxLength ? `${value.slice(0, Math.max(0, maxLength))}...` : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Reflections and names for Generics in a ReferenceType of a DeclarationReflection
|
||||
*
|
||||
* Warning to future maintainers: The code for generics doesn't account for depth. when there is a new generic, it will
|
||||
* override the previous one, if there isn't, it will just continue passing it down.
|
||||
* @param type the ReferenceType of a DeclarationReflection
|
||||
* @param out the previous reflection, it then overrides all parameters or keeps old ones
|
||||
* @param topTypeName the name of the object, with which something went wrong
|
||||
* @param path the current path to the object we are in
|
||||
* @param tags any tags attached to the type
|
||||
*/
|
||||
function getReflectionGeneric(
|
||||
type: ReferenceType,
|
||||
out: Map<string, MappingProperty>,
|
||||
topTypeName: string,
|
||||
path: string,
|
||||
tags: CommentTag[],
|
||||
): Map<string, MappingProperty> {
|
||||
if (
|
||||
type.typeArguments !== undefined &&
|
||||
type.reflection instanceof DeclarationReflection &&
|
||||
type.reflection.typeParameters !== undefined
|
||||
) {
|
||||
for (let i = 0; i < type.reflection.typeParameters.length; i++) {
|
||||
if (i < type.typeArguments.length) {
|
||||
out.set(
|
||||
type.reflection.typeParameters[i].name,
|
||||
handleType(type.typeArguments[i], out, topTypeName, path, tags),
|
||||
);
|
||||
} else {
|
||||
// this can happen due to a bug in TypeDoc https://github.com/TypeStrong/typedoc/issues/1061
|
||||
// we have no way to know the type here, so we have to use this.
|
||||
out.set(type.reflection.typeParameters[i].name, {
|
||||
dynamic: true,
|
||||
properties: {},
|
||||
});
|
||||
|
||||
console.warn(
|
||||
`Type "${type.name}": Defaults of generics (Foo<T = any>) currently don't work due to a bug` +
|
||||
` in TypeDoc. It has been replaced by a dynamic type.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a ReferenceType that has no value
|
||||
*
|
||||
* Most of the times that is an external type.
|
||||
* @param ref the ReferenceType
|
||||
* @param generics the generics from levels above, so we can use them without having access to the parent
|
||||
* @param path the current path to the object we are in
|
||||
* @param topTypeName the name of the SCThingType
|
||||
* @param tags any tags attached to the type
|
||||
*/
|
||||
function handleExternalType(
|
||||
ref: ReferenceType,
|
||||
generics: Map<string, MappingProperty>,
|
||||
path: string,
|
||||
topTypeName: string,
|
||||
tags: CommentTag[],
|
||||
): MappingProperty {
|
||||
for (const premap of Object.keys(premaps)) {
|
||||
if (premap === ref.name) {
|
||||
return readFieldTags(premaps[premap], path, topTypeName, tags);
|
||||
}
|
||||
}
|
||||
|
||||
if (ref.name === 'Array') {
|
||||
// basically an external type, but Array is quite common, especially with generics
|
||||
if (ref.typeArguments === undefined || ref.typeArguments[0] === undefined) {
|
||||
composeErrorMessage(path, topTypeName, 'Array with generics', 'array', 'Failed to parse');
|
||||
|
||||
return {type: PARSE_ERROR};
|
||||
}
|
||||
|
||||
return readFieldTags(
|
||||
handleType(
|
||||
ref.typeArguments[0],
|
||||
getReflectionGeneric(ref, new Map(generics), path, topTypeName, tags),
|
||||
path,
|
||||
topTypeName,
|
||||
tags,
|
||||
),
|
||||
path,
|
||||
topTypeName,
|
||||
tags,
|
||||
);
|
||||
}
|
||||
if (ref.name === '__type') {
|
||||
// empty object
|
||||
return {
|
||||
dynamic: 'strict',
|
||||
properties: {},
|
||||
};
|
||||
}
|
||||
|
||||
composeErrorMessage(path, topTypeName, 'external type', ref.name, 'Missing pre-map');
|
||||
|
||||
return readFieldTags({type: MISSING_PREMAP}, path, topTypeName, tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an object
|
||||
* @param decl the DeclarationReflection of the object
|
||||
* @param generics the generics from levels above, so we can use them without having access to the parent
|
||||
* @param path the current path to the object we are in
|
||||
* @param topTypeName the name of the SCThingType
|
||||
* @param inheritedTags the inherited tags
|
||||
*/
|
||||
function handleDeclarationReflection(
|
||||
decl: DeclarationReflection,
|
||||
generics: Map<string, MappingProperty>,
|
||||
path: string,
|
||||
topTypeName: string,
|
||||
inheritedTags?: CommentTag[],
|
||||
): MappingProperty {
|
||||
// check if we have an object referencing a generic
|
||||
if (generics.has(decl.name)) {
|
||||
// if the object name is the same as the generic name
|
||||
return readFieldTags(generics.get(decl.name)!, path, topTypeName, decl.comment?.tags ?? []);
|
||||
// use the value defined by the generic
|
||||
}
|
||||
|
||||
// start the actual handling process
|
||||
const out: MappingProperty = {
|
||||
dynamic: 'strict',
|
||||
properties: {},
|
||||
};
|
||||
|
||||
let empty = true;
|
||||
// first check if there are any index signatures, so for example `[name: string]: Foo`
|
||||
if (decl.indexSignature !== undefined) {
|
||||
out.dynamic = true;
|
||||
|
||||
if (decl.indexSignature.type !== undefined) {
|
||||
empty = false;
|
||||
const template: Record<string, MappingDynamicTemplate> = {};
|
||||
template[decl.name] = {
|
||||
mapping: handleType(
|
||||
decl.indexSignature.type,
|
||||
new Map(generics),
|
||||
path,
|
||||
topTypeName,
|
||||
getCommentTags(decl.indexSignature, path, topTypeName),
|
||||
),
|
||||
match: '*',
|
||||
match_mapping_type: '*',
|
||||
path_match: `${path}*`,
|
||||
};
|
||||
dynamicTemplates.push(template);
|
||||
}
|
||||
}
|
||||
|
||||
if (decl.kindString === 'Enumeration') {
|
||||
return readTypeTags('string', path, topTypeName, getCommentTags(decl, path, topTypeName, inheritedTags));
|
||||
}
|
||||
|
||||
// check all the children, so in this case we are dealing with an OBJECT
|
||||
if (decl.children !== undefined && decl.children.length > 0) {
|
||||
for (const child of decl.children) {
|
||||
empty = false;
|
||||
out.properties![child.name] = handleDeclarationReflection(
|
||||
child,
|
||||
new Map(generics),
|
||||
`${path}${child.name}.`,
|
||||
topTypeName,
|
||||
);
|
||||
}
|
||||
} else if (decl.type instanceof Type) {
|
||||
// if the object is a type, so we are dealing with a PROPERTY
|
||||
// get inherited tags
|
||||
const tags =
|
||||
(inheritedTags ?? []).length > 0
|
||||
? inheritedTags!.some(it => isTagType(it.tagName))
|
||||
? inheritedTags!
|
||||
: [...(inheritedTags ?? []), ...getCommentTags(decl, path, topTypeName)]
|
||||
: getCommentTags(decl, path, topTypeName);
|
||||
|
||||
return handleType(decl.type, new Map(generics), path, topTypeName, tags);
|
||||
} else if (decl.kindString === 'Enumeration member') {
|
||||
return readTypeTags(
|
||||
typeof decl.defaultValue,
|
||||
path,
|
||||
topTypeName,
|
||||
getCommentTags(decl, path, topTypeName, inheritedTags),
|
||||
);
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
composeErrorMessage(path, topTypeName, 'object', decl.name, 'Empty object');
|
||||
}
|
||||
|
||||
return readFieldTags(out, path, topTypeName, getCommentTags(decl, path, topTypeName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all comment tags, including inherited ones
|
||||
* @param decl the DeclarationReflection to read the tags from
|
||||
* @param path the path on which the comments lie
|
||||
* @param topTypeName the name of the SCThingType
|
||||
* @param inheritedTags any tags that might have been inherited by a parent
|
||||
* @param breakId the id of the previous reflection to prevent infinite recursion in some cases
|
||||
*/
|
||||
function getCommentTags(
|
||||
decl: DeclarationReflection | SignatureReflection,
|
||||
path: string,
|
||||
topTypeName: string,
|
||||
inheritedTags: CommentTag[] = [],
|
||||
// tslint:disable-next-line:no-unnecessary-initializer
|
||||
breakId: number | undefined = undefined,
|
||||
): CommentTag[] {
|
||||
if (decl.id === breakId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let out: CommentTag[] =
|
||||
decl.comment instanceof Comment
|
||||
? decl.comment.tags !== undefined
|
||||
? decl.comment.tags
|
||||
: inheritedTags
|
||||
: inheritedTags;
|
||||
if (
|
||||
decl.overwrites instanceof ReferenceType &&
|
||||
decl.overwrites.reflection instanceof DeclarationReflection
|
||||
) {
|
||||
out = arrayPriorityJoin(
|
||||
getCommentTags(decl.overwrites.reflection, path, topTypeName, inheritedTags, decl.id),
|
||||
out,
|
||||
);
|
||||
}
|
||||
if (
|
||||
decl.inheritedFrom instanceof ReferenceType &&
|
||||
decl.inheritedFrom.reflection instanceof DeclarationReflection
|
||||
) {
|
||||
out = arrayPriorityJoin(
|
||||
getCommentTags(decl.inheritedFrom.reflection, path, topTypeName, inheritedTags, decl.id),
|
||||
out,
|
||||
);
|
||||
}
|
||||
|
||||
saveCommentTags(out, path, topTypeName);
|
||||
const inheritTag = out.find(value => value.tagName === inheritTagsName);
|
||||
if (inheritTag !== undefined) {
|
||||
out = arrayPriorityJoin(out, retrieveCommentTags(inheritTag.text.trim(), path, topTypeName));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all comment tags to the map
|
||||
* @param tags all tags to be saved (@see and @[inheritTags] will be stripped)
|
||||
* @param path the path of field
|
||||
* @param topTypeName the name of the SCThingType
|
||||
*/
|
||||
function saveCommentTags(tags: CommentTag[], path: string, topTypeName: string) {
|
||||
inheritTagsMap[`${topTypeName}::${path.slice(0, Math.max(0, path.length - 1))}`] = tags.filter(
|
||||
value => value.tagName !== 'see' && value.tagName !== inheritTagsName,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves any saved tags
|
||||
* @param path the path to the original field
|
||||
* @param currentPath the current path to the object we are in
|
||||
* @param topTypeName the name of the SCThingType
|
||||
*/
|
||||
function retrieveCommentTags(path: string, currentPath: string, topTypeName: string): CommentTag[] {
|
||||
if (!(path in inheritTagsMap)) {
|
||||
composeErrorMessage(currentPath, topTypeName, path, 'Comment', 'Referenced path to tags does not exist!');
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return inheritTagsMap[path];
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins two arrays of CommentTags, but overrides all original CommentTags with the same tagName
|
||||
* @param originals the original array
|
||||
* @param overrider the array that should be appended and provide the override values
|
||||
*/
|
||||
function arrayPriorityJoin(originals: CommentTag[], overrider: CommentTag[]): CommentTag[] {
|
||||
const out: CommentTag[] = overrider;
|
||||
|
||||
for (const original of originals) {
|
||||
const result = overrider.find(element => original.tagName === element.tagName);
|
||||
|
||||
// no support for multiple tags with the same name
|
||||
if (!(result instanceof CommentTag)) {
|
||||
out.push(original);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles UnionTypes
|
||||
*
|
||||
* Put into a separate function as it is a little bit more complex
|
||||
* Works fairly reliable, although there are issues with primitive union types, which don't work at all (And never will)
|
||||
* @param type the type object
|
||||
* @param generics the generics from levels above, so we can use them without having access to the parent
|
||||
* @param path the current path to the object we are in
|
||||
* @param topTypeName the name of the SCThingType
|
||||
* @param tags any tags attached to the type
|
||||
*/
|
||||
function handleUnionType(
|
||||
type: UnionType,
|
||||
generics: Map<string, MappingProperty>,
|
||||
path: string,
|
||||
topTypeName: string,
|
||||
tags: CommentTag[],
|
||||
): MappingProperty {
|
||||
const list: MappingProperty[] = [];
|
||||
|
||||
for (const subType of type.types) {
|
||||
if (subType instanceof IntrinsicType && subType.name === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
list.push(handleType(subType, new Map(generics), path, topTypeName, tags));
|
||||
}
|
||||
|
||||
if (list.length > 0) {
|
||||
let out = list[0];
|
||||
|
||||
for (const item of list) {
|
||||
out = merge<MappingProperty>(out, item);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
composeErrorMessage(
|
||||
path,
|
||||
topTypeName,
|
||||
'Union Type',
|
||||
stringify(list),
|
||||
'Empty union type. This is likely not a user error.',
|
||||
);
|
||||
|
||||
return {type: PARSE_ERROR};
|
||||
}
|
||||
|
||||
/**
|
||||
* Serves as a kind of distributor for the different types, should not contain any specific code
|
||||
* @param type the type object
|
||||
* @param generics the generics from levels above, so we can use them without having access to the parent
|
||||
* @param path the current path to the object we are in
|
||||
* @param topTypeName the name of the SCThingType
|
||||
* @param tags any tags attached to the type
|
||||
*/
|
||||
function handleType(
|
||||
type: Type,
|
||||
generics: Map<string, MappingProperty>,
|
||||
path: string,
|
||||
topTypeName: string,
|
||||
tags: CommentTag[],
|
||||
): MappingProperty {
|
||||
// logger.log((type as any).name);
|
||||
if (type instanceof ArrayType) {
|
||||
// array is irrelevant in Elasticsearch, so just go with the element type
|
||||
const esType = handleType(type.elementType, new Map(generics), path, topTypeName, tags);
|
||||
// also merge tags of the array to the element type
|
||||
// filter out the type tags lazily, this can lead to double messages for "Not implemented tag"
|
||||
let newTags = tags;
|
||||
if ('type' in esType) {
|
||||
newTags = tags.filter(tag => {
|
||||
return !(tag.tagName === esType.type);
|
||||
});
|
||||
}
|
||||
|
||||
return readFieldTags(esType, path, topTypeName, newTags);
|
||||
}
|
||||
if (type.type === 'stringLiteral') {
|
||||
// a string literal, usually for type
|
||||
return readTypeTags(type.type, path, topTypeName, tags);
|
||||
}
|
||||
if (type instanceof IntrinsicType) {
|
||||
// the absolute default type, like strings
|
||||
return readTypeTags(type.name, path, topTypeName, tags);
|
||||
}
|
||||
if (type instanceof UnionType) {
|
||||
// the union type...
|
||||
return handleUnionType(type, new Map(generics), path, topTypeName, tags);
|
||||
}
|
||||
if (type instanceof ReferenceType) {
|
||||
if (premaps[type.name] === undefined && type.reflection !== undefined) {
|
||||
// there is really no way to make this typesafe, every element in DeclarationReflection is optional.
|
||||
return handleDeclarationReflection(
|
||||
type.reflection as DeclarationReflection,
|
||||
getReflectionGeneric(type, new Map(generics), path, topTypeName, tags),
|
||||
path,
|
||||
topTypeName,
|
||||
tags,
|
||||
);
|
||||
}
|
||||
|
||||
return handleExternalType(type, new Map(generics), path, topTypeName, tags);
|
||||
}
|
||||
if (type instanceof TypeParameterType) {
|
||||
// check if we have an object referencing a generic
|
||||
if (generics.has(type.name)) {
|
||||
return generics.get(type.name)!;
|
||||
}
|
||||
composeErrorMessage(path, topTypeName, 'Generic', type.name, 'Missing reflection, please report!');
|
||||
|
||||
return {type: PARSE_ERROR};
|
||||
}
|
||||
if (type instanceof ReflectionType) {
|
||||
return readFieldTags(
|
||||
handleDeclarationReflection(type.declaration, new Map(generics), path, topTypeName),
|
||||
path,
|
||||
topTypeName,
|
||||
tags,
|
||||
);
|
||||
}
|
||||
|
||||
composeErrorMessage(path, topTypeName, 'type', stringify(type), 'Not implemented type');
|
||||
|
||||
return {type: PARSE_ERROR};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an aggregatable to the aggregations list
|
||||
* @param path the current path
|
||||
* @param topTypeName the name of the top type
|
||||
* @param global whether the topTypeName will be used
|
||||
*/
|
||||
function addAggregatable(path: string, topTypeName: string, global: boolean) {
|
||||
// push type.path and remove the '.' at the end of the path
|
||||
|
||||
if (global) {
|
||||
const property_ = path.slice(0, -1).split('.').pop() as string; // cannot be undefined
|
||||
|
||||
return ((aggregations['@all'] as ESNestedAggregation).aggs[property_.split('.').pop() as string] = {
|
||||
terms: {
|
||||
field: `${property_}.raw`,
|
||||
size: 1000,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const property = path.slice(0, -1);
|
||||
|
||||
return ((aggregations[topTypeName] as ESNestedAggregation).aggs[property] = {
|
||||
terms: {
|
||||
field: `${property}.raw`,
|
||||
size: 1000,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all tags related to Elasticsearch fields from the fieldMap
|
||||
* @param previous the previous ElasticsearchValue, for example and object
|
||||
* @param path the current path to the object we are in
|
||||
* @param topTypeName the name of the SCThingType
|
||||
* @param tags tags attached to the value
|
||||
* @param dataType the ElasticsearchDataType, for checking if a tag is a type tag
|
||||
*/
|
||||
function readFieldTags(
|
||||
previous: MappingProperty,
|
||||
path: string,
|
||||
topTypeName: string,
|
||||
tags: CommentTag[],
|
||||
dataType?: string,
|
||||
): MappingProperty {
|
||||
for (const tag of tags) {
|
||||
if (tag.tagName === aggregatableTag) {
|
||||
addAggregatable(path, topTypeName, tag.text.trim() === aggregatableTagParameterGlobal);
|
||||
}
|
||||
|
||||
if (!ignoredTagsList.includes(tag.tagName)) {
|
||||
if (fieldmap[tag.tagName] !== undefined) {
|
||||
if (previous.fields === undefined) {
|
||||
// create in case it doesn't exist
|
||||
previous.fields = {};
|
||||
}
|
||||
if (tag.text.trim() === '') {
|
||||
// merge fields
|
||||
previous.fields = {...previous.fields, ...fieldmap[tag.tagName].default};
|
||||
} else if (fieldmap[tag.tagName][tag.text.trim()] !== undefined) {
|
||||
// merge fields
|
||||
previous.fields = {...previous.fields, ...fieldmap[tag.tagName][tag.text.trim()]};
|
||||
} else if (!fieldmap[tag.tagName].ignore.includes(tag.text.trim())) {
|
||||
// when there is an unidentified tag
|
||||
composeErrorMessage(
|
||||
path,
|
||||
topTypeName,
|
||||
'tag',
|
||||
tag.tagName,
|
||||
`Not implemented tag param "${tag.text.trim()}"`,
|
||||
);
|
||||
}
|
||||
} else if (tag.tagName === filterableTagName) {
|
||||
if (previous.fields === undefined) {
|
||||
previous.fields = {};
|
||||
}
|
||||
if ('type' in previous) {
|
||||
const type = filterableMap[previous.type!];
|
||||
if (type !== undefined) {
|
||||
// merge fields
|
||||
previous.fields = {...previous.fields, raw: {type: type}};
|
||||
} else {
|
||||
composeErrorMessage(
|
||||
path,
|
||||
topTypeName,
|
||||
'tag',
|
||||
tag.tagName,
|
||||
`Not implemented for ${previous.type}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
composeErrorMessage(path, topTypeName, 'tag', tag.tagName, 'Not applicable for object types');
|
||||
}
|
||||
} else if (dataType === undefined || typemap[dataType][tag.tagName] === undefined) {
|
||||
composeErrorMessage(path, topTypeName, 'tag', tag.tagName, `Not implemented tag`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all types related to Elasticsearch fields from the fieldMap
|
||||
* @param type the type of the value
|
||||
* @param path the current path to the object we are in
|
||||
* @param topTypeName the name of the SCThingType
|
||||
* @param tags tags attached to the value
|
||||
*/
|
||||
function readTypeTags(type: string, path: string, topTypeName: string, tags: CommentTag[]): MappingProperty {
|
||||
let out: MappingProperty = {type: PARSE_ERROR};
|
||||
|
||||
if (typemap[type] !== undefined) {
|
||||
// first look if the value has a definition in the typemap
|
||||
for (let i = tags.length - 1; i >= 0; i--) {
|
||||
if (!ignoredTagsList.includes(tags[i].tagName) && typemap[type][tags[i].tagName] !== undefined) {
|
||||
// if we have a tag that indicates a type
|
||||
if (out.type !== PARSE_ERROR) {
|
||||
composeErrorMessage(
|
||||
path,
|
||||
topTypeName,
|
||||
'type',
|
||||
type,
|
||||
`Type conflict; "${typemap[type][tags[i].tagName]}" would override "${
|
||||
(out as MappingProperty).type
|
||||
}"`,
|
||||
);
|
||||
(out as MappingProperty).type = TYPE_CONFLICT;
|
||||
continue;
|
||||
}
|
||||
(out as MappingProperty).type = typemap[type][tags[i].tagName];
|
||||
}
|
||||
}
|
||||
|
||||
if (out.type === PARSE_ERROR) {
|
||||
(out as MappingProperty).type = typemap[type].default;
|
||||
}
|
||||
|
||||
out = readFieldTags(out, path, topTypeName, tags, type);
|
||||
|
||||
return out;
|
||||
}
|
||||
if (dynamicTypes.includes(type)) {
|
||||
// Elasticsearch dynamic type TODO: doesn't work for direct types
|
||||
return {
|
||||
dynamic: true,
|
||||
properties: {},
|
||||
};
|
||||
}
|
||||
|
||||
composeErrorMessage(path, topTypeName, 'type', type, 'Not implemented type');
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the state
|
||||
*
|
||||
* This is kind of a suboptimal solution and should be changed in the future.
|
||||
* https://gitlab.com/openstapps/core-tools/-/issues/49
|
||||
* @param resetInheritTags whether inherited tags should be reset as well
|
||||
*/
|
||||
function reset(resetInheritTags = true) {
|
||||
errors = [];
|
||||
dynamicTemplates = [];
|
||||
aggregations = {
|
||||
'@all': {
|
||||
aggs: {},
|
||||
filter: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (resetInheritTags) {
|
||||
inheritTagsMap = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a project reflection and generates an ElasticsearchTemplate from it
|
||||
*
|
||||
* Serves as the entry point for getting the mapping, so if you just want to get the mapping files for Elasticsearch,
|
||||
* you can do so by calling this function, `RETURNED_VALUE.template` contains the mapping in a fashion that is directly
|
||||
* readable by Elasticsearch.
|
||||
* @param projectReflection a reflection of the project you want to get the ES Mappings from
|
||||
* @param ignoredTags the tag names for which the error output should be suppressed
|
||||
* @param showErrorOutput whether to print all errors in the command line or not
|
||||
* @param interfaceFilter only parse specific interfaces, this is for testing purposes
|
||||
*/
|
||||
export function generateTemplate(
|
||||
projectReflection: ProjectReflection,
|
||||
ignoredTags: string[],
|
||||
showErrorOutput = true,
|
||||
interfaceFilter: string[] = [],
|
||||
): MappingGenTemplate {
|
||||
reset();
|
||||
|
||||
showErrors = showErrorOutput;
|
||||
|
||||
ignoredTagsList = ['indexable', 'validatable', inheritTagsName];
|
||||
// eslint-disable-next-line prefer-spread
|
||||
ignoredTagsList.push.apply(ignoredTagsList, ignoredTags);
|
||||
|
||||
const indexableInterfaces = getAllIndexableInterfaces(projectReflection);
|
||||
|
||||
const out: ElasticsearchTemplateCollection = {};
|
||||
|
||||
for (const _interface of indexableInterfaces) {
|
||||
// TODO: lots of duplicate code, this all needs to be changed https://gitlab.com/openstapps/core-tools/-/issues/49
|
||||
if (!Array.isArray(_interface.children) || _interface.children.length === 0) {
|
||||
throw new Error('Interface needs at least some properties to be indexable');
|
||||
}
|
||||
|
||||
const typeObject = _interface.children.find(declarationReflection => {
|
||||
return declarationReflection.name === 'type';
|
||||
});
|
||||
|
||||
if (typeObject === undefined || typeObject.type === undefined) {
|
||||
throw new TypeError('Interface needs a type to be indexable');
|
||||
}
|
||||
|
||||
let typeName = 'INVALID_TYPE';
|
||||
if (typeObject.type instanceof ReferenceType) {
|
||||
if (
|
||||
typeObject.type.reflection instanceof DeclarationReflection &&
|
||||
typeof typeObject.type.reflection.defaultValue === 'string'
|
||||
) {
|
||||
typeName = typeObject.type.reflection.defaultValue.replace('"', '').replace('"', '');
|
||||
} else {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
void console.error(
|
||||
'Your input files seem to be incorrect, or there is a major bug in the mapping generator.',
|
||||
);
|
||||
}
|
||||
} else if (typeObject.type instanceof StringLiteralType) {
|
||||
console.warn(`The interface ${_interface.name} uses a string literal as type, please use SCThingType.`);
|
||||
typeName = typeObject.type.value;
|
||||
} else {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
void console.error(
|
||||
`The interface ${_interface.name} is required to use an SCThingType as a type, please do so.`,
|
||||
);
|
||||
}
|
||||
// init aggregation schema for type
|
||||
aggregations[typeName] = {
|
||||
aggs: {},
|
||||
filter: {
|
||||
term: {
|
||||
type: typeName,
|
||||
},
|
||||
},
|
||||
};
|
||||
handleDeclarationReflection(_interface, new Map(), '', typeName);
|
||||
}
|
||||
|
||||
// second traversal
|
||||
reset(false);
|
||||
|
||||
for (const _interface of indexableInterfaces) {
|
||||
if (!Array.isArray(_interface.children) || _interface.children.length === 0) {
|
||||
throw new Error('Interface needs at least some properties to be indexable');
|
||||
}
|
||||
|
||||
const typeObject = _interface.children.find(declarationReflection => {
|
||||
return declarationReflection.name === 'type';
|
||||
});
|
||||
|
||||
if (typeObject === undefined || typeObject.type === undefined) {
|
||||
throw new TypeError('Interface needs a type to be indexable');
|
||||
}
|
||||
|
||||
let typeName = 'INVALID_TYPE';
|
||||
if (typeObject.type instanceof ReferenceType) {
|
||||
if (
|
||||
typeObject.type.reflection instanceof DeclarationReflection &&
|
||||
typeof typeObject.type.reflection.defaultValue === 'string'
|
||||
) {
|
||||
typeName = typeObject.type.reflection.defaultValue.replace('"', '').replace('"', '');
|
||||
} else {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
void console.error(
|
||||
'Your input files seem to be incorrect, or there is a major bug in the mapping generator.',
|
||||
);
|
||||
}
|
||||
} else if (typeObject.type instanceof StringLiteralType) {
|
||||
console.warn(`The interface ${_interface.name} uses a string literal as type, please use SCThingType.`);
|
||||
typeName = typeObject.type.value;
|
||||
} else {
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
void console.error(
|
||||
`The interface ${_interface.name} is required to use an SCThingType as a type, please do so.`,
|
||||
);
|
||||
}
|
||||
|
||||
// filter out
|
||||
if (interfaceFilter.length > 0 && !interfaceFilter.includes(typeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// init aggregation schema for type
|
||||
aggregations[typeName] = {
|
||||
aggs: {},
|
||||
filter: {
|
||||
term: {
|
||||
type: typeName,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-string-replace-all
|
||||
const typeNameWithoutSpaces = typeName.toLowerCase().replace(/\s/g, '_');
|
||||
const templateName = `template_${typeNameWithoutSpaces}`;
|
||||
|
||||
out[templateName] = {
|
||||
mappings: handleDeclarationReflection(_interface, new Map(), '', typeName) as MappingObjectProperty,
|
||||
settings: settings,
|
||||
index_patterns: [`stapps_${typeNameWithoutSpaces}*`],
|
||||
};
|
||||
out[templateName].mappings!.properties!.creation_date = {
|
||||
type: 'date',
|
||||
};
|
||||
|
||||
out[templateName].mappings!.dynamic_templates = dynamicTemplates;
|
||||
|
||||
// Set some properties
|
||||
out[templateName].mappings!._source = {
|
||||
excludes: ['creation_date'],
|
||||
};
|
||||
out[templateName].mappings!.date_detection = false;
|
||||
|
||||
dynamicTemplates = [];
|
||||
|
||||
if (Object.keys((aggregations[typeName] as ESNestedAggregation).aggs).length === 0) {
|
||||
delete aggregations[typeName];
|
||||
}
|
||||
}
|
||||
|
||||
return {aggregations, mappings: out, errors};
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018-2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {existsSync, PathLike} from 'fs';
|
||||
import {platform} from 'os';
|
||||
import path from 'path';
|
||||
import {Application, ProjectReflection} from 'typedoc';
|
||||
|
||||
/**
|
||||
* Get a project reflection from a path
|
||||
* @param sourcePath Path to get reflection from
|
||||
* @param excludeExternals Exclude external dependencies
|
||||
*/
|
||||
export function getProjectReflection(sourcePath: PathLike, excludeExternals = true): ProjectReflection {
|
||||
console.info(`Generating project reflection for ${sourcePath.toString()}.`);
|
||||
|
||||
const tsconfigPath = getTsconfigPath(sourcePath.toString());
|
||||
|
||||
// initialize new Typedoc application
|
||||
const app = new Application();
|
||||
|
||||
app.bootstrap({
|
||||
excludeExternals: excludeExternals,
|
||||
ignoreCompilerErrors: true,
|
||||
includeDeclarations: true,
|
||||
tsconfig: path.join(tsconfigPath, 'tsconfig.json'),
|
||||
});
|
||||
|
||||
let inputFilePath = sourcePath;
|
||||
if (inputFilePath === tsconfigPath) {
|
||||
inputFilePath = path.join(tsconfigPath, 'src');
|
||||
}
|
||||
|
||||
// get input files
|
||||
const inputFiles = app.expandInputFiles([inputFilePath.toString()]);
|
||||
|
||||
// get project reflection from input files
|
||||
const result = app.convert(inputFiles);
|
||||
|
||||
if (result === undefined) {
|
||||
throw new TypeError('Project reflection could not be generated.');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path that contains a tsconfig.json
|
||||
* @param startPath Path from where to start searching "upwards"
|
||||
*/
|
||||
export function getTsconfigPath(startPath: string): string {
|
||||
let tsconfigPath = startPath;
|
||||
|
||||
// see https://stackoverflow.com/questions/9652043/identifying-the-file-system-root-with-node-js
|
||||
const root = platform() === 'win32' ? process.cwd().split(path.sep)[0] : '/';
|
||||
|
||||
// repeat until a tsconfig.json is found
|
||||
while (!existsSync(path.join(tsconfigPath, 'tsconfig.json'))) {
|
||||
if (tsconfigPath === root) {
|
||||
throw new Error(
|
||||
`Reached file system root ${root} while searching for 'tsconfig.json' in ${startPath}!`,
|
||||
);
|
||||
}
|
||||
|
||||
// pop last directory
|
||||
const tsconfigPathParts = tsconfigPath.split(path.sep);
|
||||
tsconfigPathParts.pop();
|
||||
tsconfigPath = tsconfigPathParts.join(path.sep);
|
||||
}
|
||||
|
||||
console.info(`Using 'tsconfig.json' from ${tsconfigPath}.`);
|
||||
|
||||
return tsconfigPath;
|
||||
}
|
||||
13
packages/es-mapping-generator/src/template.ts
Normal file
13
packages/es-mapping-generator/src/template.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Render a template
|
||||
* @param template The template to render (must be stringify-able)
|
||||
* @param substitutions the substitutions
|
||||
*/
|
||||
export function renderTemplate<T>(template: T, substitutions: [string, string][]): T {
|
||||
return JSON.parse(
|
||||
substitutions.reduce(
|
||||
(template, [search, replace]) => template.replaceAll(search, replace),
|
||||
JSON.stringify(template),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -15,25 +15,28 @@
|
||||
import {ThingType} from './types';
|
||||
import {MapAggTestOptions} from '../../map-agg-test-options';
|
||||
|
||||
/** @integer */
|
||||
export type Integer = number;
|
||||
|
||||
/**
|
||||
* @indexable
|
||||
*/
|
||||
export interface InheritedProperty extends Bar {
|
||||
foo: number;
|
||||
/** @filterable */
|
||||
foo: Integer;
|
||||
|
||||
type: ThingType.InheritedProperty;
|
||||
// type: ThingType.InheritedProperty;
|
||||
}
|
||||
|
||||
interface Bar {
|
||||
/**
|
||||
* @keyword
|
||||
*/
|
||||
bar: string;
|
||||
|
||||
//bar: string;
|
||||
/**
|
||||
* @float
|
||||
*/
|
||||
baz: number;
|
||||
// baz: number;
|
||||
}
|
||||
|
||||
export const testConfig: MapAggTestOptions = {
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"alwaysStrict": true,
|
||||
"charset": "utf8",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"noErrorTruncation": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "./lib/",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "ES2020"
|
||||
},
|
||||
"exclude": ["./lib/", "./test/"]
|
||||
"extends": "@openstapps/tsconfig"
|
||||
}
|
||||
|
||||
12
packages/es-mapping-generator/tsup.config.ts
Normal file
12
packages/es-mapping-generator/tsup.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {defineConfig} from 'tsup';
|
||||
import {jsonSchemaPlugin} from '@openstapps/json-schema-generator';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
format: 'esm',
|
||||
outDir: 'lib',
|
||||
external: ['./index.schema.json'],
|
||||
plugins: [jsonSchemaPlugin('index.schema.json')],
|
||||
});
|
||||
78
packages/json-schema-generator/package.json
Normal file
78
packages/json-schema-generator/package.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "@openstapps/json-schema-generator",
|
||||
"description": "Validator for @openstapps/core",
|
||||
"version": "3.0.0",
|
||||
"type": "module",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": "git@gitlab.com:openstapps/openstapps.git",
|
||||
"author": "Thea Schöbl <dev@theaninova.de>",
|
||||
"keywords": [
|
||||
"StApps",
|
||||
"StAppsCore",
|
||||
"converter",
|
||||
"core",
|
||||
"validator"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"README.md",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup-node --dts",
|
||||
"docs": "typedoc --json ./docs/docs.json --options ../../typedoc.base.json src/index.ts",
|
||||
"format": "prettier . -c --ignore-path ../../.gitignore",
|
||||
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint --ext .ts src/",
|
||||
"lint:fix": "eslint --fix --ext .ts src/",
|
||||
"test": "c8 mocha"
|
||||
},
|
||||
"dependencies": {
|
||||
"@openstapps/tsup-plugin": "workspace:*",
|
||||
"deepmerge": "4.3.1",
|
||||
"ts-json-schema-generator": "1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openstapps/eslint-config": "workspace:*",
|
||||
"@openstapps/prettier-config": "workspace:*",
|
||||
"@openstapps/tsconfig": "workspace:*",
|
||||
"@types/chai": "4.3.5",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/glob": "8.0.1",
|
||||
"@types/json-schema": "7.0.14",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/mustache": "4.2.2",
|
||||
"@types/node": "18.15.3",
|
||||
"c8": "7.14.0",
|
||||
"chai": "4.3.7",
|
||||
"esbuild": "0.17.19",
|
||||
"mocha": "10.2.0",
|
||||
"mocha-junit-reporter": "2.2.0",
|
||||
"nock": "13.3.1",
|
||||
"ts-node": "10.9.1",
|
||||
"tsup": "6.7.0",
|
||||
"typedoc": "0.24.8",
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"tsup": {
|
||||
"entry": [
|
||||
"src/app.ts",
|
||||
"src/index.ts"
|
||||
],
|
||||
"sourcemap": true,
|
||||
"clean": true,
|
||||
"format": "esm",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"prettier": "@openstapps/prettier-config",
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"@openstapps"
|
||||
]
|
||||
},
|
||||
"eslintIgnore": [
|
||||
"resources"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {createGenerator, SchemaGenerator} from 'ts-json-schema-generator';
|
||||
import {getValidatableTypes} from './get-validatable-types.js';
|
||||
|
||||
/**
|
||||
* Compile the JSON schema for a path
|
||||
*/
|
||||
export function compileSchema(path: string, tsconfig: string): ReturnType<SchemaGenerator['createSchema']> {
|
||||
const generator = createGenerator({
|
||||
path,
|
||||
tsconfig,
|
||||
extraTags: ['elasticsearch'],
|
||||
skipTypeCheck: true,
|
||||
});
|
||||
// @ts-expect-error private access
|
||||
const program = generator.program;
|
||||
const schemaNames = getValidatableTypes(program);
|
||||
const fullSchema = {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
definitions: {},
|
||||
};
|
||||
|
||||
for (const schema of schemaNames) {
|
||||
Object.assign(fullSchema.definitions, generator.createSchema(schema).definitions);
|
||||
}
|
||||
|
||||
return fullSchema;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user