Add extra validation on custom downloaded themes

This commit is contained in:
Pieter Vander Vennet 2022-09-11 01:49:07 +02:00
parent 818cd62abc
commit 6f9199f1ad
3 changed files with 45 additions and 23 deletions

View file

@ -1,23 +1,24 @@
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import { QueryParameters } from "./Web/QueryParameters" import {QueryParameters} from "./Web/QueryParameters"
import { AllKnownLayouts } from "../Customizations/AllKnownLayouts" import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"
import { FixedUiElement } from "../UI/Base/FixedUiElement" import {FixedUiElement} from "../UI/Base/FixedUiElement"
import { Utils } from "../Utils" import {Utils} from "../Utils"
import Combine from "../UI/Base/Combine" import Combine from "../UI/Base/Combine"
import { SubtleButton } from "../UI/Base/SubtleButton" import {SubtleButton} from "../UI/Base/SubtleButton"
import BaseUIElement from "../UI/BaseUIElement" import BaseUIElement from "../UI/BaseUIElement"
import { UIEventSource } from "./UIEventSource" import {UIEventSource} from "./UIEventSource"
import { LocalStorageSource } from "./Web/LocalStorageSource" import {LocalStorageSource} from "./Web/LocalStorageSource"
import LZString from "lz-string" import LZString from "lz-string"
import { FixLegacyTheme } from "../Models/ThemeConfig/Conversion/LegacyJsonConvert" import {FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"
import SharedTagRenderings from "../Customizations/SharedTagRenderings" import SharedTagRenderings from "../Customizations/SharedTagRenderings"
import * as known_layers from "../assets/generated/known_layers.json" import * as known_layers from "../assets/generated/known_layers.json"
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme" import {PrepareTheme} from "../Models/ThemeConfig/Conversion/PrepareTheme"
import * as licenses from "../assets/generated/license_info.json" import * as licenses from "../assets/generated/license_info.json"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages" import {FixImages} from "../Models/ThemeConfig/Conversion/FixImages"
import Svg from "../Svg" import Svg from "../Svg"
import {DoesImageExist, PrevalidateTheme, ValidateThemeAndLayers} from "../Models/ThemeConfig/Conversion/Validation";
export default class DetermineLayout { export default class DetermineLayout {
private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path)) private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path))
@ -129,11 +130,11 @@ export default class DetermineLayout {
}), }),
json !== undefined json !== undefined
? new SubtleButton(Svg.download_svg(), "Download the JSON file").onClick(() => { ? new SubtleButton(Svg.download_svg(), "Download the JSON file").onClick(() => {
Utils.offerContentsAsDownloadableFile( Utils.offerContentsAsDownloadableFile(
JSON.stringify(json, null, " "), JSON.stringify(json, null, " "),
"theme_definition.json" "theme_definition.json"
) )
}) })
: undefined, : undefined,
]) ])
.SetClass("flex flex-col clickable") .SetClass("flex flex-col clickable")
@ -179,6 +180,23 @@ export default class DetermineLayout {
json.id = forceId ?? json.id json.id = forceId ?? json.id
{
let {errors} = new PrevalidateTheme().convert(json, "validation")
if (errors.length > 0) {
throw "Detected errors: " + errors.join("\n")
}
}
{
let {errors} = new ValidateThemeAndLayers(
new DoesImageExist(new Set<string>(), _ => true),
"",
false,
SharedTagRenderings.SharedTagRendering
).convert(json, "validation")
if (errors.length > 0) {
throw "Detected errors: " + errors.join("\n")
}
}
return new LayoutConfig(json, false, { return new LayoutConfig(json, false, {
definitionRaw: JSON.stringify(raw, null, " "), definitionRaw: JSON.stringify(raw, null, " "),
definedAtUrl: sourceUrl, definedAtUrl: sourceUrl,

View file

@ -147,7 +147,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
const warnings = [] const warnings = []
const information = [] const information = []
const theme = new LayoutConfig(json, true) const theme = new LayoutConfig(json, this._isBuiltin)
{ {
// Legacy format checks // Legacy format checks
@ -168,7 +168,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
} }
} }
} }
{ if(this._isBuiltin) {
// Check images: are they local, are the licenses there, is the theme icon square, ... // Check images: are they local, are the licenses there, is the theme icon square, ...
const images = new ExtractImages( const images = new ExtractImages(
this._isBuiltin, this._isBuiltin,
@ -224,6 +224,8 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
} }
try { try {
if(this._isBuiltin){
if (theme.id !== theme.id.toLowerCase()) { if (theme.id !== theme.id.toLowerCase()) {
errors.push("Theme ids should be in lowercase, but it is " + theme.id) errors.push("Theme ids should be in lowercase, but it is " + theme.id)
} }
@ -250,6 +252,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
warnings, warnings,
information information
) )
}
const dups = Utils.Dupiclates(json.layers.map((layer) => layer["id"])) const dups = Utils.Dupiclates(json.layers.map((layer) => layer["id"]))
if (dups.length > 0) { if (dups.length > 0) {
errors.push( errors.push(
@ -298,7 +301,7 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
super( super(
"Validates a theme and the contained layers", "Validates a theme and the contained layers",
new ValidateTheme(doesImageExist, path, isBuiltin, sharedTagRenderings), new ValidateTheme(doesImageExist, path, isBuiltin, sharedTagRenderings),
new On("layers", new Each(new ValidateLayer(undefined, false, doesImageExist))) new On("layers", new Each(new ValidateLayer(undefined, isBuiltin, doesImageExist)))
) )
} }
} }
@ -700,7 +703,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
} }
try { try {
{ if(this._isBuiltin) {
// Some checks for legacy elements // Some checks for legacy elements
if (json["overpassTags"] !== undefined) { if (json["overpassTags"] !== undefined) {
@ -747,7 +750,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
warnings.push(context + " has a tagRendering as `isShown`") warnings.push(context + " has a tagRendering as `isShown`")
} }
} }
{ if(this._isBuiltin) {
// Check location of layer file // Check location of layer file
const expected: string = `assets/layers/${json.id}/${json.id}.json` const expected: string = `assets/layers/${json.id}/${json.id}.json`
if (this._path != undefined && this._path.indexOf(expected) < 0) { if (this._path != undefined && this._path.indexOf(expected) < 0) {
@ -795,6 +798,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
} }
} }
} }
if (json.tagRenderings !== undefined) { if (json.tagRenderings !== undefined) {
const r = new On( const r = new On(
"tagRenderings", "tagRenderings",

View file

@ -122,7 +122,7 @@ export default class ScriptUtils {
return root.svg return root.svg
} }
public static async ReadSvgSync(path: string, callback: (svg: any) => void): Promise<any> { public static ReadSvgSync(path: string, callback: (svg: any) => void): any {
xml2js.parseString(readFileSync(path, "UTF8"), { async: false }, (err, root) => { xml2js.parseString(readFileSync(path, "UTF8"), { async: false }, (err, root) => {
if (err) { if (err) {
throw err throw err