property mapping validation #48

This commit is contained in:
mProjectsCode 2022-09-26 15:44:12 +02:00
parent 3e79f8ad77
commit 68ec5135cc
6 changed files with 146 additions and 23 deletions

View file

@ -507,15 +507,15 @@ export default class MediaDbPlugin extends Plugin {
for (const defaultProperty of defaultPropertyMappingModel.properties) { for (const defaultProperty of defaultPropertyMappingModel.properties) {
let newProperty = newPropertyMappingModel.properties.find(x => x.property === defaultProperty.property); let newProperty = newPropertyMappingModel.properties.find(x => x.property === defaultProperty.property);
if (newProperty === undefined) { if (newProperty === undefined) {
// default property is an instance
newProperties.push(defaultProperty); newProperties.push(defaultProperty);
} else { } else {
newProperties.push(newProperty); // newProperty is just an object and take locked status from default property
newProperties.push(new PropertyMapping(newProperty.property, newProperty.newProperty, newProperty.mapping, defaultProperty.locked));
} }
} }
newPropertyMappingModel.properties = newProperties; newPropertyMappings.push(new PropertyMappingModel(newPropertyMappingModel.type, newProperties));
newPropertyMappings.push(newPropertyMappingModel);
} }
} }
loadedSettings.propertyMappingModels = newPropertyMappings; loadedSettings.propertyMappingModels = newPropertyMappings;

View file

