Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/VDX-339_error-types #176

Merged
merged 2 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 ');
sanderPostma marked this conversation as resolved.
Show resolved Hide resolved
}

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;
}
53 changes: 53 additions & 0 deletions lib/validation/validators.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { PresentationDefinitionV1, PresentationDefinitionV2 } from '@sphereon/pex-models';

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 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: PresentationDefinitionV1 | PresentationDefinitionV2;
};
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
Loading