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

feat(core)!: metadata on records #505

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { ConnectionService } from '../../connections/services/ConnectionSer
import type { StoreCredentialOptions } from '../../indy/services/IndyHolderService'
import type { CredentialStateChangedEvent } from '../CredentialEvents'
import type { CredentialPreviewAttribute } from '../messages'
import type { CredentialRecordMetadata, CustomCredentialTags } from '../repository/CredentialRecord'
import type { IndyCredentialMetadata } from '../models/CredentialInfo'
import type { CustomCredentialTags } from '../repository/CredentialRecord'
import type { CredentialOfferTemplate } from '../services'

import { getAgentConfig, getMockConnection, mockFunction } from '../../../../tests/helpers'
Expand Down Expand Up @@ -98,7 +99,7 @@ const mockCredentialRecord = ({
}: {
state?: CredentialState
requestMessage?: RequestCredentialMessage
metadata?: CredentialRecordMetadata
metadata?: IndyCredentialMetadata & { indyRequestMetadata: { cred_req: string } }
tags?: CustomCredentialTags
threadId?: string
connectionId?: string
Expand All @@ -116,20 +117,27 @@ const mockCredentialRecord = ({
id,
credentialAttributes: credentialAttributes || credentialPreview.attributes,
requestMessage,
metadata,
state: state || CredentialState.OfferSent,
threadId: threadId ?? offerMessage.id,
connectionId: connectionId ?? '123',
tags,
})

if (metadata?.requestMetadata) {
credentialRecord.metadata.set('indyRequestMetadata', { requestMetadata: metadata?.requestMetadata })
if (metadata?.indyRequestMetadata) {
credentialRecord.metadata.set('indyRequestMetadata', { ...metadata.indyRequestMetadata })
}

if (metadata?.schemaId) {
credentialRecord.metadata.set('indyCredentialMetadata', {
schemaId: metadata.schemaId,
})
}

if (metadata?.credentialDefinitionId) {
credentialRecord.metadata.set('indyCredentialMetadata', {
credentialDefinitionId: metadata.credentialDefinitionId,
})
}
credentialRecord.metadata.set('indyCredentialMetadata', {
credentialDefinitionId: metadata?.credentialDefinitionId,
schemaId: metadata?.schemaId,
})

return credentialRecord
}
Expand Down Expand Up @@ -613,7 +621,7 @@ describe('CredentialService', () => {
requestMessage: new RequestCredentialMessage({
requestAttachments: [requestAttachment],
}),
metadata: { requestMetadata: { cred_req: 'meta-data' } },
metadata: { indyRequestMetadata: { cred_req: 'meta-data' } },
})

const credentialResponse = new IssueCredentialMessage({
Expand Down Expand Up @@ -646,7 +654,7 @@ describe('CredentialService', () => {

expect(storeCredentialMock).toHaveBeenNthCalledWith(1, {
credentialId: expect.any(String),
credentialRequestMetadata: { requestMetadata: { cred_req: 'meta-data' } },
credentialRequestMetadata: { cred_req: 'meta-data' },
credential: messageContext.message.indyCredential,
credentialDefinition: credDef,
})
Expand Down Expand Up @@ -735,7 +743,7 @@ describe('CredentialService', () => {
Promise.resolve(
mockCredentialRecord({
state,
metadata: { requestMetadata: { cred_req: 'meta-data' } },
metadata: { indyRequestMetadata: { cred_req: 'meta-data' } },
})
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ import {
} from '../messages'
import { CredentialInfo } from '../models/CredentialInfo'

export interface CredentialRecordMetadata {
requestMetadata?: Record<string, unknown>
credentialDefinitionId?: string
schemaId?: string
}

export interface CredentialRecordProps {
id?: string
createdAt?: Date
Expand All @@ -31,7 +25,6 @@ export interface CredentialRecordProps {
threadId: string

credentialId?: string
metadata?: CredentialRecordMetadata
tags?: CustomCredentialTags
proposalMessage?: ProposeCredentialMessage
offerMessage?: OfferCredentialMessage
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/storage/BaseRecord.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Exclude, Type } from 'class-transformer'

import { JsonTransformer } from '../utils/JsonTransformer'
import { MetadataTransformer } from '../utils/transformers'

import { Metadata, MetadataTransformer } from './Metadata'
import { Metadata } from './Metadata'

export type TagValue = string | boolean | undefined | Array<string>
export type TagsBase = {
Expand Down
16 changes: 0 additions & 16 deletions packages/core/src/storage/Metadata.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
import { Transform, TransformationType } from 'class-transformer'

export type MetadataBase = {
[key: string]: Record<string, unknown>
}

export function MetadataTransformer() {
return Transform(({ value, type }) => {
switch (type) {
case TransformationType.CLASS_TO_PLAIN:
return { ...value.data }

case TransformationType.PLAIN_TO_CLASS:
return new Metadata(value)
default:
return value
}
})
}

/**
* Metadata access class to get, set (create and update) and delete
* metadata on any record.
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/utils/JsonTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,21 @@ export class JsonTransformer {
public static deserialize<T>(jsonString: string, Class: { new (...args: any[]): T }): T {
return deserialize(Class, jsonString, { exposeDefaultValues: true })
}

/**
* replaces a key in an object
* These generics are inferred, do not pass them in.
*/
public static renameKey<OldKey extends keyof T, NewKey extends string, T extends Record<string, unknown>>(
oldKey: OldKey,
newKey: NewKey,
userObject: T
): Record<NewKey, T[OldKey]> & Omit<T, OldKey> {
const { [oldKey]: value, ...common } = userObject

return {
...common,
...({ [newKey]: value } as Record<NewKey, T[OldKey]>),
}
}
}
17 changes: 17 additions & 0 deletions packages/core/src/utils/__tests__/JsonTransformer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,21 @@ describe('JsonTransformer', () => {
expect(connection.didDoc).toBeInstanceOf(DidDoc)
})
})

describe('rename a key on a simple JSON object', () => {
it('renames a key when it is there', () => {
const sampleObject = { foo: 'bar' }

const renamedObject = JsonTransformer.renameKey('foo', 'baz', sampleObject)

expect(renamedObject).toEqual({ baz: 'bar' })
})
it('does not rename a key when it is not there', () => {
const sampleObject = { foo: 'bar' }

const renamedObject = JsonTransformer.renameKey('baz' as 'foo', 'baz', sampleObject)

expect(renamedObject).toEqual({ foo: 'bar' })
})
})
})
25 changes: 25 additions & 0 deletions packages/core/src/utils/transformers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Transform, TransformationType } from 'class-transformer'
import { DateTime } from 'luxon'

import { Metadata } from '../storage/Metadata'

import { JsonTransformer } from './JsonTransformer'

/**
Expand Down Expand Up @@ -39,6 +41,29 @@ export function RecordTransformer<T>(Class: { new (...args: any[]): T }) {
}
})
}

/*
* Decorator that transforms to and from a metadata instance.
*
*/
export function MetadataTransformer() {
return Transform(({ value, type }) => {
switch (type) {
case TransformationType.CLASS_TO_PLAIN:
return { ...value.data }

case TransformationType.PLAIN_TO_CLASS:
// TODO: Remove conversion in 0.1.0 via a migration script
JsonTransformer.renameKey('requestMetadata', 'indyRequestMetadata', value)
JsonTransformer.renameKey('credentialMetadata', 'indyCredentialMetadata', value)

return new Metadata(value)
berendsliedrecht marked this conversation as resolved.
Show resolved Hide resolved
default:
return value
}
})
}

/*
* Function that parses date from multiple formats
* including SQL formats.
Expand Down