Skip to content

Commit

Permalink
chore: improve error handling definitionVersionDiscovery
Browse files Browse the repository at this point in the history
  • Loading branch information
sanderPostma committed Oct 25, 2024
1 parent be27cde commit db4b2d5
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 24 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ module.exports = {
transform: {'^.+\\.ts?$': 'ts-jest'},
testEnvironment: 'node',
testRegex: '/test/.*\\.(test|spec)?\\.ts$',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node', 'd.ts']
};
17 changes: 15 additions & 2 deletions lib/PEX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
VerifiablePresentationResult,
} from './signing';
import { DiscoveredVersion, IInternalPresentationDefinition, IPresentationDefinition, OrArray, PEVersion, SSITypesBuilder } from './types';
import { calculateSdHash, definitionVersionDiscovery, getSubjectIdsAsString } from './utils';
import { calculateSdHash, definitionVersionDiscovery, formatValidationErrors, getSubjectIdsAsString } from './utils';
import { PresentationDefinitionV1VB, PresentationDefinitionV2VB, PresentationSubmissionVB, Validated, ValidationEngine } from './validation';

export interface PEXOptions {
Expand Down Expand Up @@ -444,8 +444,21 @@ export class PEX {
public static validateDefinition(presentationDefinition: IPresentationDefinition): Validated {
const result = definitionVersionDiscovery(presentationDefinition);
if (result.error) {
throw new Error(result.error);
const errorParts = [result.error];

const v1ErrorString = formatValidationErrors(result.v1Errors);
if (v1ErrorString) {
errorParts.push('\nVersion 1 validation errors:\n ' + v1ErrorString);
}

const v2ErrorString = formatValidationErrors(result.v2Errors);
if (v2ErrorString) {
errorParts.push('\nVersion 2 validation errors:\n ' + v2ErrorString);
}

throw new Error(errorParts.join(''));
}

const validators = [];
result.version === PEVersion.v1
? validators.push({
Expand Down
6 changes: 4 additions & 2 deletions lib/types/Internal.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from '@sphereon/pex-models';
import { IVerifiableCredential, IVerifiablePresentation } from '@sphereon/ssi-types';

import { ValidationError } from '../validation/validators';

export interface InputDescriptorWithIndex {
inputDescriptorIndex: number;
inputDescriptor: InputDescriptorV1 | InputDescriptorV2;
Expand Down Expand Up @@ -92,8 +94,8 @@ export class InternalPresentationDefinitionV2 implements PresentationDefinitionV
export interface DiscoveredVersion {
version?: PEVersion;
error?: string;
v1Errors?: Record<string, unknown>;
v2Errors?: Record<string, unknown>;
v1Errors?: Array<ValidationError>;
v2Errors?: Array<ValidationError>;
}

export type IPresentationDefinition = PresentationDefinitionV1 | PresentationDefinitionV2;
Expand Down
38 changes: 20 additions & 18 deletions lib/utils/VCUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { AdditionalClaims, CredentialMapper, ICredential, ICredentialSubject, IIssuer, SdJwtDecodedVerifiableCredential } from '@sphereon/ssi-types';

import { DiscoveredVersion, IPresentationDefinition, PEVersion } from '../types';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import validatePDv1 from '../validation/validatePDv1.js';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import validatePDv2 from '../validation/validatePDv2.js';
import { ValidationError } from '../validation/validators';

import { ObjectUtils } from './ObjectUtils';
import { JsonPathUtils } from './jsonPathUtils';
Expand Down Expand Up @@ -34,27 +31,32 @@ export function definitionVersionDiscovery(presentationDefinition: IPresentation
JsonPathUtils.changePropertyNameRecursively(presentationDefinitionCopy, '_const', 'const');
JsonPathUtils.changePropertyNameRecursively(presentationDefinitionCopy, '_enum', 'enum');
const data = { presentation_definition: presentationDefinitionCopy };
let result = validatePDv2(data);

if (result) {
if (validatePDv2(data)) {
return { version: PEVersion.v2 };
}
// Errors are added to the validation method, but not typed correctly
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const v2Errors = validatePDv2.errors;
const v2Errors = validatePDv2.errors ?? undefined;

result = validatePDv1(data);
if (result) {
if (validatePDv1(data)) {
return { version: PEVersion.v1 };
}
const v1Errors = validatePDv1.errors ?? undefined;

// Errors are added to the validation method, but not typed correctly
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const v1Errors = validatePDv1.errors;
return {
error: 'This is not a valid PresentationDefinition',
v1Errors,
v2Errors,
};
}

return { error: 'This is not a valid PresentationDefinition', v1Errors, v2Errors };
export function formatValidationError(error: ValidationError): string {
return `${error.instancePath || '/'}: ${error.message}${error.params.additionalProperty ? ` (${error.params.additionalProperty})` : ''}`;
}

export function formatValidationErrors(errors: ValidationError[] | undefined): string | undefined {
if (!errors?.length) {
return undefined;

Check warning on line 57 in lib/utils/VCUtils.ts

View check run for this annotation

Codecov / codecov/patch

lib/utils/VCUtils.ts#L57

Added line #L57 was not covered by tests
}
return errors.map(formatValidationError).join('\n ');
}

export function uniformDIDMethods(dids?: string[], opts?: { removePrefix: 'did:' }) {
Expand Down
6 changes: 6 additions & 0 deletions lib/validation/validatePDv1.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { ValidateFunction, ValidationError } from './validators';

declare module './validatePDv1.js' {
const validate: ValidateFunction & { errors?: ValidationError[] | null };
export default validate;
}
6 changes: 6 additions & 0 deletions lib/validation/validatePDv2.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { ValidateFunction, ValidationError } from './validators';

declare module './validatePDv2.js' {
const validate: ValidateFunction & { errors?: ValidationError[] | null };
export default validate;
}
71 changes: 71 additions & 0 deletions lib/validation/validators.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
export interface ValidateFunction {
(data: unknown): boolean;
errors?: ValidationError[];
}

export type ValidationParentSchema = {
type?: string | string[];
properties?: {
[key: string]: {
type?: string;
enum?: string[];
items?: {
type?: string;
$ref?: string;
};
properties?: Record<string, unknown>;
required?: string[];
additionalProperties?: boolean;
$ref?: string;
};
};
required?: string[];
additionalProperties?: boolean;
items?: {
type?: string;
$ref?: string;
};
};

export type ValidationData = {
id?: string;
name?: string;
purpose?: string;
constraints?: {
limit_disclosure?: string;
fields?: Array<{
path?: string[];
purpose?: string;
filter?: {
type?: string;
pattern?: string;
};
}>;
};
schema?: Array<{
uri?: string;
}>;
[key: string]: unknown;
};

export type ValidationError = {
instancePath: string;
schemaPath: string;
keyword: string;
params: {
type?: string | string[];
limit?: number;
comparison?: string;
missingProperty?: string;
additionalProperty?: string;
propertyName?: string;
i?: number;
j?: number;
allowedValues?: string[] | readonly string[];
passingSchemas?: number | number[];
};
message: string;
schema: boolean | ValidationParentSchema;
parentSchema: ValidationParentSchema;
data: ValidationData;
};
17 changes: 17 additions & 0 deletions test/PEXv2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,23 @@ describe('evaluate', () => {
expect(result!.areRequiredCredentialsPresent).toBe('info');
});

it('Evaluate selectFrom should fail', () => {
const pd: PresentationDefinitionV2 = getPresentationDefinitionV2_1();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(pd as any).input_descriptors = undefined;
const result1 = PEX.definitionVersionDiscovery(pd);
expect(result1.v1Errors?.length).toBe(2);
expect(result1.v2Errors?.length).toBe(1);
expect(() => PEX.validateDefinition(pd)).toThrow(
'This is not a valid PresentationDefinition\n' +
'Version 1 validation errors:\n' +
" /presentation_definition: must have required property 'input_descriptors'\n" +
' /presentation_definition: must NOT have additional properties (frame)\n' +
'Version 2 validation errors:\n' +
" /presentation_definition: must have required property 'input_descriptors'",
);
});

it("should throw error if proofOptions doesn't have a type with v2 pd", async () => {
const pdSchema = getFile('./test/dif_pe_examples/pdV2/vc_expiration(corrected).json');
const vpSimple = getFile('./test/dif_pe_examples/vp/vp_general.json') as IVerifiablePresentation;
Expand Down
6 changes: 5 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
"lib": [
"es7", "dom"
],*/
"types": ["jest", "node"]
"types": ["jest", "node"],
"typeRoots": [
"./node_modules/@types",
"./src/validation"
]
},
"include": [
"declarations.d.ts",
Expand Down

0 comments on commit db4b2d5

Please sign in to comment.