@ -16,15 +16,23 @@ export class PropertyMapper {
* @param obj * @param obj
*/ */
convertObject(obj: object): object { convertObject(obj: object): object {
console.log('test1');
if (!obj.hasOwnProperty('type')) { if (!obj.hasOwnProperty('type')) {
return obj; return obj;
} }
console.log('test2');
// @ts-ignore // @ts-ignore
if (MEDIA_TYPES.contains(obj.type)) { console.log(obj.type);
// @ts-ignore
if (MEDIA_TYPES.filter(x => x.toString() == obj.type).length < 1) {
return obj; return obj;
} }
console.log('test3');
// @ts-ignore // @ts-ignore
const propertyMappings = this.plugin.settings.propertyMappingModels.find(x => x.type === obj.type).properties; const propertyMappings = this.plugin.settings.propertyMappingModels.find(x => x.type === obj.type).properties;

View file

@ -1,4 +1,4 @@
import {containsOnlyLettersAndUnderscores} from '../utils/Utils'; import {containsOnlyLettersAndUnderscores, PropertyMappingNameConflictError, PropertyMappingValidationError} from '../utils/Utils';
import {MediaType} from '../utils/MediaType'; import {MediaType} from '../utils/MediaType';
export enum PropertyMappingOption { export enum PropertyMappingOption {
@ -9,9 +9,73 @@ export enum PropertyMappingOption {
export const propertyMappingOptions = [PropertyMappingOption.Default, PropertyMappingOption.Map, PropertyMappingOption.Remove]; export const propertyMappingOptions = [PropertyMappingOption.Default, PropertyMappingOption.Map, PropertyMappingOption.Remove];
export interface PropertyMappingModel { export class PropertyMappingModel {
type: MediaType, type: MediaType;
properties: PropertyMapping[], properties: PropertyMapping[];
constructor(type: MediaType, properties?: PropertyMapping[]) {
this.type = type;
this.properties = properties ?? [];
}
validate(): { res: boolean, err?: Error } {
// check properties
for (const property of this.properties) {
const propertyValidation = property.validate();
if (!propertyValidation.res) {
return {
res: false,
err: propertyValidation.err,
};
}
}
// check for name collisions
for (const property of this.getMappedProperties()) {
const propertiesWithSameTarget = this.getMappedProperties().filter(x => x.newProperty === property.newProperty);
if (propertiesWithSameTarget.length === 0) {
// if we get there, then something in this code is wrong
} else if (propertiesWithSameTarget.length === 1) {
// all good
} else {
// two or more properties are mapped to the same property
return {
res: false,
err: new PropertyMappingNameConflictError(`Multiple remapped properties (${propertiesWithSameTarget.map(x => x.toString()).toString()}) may not share the same name.`),
};
}
}
// remapped properties may not have the same name as any original property
for (const property of this.getMappedProperties()) {
const propertiesWithSameTarget = this.properties.filter(x => x.newProperty === property.property);
if (propertiesWithSameTarget.length === 0) {
// all good
} else {
// a mapped property shares the same name with an original property
return {
res: false,
err: new PropertyMappingNameConflictError(`Remapped property (${property}) may not share it's new name with an existing property.`),
};
}
}
return {
res: true,
};
}
getMappedProperties() {
return this.properties.filter(x => x.mapping === PropertyMappingOption.Map);
}
copy(): PropertyMappingModel {
const copy = new PropertyMappingModel(this.type);
for (const property of this.properties) {
const propertyCopy = new PropertyMapping(property.property, property.newProperty, property.mapping, property.locked);
copy.properties.push(propertyCopy);
}
return copy;
}
} }
export class PropertyMapping { export class PropertyMapping {
@ -49,16 +113,47 @@ export class PropertyMapping {
*/ */
} }
validate(): string { validate(): { res: boolean, err?: Error } {
// locked property may only be default
if (this.locked) {
if (this.mapping === PropertyMappingOption.Remove) {
return {
res: false,
err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": locked property may not be removed.`),
};
}
if (this.mapping === PropertyMappingOption.Map) {
return {
res: false,
err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": locked property may not be remapped.`),
};
}
}
if (this.mapping === PropertyMappingOption.Default) {
return {res: true};
}
if (this.mapping === PropertyMappingOption.Remove) {
return {res: true};
}
if (!this.property || !containsOnlyLettersAndUnderscores(this.property)) { if (!this.property || !containsOnlyLettersAndUnderscores(this.property)) {
return `Error in conversion rule "${this.toString()}": property may not be empty and only contain letters and underscores.`; return {
res: false,
err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": property may not be empty and only contain letters and underscores.`),
};
} }
if (!this.newProperty || !containsOnlyLettersAndUnderscores(this.newProperty)) { if (!this.newProperty || !containsOnlyLettersAndUnderscores(this.newProperty)) {
return `Error in conversion rule "${this.toString()}": new property may not be empty and only contain letters and underscores.`; return {
res: false,
err: new PropertyMappingValidationError(`Error in property mapping "${this.toString()}": new property may not be empty and only contain letters and underscores.`),
};
} }
return ''; return {
res: true,
};
} }
toString(): string { toString(): string {

View file

@ -49,10 +49,15 @@
margin: 0; margin: 0;
} }
.media-db-plugin-property-binding-to { .media-db-plugin-property-mapping-to {
display: flex; display: flex;
align-items: center; align-items: center;
} }
.media-db-plugin-property-mapping-validation {
color: var(--text-error);
margin-bottom: 5px;
}
</style> </style>
<div class="setting-item" style="display: flex; gap: 10px; flex-direction: column; align-items: stretch;"> <div class="setting-item" style="display: flex; gap: 10px; flex-direction: column; align-items: stretch;">
@ -81,18 +86,25 @@
{#if property.mapping === PropertyMappingOption.Map} {#if property.mapping === PropertyMappingOption.Map}
<Icon iconName="arrow-right"/> <Icon iconName="arrow-right"/>
<div class="media-db-plugin-property-binding-to"> <div class="media-db-plugin-property-mapping-to">
<input type="text" spellcheck="false" bind:value="{property.newProperty}"> <input type="text" spellcheck="false" bind:value="{property.newProperty}">
</div> </div>
{/if} {/if}
{/if} {/if}
</div> </div>
{ /each } { /each }
</div> </div>
<button class="mod-cta media-db-plugin-property-mappings-save-button" on:click={() => save(model)}>Save { #if !model.validate().res }
<div class="media-db-plugin-property-mapping-validation">
{model.validate().err?.message}
</div>
{/if}
<button
class="media-db-plugin-property-mappings-save-button {model.validate().res ? 'mod-cta' : 'mod-muted'}"
on:click={() => { if(model.validate().res) save(model) }}>Save
</button> </button>
</div> </div>
{ /each } { /each }
<pre>{JSON.stringify(models, null, 4)}</pre> <pre>{JSON.stringify(models, null, 4)}</pre>

View file

@ -142,10 +142,7 @@ export function getDefaultSettings(plugin: MediaDbPlugin): MediaDbPluginSettings
// console.log(metadataObj); // console.log(metadataObj);
// console.log(model); // console.log(model);
const propertyMappingModel: PropertyMappingModel = { const propertyMappingModel: PropertyMappingModel = new PropertyMappingModel(mediaType);
type: mediaType,
properties: [],
};
for (const key of Object.keys(metadataObj)) { for (const key of Object.keys(metadataObj)) {
propertyMappingModel.properties.push( propertyMappingModel.properties.push(
@ -486,7 +483,7 @@ export class MediaDbSettingTab extends PluginSettingTab {
new PropertyMappingModelsComponent({ new PropertyMappingModelsComponent({
target: this.containerEl, target: this.containerEl,
props: { props: {
models: JSON.parse(JSON.stringify(this.plugin.settings.propertyMappingModels)), models: this.plugin.settings.propertyMappingModels.map(x => x.copy()),
save: (model: PropertyMappingModel) => { save: (model: PropertyMappingModel) => {
let propertyMappingModels: PropertyMappingModel[] = []; let propertyMappingModels: PropertyMappingModel[] = [];

File diff suppressed because one or more lines are too long