Skip to content

Commit

Permalink
Improvements to ConfigDescription (#65)
Browse files Browse the repository at this point in the history
* Better tracing for errors back to the original config description.

* weheheh

* Add titles to config description schema.

This is now used to render some text.
  • Loading branch information
Gnuxie authored Oct 4, 2024
1 parent e6509c7 commit 9e3bf1c
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 48 deletions.
13 changes: 11 additions & 2 deletions src/Config/ConfigDescription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type ConfigPropertyDescription = {
default: unknown;
};

export type ConfigDescription<TConfigSchema extends TObject> = {
export type ConfigDescription<TConfigSchema extends TObject = TObject> = {
readonly schema: TConfigSchema;
parseConfig(
config: unknown
Expand All @@ -51,8 +51,17 @@ export class StandardConfigDescription<TConfigSchema extends TObject>
return ConfigParseError.Result('Unable to parse this config', {
errors: errors.map(
(error) =>
new ConfigPropertyError(error.message, error.path, error.value)
new ConfigPropertyError(
error.message,
this as unknown as ConfigDescription,
error.path,
error.value
)
),
config: withDefaults,
// We have a contravariance issue on the `toMirror` method because
// the mirror accepts specific config shapes.
description: this as never,
});
} else {
return Ok(TBValue.Decode(this.schema, withDefaults));
Expand Down
46 changes: 25 additions & 21 deletions src/Config/ConfigMirror.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ import { ConfigPropertyError } from './ConfigParseError';
import { Ok, Result } from '@gnuxie/typescript-result';
import { Value as TBValue } from '@sinclair/typebox/value';

export interface ConfigMirror<TSchema extends TObject> {
readonly description: ConfigDescription<TSchema>;
export interface ConfigMirror<TConfigSchema extends TObject = TObject> {
readonly description: ConfigDescription<TConfigSchema>;
setValue(
config: EDStatic<TSchema>,
key: keyof EDStatic<TSchema>,
config: EDStatic<TConfigSchema>,
key: keyof EDStatic<TConfigSchema>,
value: unknown
): Result<EDStatic<TSchema>, ConfigPropertyError>;
): Result<EDStatic<TConfigSchema>, ConfigPropertyError>;
addItem(
config: EDStatic<TSchema>,
key: keyof EDStatic<TSchema>,
config: EDStatic<TConfigSchema>,
key: keyof EDStatic<TConfigSchema>,
value: unknown
): Result<EDStatic<TSchema>, ConfigPropertyError>;
): Result<EDStatic<TConfigSchema>, ConfigPropertyError>;
// needed for when additionalProperties is true.
removeProperty<TKey extends string>(
key: TKey,
Expand All @@ -44,17 +44,19 @@ export interface ConfigMirror<TSchema extends TObject> {
): Record<TKey, unknown[]>;
}

export class StandardConfigMirror<TSchema extends TObject>
implements ConfigMirror<TSchema>
export class StandardConfigMirror<TConfigSchema extends TObject>
implements ConfigMirror<TConfigSchema>
{
public constructor(public readonly description: ConfigDescription<TSchema>) {
public constructor(
public readonly description: ConfigDescription<TConfigSchema>
) {
// nothing to do.
}
setValue(
config: Evaluate<StaticDecode<TSchema>>,
key: keyof Evaluate<StaticDecode<TSchema>>,
config: Evaluate<StaticDecode<TConfigSchema>>,
key: keyof Evaluate<StaticDecode<TConfigSchema>>,
value: unknown
): Result<Evaluate<StaticDecode<TSchema>>, ConfigPropertyError> {
): Result<Evaluate<StaticDecode<TConfigSchema>>, ConfigPropertyError> {
const schema = this.description.schema.properties[key as keyof TProperties];
if (schema === undefined) {
throw new TypeError(
Expand All @@ -66,19 +68,20 @@ export class StandardConfigMirror<TSchema extends TObject>
return ConfigPropertyError.Result(errors[0].message, {
path: `/${key.toString()}`,
value,
description: this.description as unknown as ConfigDescription,
});
}
const newConfig = {
...config,
[key]: TBValue.Decode(schema, value),
};
return Ok(newConfig as EDStatic<TSchema>);
return Ok(newConfig as EDStatic<TConfigSchema>);
}
private addUnparsedItem(
config: Evaluate<StaticDecode<TSchema>>,
key: keyof Evaluate<StaticDecode<TSchema>>,
config: Evaluate<StaticDecode<TConfigSchema>>,
key: keyof Evaluate<StaticDecode<TConfigSchema>>,
value: unknown
): Evaluate<StaticDecode<TSchema>> {
): Evaluate<StaticDecode<TConfigSchema>> {
const schema = this.description.schema.properties[key as keyof TProperties];
if (schema === undefined) {
throw new TypeError(
Expand All @@ -104,10 +107,10 @@ export class StandardConfigMirror<TSchema extends TObject>
}
}
addItem(
config: Evaluate<StaticDecode<TSchema>>,
key: keyof Evaluate<StaticDecode<TSchema>>,
config: Evaluate<StaticDecode<TConfigSchema>>,
key: keyof Evaluate<StaticDecode<TConfigSchema>>,
value: unknown
): Result<Evaluate<StaticDecode<TSchema>>, ConfigPropertyError> {
): Result<Evaluate<StaticDecode<TConfigSchema>>, ConfigPropertyError> {
const schema = this.description.schema.properties[key as keyof TProperties];
if (schema === undefined) {
throw new TypeError(
Expand All @@ -125,6 +128,7 @@ export class StandardConfigMirror<TSchema extends TObject>
return ConfigPropertyError.Result(errors[0].message, {
path: `/${key.toString()}${errors[0].path}`,
value,
description: this.description as unknown as ConfigDescription,
});
}
return Ok(this.addUnparsedItem(config, key, value));
Expand Down
54 changes: 45 additions & 9 deletions src/Config/ConfigParseError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@

import { Err, ResultError } from '@gnuxie/typescript-result';
import { ConfigRecoveryOption } from './PersistentConfigData';
import { ConfigDescription } from './ConfigDescription';

export class ConfigRecoverableError extends ResultError {
public readonly recoveryOptions: ConfigRecoveryOption[] = [];

public constructor(
message: string,
public readonly configDescription: ConfigDescription
) {
super(message);
}

addRecoveryOptions(options: ConfigRecoveryOption[]): this {
this.recoveryOptions.push(...options);
return this;
Expand All @@ -25,16 +33,29 @@ export enum ConfigErrorDiagnosis {
export class ConfigParseError extends ConfigRecoverableError {
constructor(
message: string,
public readonly errors: ConfigPropertyError[]
description: ConfigDescription,
public readonly errors: ConfigPropertyError[],
public readonly config: unknown
) {
super(message);
super(message, description);
}

public static Result(
message: string,
options: { errors: ConfigPropertyError[] }
options: {
errors: ConfigPropertyError[];
description: ConfigDescription;
config: unknown;
}
) {
return Err(new ConfigParseError(message, options.errors));
return Err(
new ConfigParseError(
message,
options.description,
options.errors,
options.config
)
);
}
}

Expand All @@ -45,10 +66,11 @@ export class ConfigPropertyError extends ConfigRecoverableError {
public readonly diagnosis: ConfigErrorDiagnosis;
constructor(
message: string,
description: ConfigDescription,
public readonly path: string,
public readonly value: unknown
) {
super(message);
super(message, description);
if (/\d+$/.test(path)) {
this.diagnosis = ConfigErrorDiagnosis.ProblematicArrayItem;
} else {
Expand All @@ -58,9 +80,16 @@ export class ConfigPropertyError extends ConfigRecoverableError {

public static Result(
message: string,
options: { path: string; value: unknown }
options: { path: string; value: unknown; description: ConfigDescription }
) {
return Err(new ConfigPropertyError(message, options.path, options.value));
return Err(
new ConfigPropertyError(
message,
options.description,
options.path,
options.value
)
);
}

public toReadableString(): string {
Expand All @@ -87,20 +116,27 @@ export class ConfigPropertyError extends ConfigRecoverableError {
export class ConfigPropertyUseError extends ConfigPropertyError {
constructor(
message: string,
description: ConfigDescription,
path: string,
value: unknown,
public readonly cause: ResultError
) {
super(message, path, value);
super(message, description, path, value);
}

public static Result(
message: string,
options: { path: string; value: unknown; cause: ResultError }
options: {
path: string;
value: unknown;
cause: ResultError;
description: ConfigDescription;
}
) {
return Err(
new ConfigPropertyUseError(
message,
options.description,
options.path,
options.value,
options.cause
Expand Down
8 changes: 6 additions & 2 deletions src/Config/PersistentConfigData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ConfigErrorDiagnosis,
ConfigParseError,
ConfigPropertyError,
ConfigPropertyUseError,
ConfigRecoverableError,
} from './ConfigParseError';
import { StaticEncode, TObject } from '@sinclair/typebox';
Expand All @@ -33,7 +34,7 @@ export type ConfigRecoveryOption = {
* Draupnir maintains a list of these which are editable generically
* via safe mode.
*/
export interface PersistentConfigData<T extends TObject> {
export interface PersistentConfigData<T extends TObject = TObject> {
readonly description: ConfigDescription<T>;
requestConfig(): Promise<
Result<EDStatic<T> | undefined, ResultError | ConfigParseError>
Expand Down Expand Up @@ -222,7 +223,10 @@ export class StandardPersistentConfigData<TConfigSchema extends TObject>
}
return this.addRecoveryOptionsToResult(
loadResult.ok,
ConfigPropertyError.Result(message, options)
ConfigPropertyUseError.Result(message, {
...options,
description: this.description as unknown as ConfigDescription,
})
);
}
}
12 changes: 9 additions & 3 deletions src/Protection/PolicyListConfig/MjolnirPolicyRoomsDescription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ import { PermalinkSchema } from '../../MatrixTypes/PermalinkSchema';
import { EDStatic } from '../../Interface/Static';

export const MjolnirPolicyRoomsDescription = describeConfig({
schema: Type.Object({
references: Type.Array(PermalinkSchema, { default: [], uniqueItems: true }),
}),
schema: Type.Object(
{
references: Type.Array(PermalinkSchema, {
default: [],
uniqueItems: true,
}),
},
{ title: 'PolicyRoomsConfig' }
),
});

export type MjolnirPolicyRoomsDescriptionEvent = EDStatic<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import { StringRoomIDSchema } from '../../MatrixTypes/StringlyTypedMatrix';
import { EDStatic } from '../../Interface/Static';

export const MjolnirProtectedRoomsDescription = describeConfig({
schema: Type.Object({
// does not have `uniqueItems: true` because there was as bug where
// the management room kept on being added to the list of protected rooms.
// deduplication is managed by the config implementation.
// https://github.com/Gnuxie/matrix-protection-suite/blob/de249c4cb81290aa1081f440af16f0cadc3522d0/src/Protection/ProtectedRoomsConfig/ProtectedRoomsConfig.ts#L108
rooms: Type.Array(StringRoomIDSchema, { default: [] }),
}),
schema: Type.Object(
{
// does not have `uniqueItems: true` because there was as bug where
// the management room kept on being added to the list of protected rooms.
// deduplication is managed by the config implementation.
// https://github.com/Gnuxie/matrix-protection-suite/blob/de249c4cb81290aa1081f440af16f0cadc3522d0/src/Protection/ProtectedRoomsConfig/ProtectedRoomsConfig.ts#L108
rooms: Type.Array(StringRoomIDSchema, { default: [] }),
},
{ title: 'ProtectedRoomsConfig' }
),
});

export type MjolnirProtectedRoomsConfigEvent = EDStatic<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import { EDStatic } from '../../Interface/Static';
import { DRAUPNIR_SCHEMA_VERSION_KEY } from '../../Interface/SchemedMatrixData';

export const MjolnirEnabledProtectionsDescription = describeConfig({
schema: Type.Object({
enabled: Type.Array(Type.String(), { default: [], uniqueItems: true }),
[DRAUPNIR_SCHEMA_VERSION_KEY]: Type.Optional(Type.Number()),
}),
schema: Type.Object(
{
enabled: Type.Array(Type.String(), { default: [], uniqueItems: true }),
[DRAUPNIR_SCHEMA_VERSION_KEY]: Type.Optional(Type.Number()),
},
{ title: 'EnabledProtectionsConfig' }
),
});

export type MjolnirEnabledProtectionsDescriptionEvent = EDStatic<
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,18 @@ export * from './Protection/Capability/CapabilitySet';
export * from './Protection/PolicyListConfig/FakePolicyListConfig';
export * from './Protection/PolicyListConfig/MjolnirWatchedListsEvent';
export * from './Protection/PolicyListConfig/MjolnirPolicyRoomsConfig';
export * from './Protection/PolicyListConfig/MjolnirPolicyRoomsDescription';
export * from './Protection/PolicyListConfig/PolicyListConfig';

export * from './Protection/ProtectedRoomsConfig/FakeProtectedRoomsConfig';
export * from './Protection/ProtectedRoomsConfig/MjolnirProtectedRoomsDescription';
export * from './Protection/ProtectedRoomsConfig/MjolnirProtectedRoomsEvent';
export * from './Protection/ProtectedRoomsConfig/ProtectedRoomsConfig';

export * from './Protection/ProtectedRoomsManager/ProtectedRoomsManager';
export * from './Protection/ProtectedRoomsManager/StandardProtectedRoomsManager';

export * from './Protection/ProtectionsConfig/MjolnirEnabledProtectionsDescription';
export * from './Protection/ProtectionsConfig/MjolnirEnabledProtectionsEvent';
export * from './Protection/ProtectionsConfig/ProtectionsConfig';
export * from './Protection/ProtectionsConfig/StandardProtectionsConfig';
Expand Down

0 comments on commit 9e3bf1c

Please sign in to comment.