From 77e49146c0619566919815bd5d63ddf34dc19387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wieland=20Sch=C3=B6bl?= Date: Tue, 20 Aug 2019 17:48:37 +0200 Subject: [PATCH] fix: read type tags correctly after the first time --- src/cli.ts | 9 +- src/mapping.ts | 130 +++++++++++++++------------ src/mappings/definitions/fieldmap.ts | 1 + src/mappings/definitions/premap.ts | 10 ++- 4 files changed, 88 insertions(+), 62 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 3e8c13d6..7668fb8a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -93,11 +93,16 @@ commander // get project reflection const projectReflection = getProjectReflection(srcPath); - const mapping = generateTemplate(projectReflection, ignoredTagsList); + const result = generateTemplate(projectReflection, ignoredTagsList, true); + if (result.errors.length !== 0) { + await Logger.error('Mapping generated with errors!'); + } else { + Logger.ok('Mapping generated without errors!'); + } // write documentation to file // tslint:disable-next-line:no-magic-numbers - writeFileSync(mappingPath, JSON.stringify(mapping.template, null, 2)); + writeFileSync(mappingPath, JSON.stringify(result.template, null, 2)); Logger.ok(`Elasticsearch mapping written to ${mappingPath}.`); }); diff --git a/src/mapping.ts b/src/mapping.ts index db2d97e0..72ef7109 100644 --- a/src/mapping.ts +++ b/src/mapping.ts @@ -108,8 +108,11 @@ function composeErrorMessage(path: string, typeName: string, object: string, mes * @param type the ReferenceType of a DeclarationReflection * @param out the previous reflection, it then overrides all parameters or keeps old ones * @param path the current path to the object we are in + * @param tags any tags attached to the type */ -function getReflectionGeneric(type: ReferenceType, out: ReflectionGeneric[], path: string): ReflectionGeneric[] { +function getReflectionGeneric(type: ReferenceType, + out: ReflectionGeneric[], + path: string, tags: CommentTag[]): ReflectionGeneric[] { if (typeof type.typeArguments !== 'undefined' && type.reflection instanceof DeclarationReflection && typeof type.reflection.typeParameters !== 'undefined' @@ -118,14 +121,14 @@ function getReflectionGeneric(type: ReferenceType, out: ReflectionGeneric[], pat let replaced = false; for (const old of out) { if (old.name === type.reflection.typeParameters[i].name) { - old.value = handleType(type.typeArguments[i], out, path); + old.value = handleType(type.typeArguments[i], out, path, tags); replaced = true; } } if (!replaced) { out.push({ name: type.reflection.typeParameters[i].name, - value: handleType(type.typeArguments[i], out, path), + value: handleType(type.typeArguments[i], out, path, tags), }); } } @@ -143,7 +146,7 @@ function getReflectionGeneric(type: ReferenceType, out: ReflectionGeneric[], pat * @param tags any tags attached to the type */ function handleRefWithoutReflection(ref: ReferenceType, generics: ReflectionGeneric[], - path: string, tags?: CommentTag[]): ElasticsearchValue { + path: string, tags: CommentTag[]): ElasticsearchValue { for (const premap in premaps) { if (premap === ref.name) { return readFieldTags(premaps[premap], path, tags); @@ -157,7 +160,7 @@ function handleRefWithoutReflection(ref: ReferenceType, generics: ReflectionGene return {type: ElasticsearchDataType.parse_error}; } - return readFieldTags(handleType(ref.typeArguments[0], getReflectionGeneric(ref, generics, path), path), + return readFieldTags(handleType(ref.typeArguments[0], getReflectionGeneric(ref, generics, path, tags), path, tags), path, tags); } if (ref.name === '__type') { // empty object @@ -179,12 +182,15 @@ function handleRefWithoutReflection(ref: ReferenceType, generics: ReflectionGene * @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 */ -function handleDeclarationReflection(decl: DeclarationReflection, generics: ReflectionGeneric[], path: string): +function handleDeclarationReflection(decl: DeclarationReflection, + generics: ReflectionGeneric[], + path: string): ElasticsearchValue { // check if we have an object referencing a generic for (const gRefl of generics) { if (gRefl.name === decl.name) { // if the object name is the same as the generic name - return readFieldTags(gRefl.value, path, typeof decl.comment !== 'undefined' ? decl.comment.tags : undefined); + return readFieldTags(gRefl.value, path, + typeof decl.comment !== 'undefined' ? typeof decl.comment.tags !== 'undefined' ? decl.comment.tags : [] : []); // use the value defined by the generic } } @@ -219,17 +225,18 @@ function handleDeclarationReflection(decl: DeclarationReflection, generics: Refl } } else if (decl.type instanceof Type) { // if the object is a type, so we are dealing with a PROPERTY return handleType(decl.type, generics, path, - typeof decl.comment !== 'undefined' ? decl.comment.tags : undefined); + typeof decl.comment !== 'undefined' ? typeof decl.comment.tags !== 'undefined' ? decl.comment.tags : [] : []); } else if (decl.kindString === 'Enumeration member') { return readTypeTags(typeof decl.defaultValue, path, - typeof decl.comment !== 'undefined' ? decl.comment.tags : undefined); + typeof decl.comment !== 'undefined' ? typeof decl.comment.tags !== 'undefined' ? decl.comment.tags : [] : []); } if (empty) { composeErrorMessage(path, 'object', decl.name, 'Empty object'); } - return readFieldTags(out, path, typeof decl.comment !== 'undefined' ? decl.comment.tags : undefined); + return readFieldTags(out, path, + typeof decl.comment !== 'undefined' ? typeof decl.comment.tags !== 'undefined' ? decl.comment.tags : [] : []); } /** @@ -241,15 +248,19 @@ function handleDeclarationReflection(decl: DeclarationReflection, generics: Refl * @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 tags any tags attached to the type */ -function handleUnionType(type: UnionType, generics: ReflectionGeneric[], path: string): ElasticsearchValue { +function handleUnionType(type: UnionType, + generics: ReflectionGeneric[], + path: string, + tags: CommentTag[]): ElasticsearchValue { const list: ElasticsearchValue[] = []; for (const subType of type.types) { if (subType instanceof IntrinsicType && subType.name === 'undefined') { continue; } - list.push(handleType(subType, generics, path)); + list.push(handleType(subType, generics, path, tags)); } if (list.length > 0) { @@ -275,7 +286,7 @@ function handleUnionType(type: UnionType, generics: ReflectionGeneric[], path: s * @param path the current path to the object we are in * @param tags any tags attached to the type */ -function handleType(type: Type, generics: ReflectionGeneric[], path: string, tags?: CommentTag[]): ElasticsearchValue { +function handleType(type: Type, generics: ReflectionGeneric[], path: string, tags: CommentTag[]): ElasticsearchValue { // logger.log((type as any).name); if (type instanceof ArrayType) { // array is irrelevant in Elasticsearch, so just go with the element type return handleType(type.elementType, generics, path, tags); @@ -287,13 +298,13 @@ function handleType(type: Type, generics: ReflectionGeneric[], path: string, tag return readTypeTags(type.name, path, tags); } if (type instanceof UnionType) { // the union type... - return handleUnionType(type, generics, path); + return handleUnionType(type, generics, path, tags); } if (type instanceof ReferenceType) { if (typeof 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, generics, path), path); + getReflectionGeneric(type, generics, path, tags), path); } return handleRefWithoutReflection(type, generics, path, tags); @@ -325,40 +336,46 @@ function handleType(type: Type, generics: ReflectionGeneric[], path: string, tag * @param prev the previous ElasticsearchValue, for example and object * @param path the current path to the object we are in * @param tags tags attached to the value + * @param dataType the ElasticsearchDataType, for checking if a tag is a type tag */ -function readFieldTags(prev: ElasticsearchValue, path: string, tags?: CommentTag[]): ElasticsearchValue { - if (typeof tags !== 'undefined') { - for (const tag of tags) { - if (!ignoredTagsList.includes(tag.tagName)) { - if (typeof fieldmap[tag.tagName] !== 'undefined') { - if (typeof prev.fields === 'undefined') { - prev.fields = {}; - } - if (tag.text.trim() === '') { - prev.fields = {...prev.fields, ...fieldmap[tag.tagName].default}; - } else if (typeof fieldmap[tag.tagName][tag.text.trim()] !== 'undefined') { - // merge the fields - prev.fields = {...prev.fields, ...fieldmap[tag.tagName][tag.text.trim()]}; - } else if (!fieldmap[tag.tagName].ignore.includes(tag.text.trim())) { - composeErrorMessage(path, 'tag', tag.tagName, `Not implemented tag param "${tag.text.trim()}"`); - } - } else if (tag.tagName === filterableTagName) { - if (typeof prev.fields === 'undefined') { - prev.fields = {}; - } - if ('type' in prev) { - const type = filterableMap[prev.type]; - if (typeof type !== 'undefined') { - prev.fields = {...prev.fields, ...{raw: {type: type}}}; - } else { - composeErrorMessage(path, 'tag', tag.tagName, `Not implemented for ${prev.type}`); - } +function readFieldTags(prev: ElasticsearchValue, + path: string, + tags: CommentTag[], + dataType?: string): ElasticsearchValue { + for (const tag of tags) { + if (!ignoredTagsList.includes(tag.tagName)) { + if (typeof fieldmap[tag.tagName] !== 'undefined') { + if (typeof prev.fields === 'undefined') { + // create in case it doesn't exist + prev.fields = {}; + } + if (tag.text.trim() === '') { + // merge fields + prev.fields = {...prev.fields, ...fieldmap[tag.tagName].default}; + } else if (typeof fieldmap[tag.tagName][tag.text.trim()] !== 'undefined') { + // merge fields + prev.fields = {...prev.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, 'tag', tag.tagName, `Not implemented tag param "${tag.text.trim()}"`); + } + } else if (tag.tagName === filterableTagName) { + if (typeof prev.fields === 'undefined') { + prev.fields = {}; + } + if ('type' in prev) { + const type = filterableMap[prev.type]; + if (typeof type !== 'undefined') { + // merge fields + prev.fields = {...prev.fields, ...{raw: {type: type}}}; } else { - composeErrorMessage(path, 'tag', tag.tagName, 'Not applicable for object types'); + composeErrorMessage(path, 'tag', tag.tagName, `Not implemented for ${prev.type}`); } } else { - composeErrorMessage(path, 'tag', tag.tagName, `Not implemented tag`); + composeErrorMessage(path, 'tag', tag.tagName, 'Not applicable for object types'); } + } else if (typeof dataType === 'undefined' || typeof typemap[dataType][tag.tagName] === 'undefined') { + composeErrorMessage(path, 'tag', tag.tagName, `Not implemented tag`); } } } @@ -373,23 +390,20 @@ function readFieldTags(prev: ElasticsearchValue, path: string, tags?: CommentTag * @param path the current path to the object we are in * @param tags tags attached to the value */ -function readTypeTags(type: string, path: string, tags?: CommentTag[]): ElasticsearchValue { +function readTypeTags(type: string, path: string, tags: CommentTag[]): ElasticsearchValue { let out: ElasticsearchValue = {type: ElasticsearchDataType.parse_error}; if (typeof typemap[type] !== 'undefined') { // first look if the value has a definition in the typemap - if (typeof tags !== 'undefined') { // look if there are any tags - for (let i = tags.length - 1; i >= 0; i--) { - if (!ignoredTagsList.includes(tags[i].tagName) && typeof typemap[type][tags[i].tagName] !== 'undefined') { - // if we have a tag that indicates a type - if (out.type !== ElasticsearchDataType.parse_error) { - composeErrorMessage(path, 'type', type, `Type conflict; "${typemap[type][tags[i].tagName]}" would` + - ` override "${out.type}"`); - out.type = ElasticsearchDataType.type_conflict; - continue; - } - out.type = typemap[type][tags[i].tagName]; - tags.splice(i, 1); // we need this so readFieldTags can process correctly + for (let i = tags.length - 1; i >= 0; i--) { + if (!ignoredTagsList.includes(tags[i].tagName) && typeof typemap[type][tags[i].tagName] !== 'undefined') { + // if we have a tag that indicates a type + if (out.type !== ElasticsearchDataType.parse_error) { + composeErrorMessage(path, 'type', type, `Type conflict; "${typemap[type][tags[i].tagName]}" would` + + ` override "${out.type}"`); + out.type = ElasticsearchDataType.type_conflict; + continue; } + out.type = typemap[type][tags[i].tagName]; } } @@ -397,7 +411,7 @@ function readTypeTags(type: string, path: string, tags?: CommentTag[]): Elastics out.type = typemap[type].default; } - out = readFieldTags(out, path, tags); + out = readFieldTags(out, path, tags, type); return out; } diff --git a/src/mappings/definitions/fieldmap.ts b/src/mappings/definitions/fieldmap.ts index 7937f1bf..8b7aa288 100644 --- a/src/mappings/definitions/fieldmap.ts +++ b/src/mappings/definitions/fieldmap.ts @@ -53,5 +53,6 @@ export const filterableTagName = 'filterable'; export const filterableMap: ElasticsearchFilterableMap = { date: ElasticsearchDataType.keyword, + keyword: ElasticsearchDataType.keyword, text: ElasticsearchDataType.keyword, }; diff --git a/src/mappings/definitions/premap.ts b/src/mappings/definitions/premap.ts index 9bbc6ac9..2e20eec2 100644 --- a/src/mappings/definitions/premap.ts +++ b/src/mappings/definitions/premap.ts @@ -44,7 +44,10 @@ export const premaps: ElasticsearchPremap = { dynamic: 'strict', properties: { bbox: {type: ElasticsearchDataType.float}, - coordinates: {type: ElasticsearchDataType.geo_point}, // TODO: filterable + coordinates: { + fields: {raw: {type: ElasticsearchDataType.keyword}}, + type: ElasticsearchDataType.geo_point, + }, crs: { dynamic: 'strict', properties: { @@ -62,7 +65,10 @@ export const premaps: ElasticsearchPremap = { dynamic: 'strict', properties: { bbox: {type: ElasticsearchDataType.float}, - coordinates: {type: ElasticsearchDataType.geo_point}, // TODO: filterable + coordinates: { + fields: {raw: {type: ElasticsearchDataType.keyword}}, + type: ElasticsearchDataType.geo_point, + }, crs: { dynamic: 'strict', properties: {