Skip to content

Commit

Permalink
feat: automatic transformation of record classes (#253)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <timo@animo.id>
  • Loading branch information
TimoGlastra committed May 2, 2021
1 parent cf29d8f commit e07b90e
Show file tree
Hide file tree
Showing 23 changed files with 266 additions and 295 deletions.
45 changes: 19 additions & 26 deletions src/__tests__/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
CredentialPreviewAttribute,
} from '../modules/credentials'
import { InitConfig } from '../types'
import { JsonTransformer } from '../utils/JsonTransformer'

import testLogger from './logger'

Expand Down Expand Up @@ -136,13 +137,8 @@ describe('credentials', () => {
state: CredentialState.OfferReceived,
})

// FIXME: expect below expects json, so we do a refetch because the current
// returns class instance
aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id)

expect(aliceCredentialRecord).toMatchObject({
createdAt: expect.any(Number),
id: expect.any(String),
expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({
createdAt: expect.any(Date),
offerMessage: {
'@id': expect.any(String),
'@type': 'https://didcomm.org/issue-credential/1.0/offer-credential',
Expand All @@ -164,11 +160,14 @@ describe('credentials', () => {
},
'offers~attach': expect.any(Array),
},
tags: { threadId: faberCredentialRecord.tags.threadId },
type: CredentialRecord.name,
state: CredentialState.OfferReceived,
})

// below values are not in json object
expect(aliceCredentialRecord.id).not.toBeNull()
expect(aliceCredentialRecord.tags).toEqual({ threadId: faberCredentialRecord.tags.threadId })
expect(aliceCredentialRecord.type).toBe(CredentialRecord.name)

testLogger.test('Alice sends credential request to Faber')
aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id)

Expand Down Expand Up @@ -199,7 +198,7 @@ describe('credentials', () => {
expect(aliceCredentialRecord).toMatchObject({
type: CredentialRecord.name,
id: expect.any(String),
createdAt: expect.any(Number),
createdAt: expect.any(Date),
tags: {
threadId: expect.any(String),
},
Expand All @@ -213,14 +212,12 @@ describe('credentials', () => {
expect(faberCredentialRecord).toMatchObject({
type: CredentialRecord.name,
id: expect.any(String),
createdAt: expect.any(Number),
createdAt: expect.any(Date),
tags: {
threadId: expect.any(String),
},
offerMessage: expect.any(Object),
requestMessage: expect.any(Object),
requestMetadata: undefined,
credentialId: undefined,
state: CredentialState.Done,
})
})
Expand All @@ -239,13 +236,8 @@ describe('credentials', () => {
state: CredentialState.OfferReceived,
})

// FIXME: expect below expects json, so we do a refetch because the current
// returns class instance
aliceCredentialRecord = await aliceAgent.credentials.getById(aliceCredentialRecord.id)

expect(aliceCredentialRecord).toMatchObject({
createdAt: expect.any(Number),
id: expect.any(String),
expect(JsonTransformer.toJSON(aliceCredentialRecord)).toMatchObject({
createdAt: expect.any(Date),
offerMessage: {
'@id': expect.any(String),
'@type': 'https://didcomm.org/issue-credential/1.0/offer-credential',
Expand All @@ -267,11 +259,14 @@ describe('credentials', () => {
},
'offers~attach': expect.any(Array),
},
tags: { threadId: faberCredentialRecord.tags.threadId },
type: CredentialRecord.name,
state: CredentialState.OfferReceived,
})

// below values are not in json object
expect(aliceCredentialRecord.id).not.toBeNull()
expect(aliceCredentialRecord.tags).toEqual({ threadId: faberCredentialRecord.tags.threadId })
expect(aliceCredentialRecord.type).toBe(CredentialRecord.name)

testLogger.test('Alice sends credential request to Faber')
aliceCredentialRecord = await aliceAgent.credentials.acceptOffer(aliceCredentialRecord.id)

Expand Down Expand Up @@ -302,7 +297,7 @@ describe('credentials', () => {
expect(aliceCredentialRecord).toMatchObject({
type: CredentialRecord.name,
id: expect.any(String),
createdAt: expect.any(Number),
createdAt: expect.any(Date),
tags: {
threadId: expect.any(String),
},
Expand All @@ -316,14 +311,12 @@ describe('credentials', () => {
expect(faberCredentialRecord).toMatchObject({
type: CredentialRecord.name,
id: expect.any(String),
createdAt: expect.any(Number),
createdAt: expect.any(Date),
tags: {
threadId: expect.any(String),
},
offerMessage: expect.any(Object),
requestMessage: expect.any(Object),
requestMetadata: undefined,
credentialId: undefined,
state: CredentialState.Done,
})
})
Expand Down
22 changes: 17 additions & 5 deletions src/agent/Agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,23 @@ export class Agent {
this.dispatcher = new Dispatcher(this.messageSender)

const storageService = new IndyStorageService(this.wallet)
this.basicMessageRepository = new Repository<BasicMessageRecord>(BasicMessageRecord, storageService)
this.connectionRepository = new Repository<ConnectionRecord>(ConnectionRecord, storageService)
this.provisioningRepository = new Repository<ProvisioningRecord>(ProvisioningRecord, storageService)
this.credentialRepository = new Repository<CredentialRecord>(CredentialRecord, storageService)
this.proofRepository = new Repository<ProofRecord>(ProofRecord, storageService)
this.basicMessageRepository = new Repository<BasicMessageRecord>(
BasicMessageRecord,
storageService as IndyStorageService<BasicMessageRecord>
)
this.connectionRepository = new Repository<ConnectionRecord>(
ConnectionRecord,
storageService as IndyStorageService<ConnectionRecord>
)
this.provisioningRepository = new Repository<ProvisioningRecord>(
ProvisioningRecord,
storageService as IndyStorageService<ProvisioningRecord>
)
this.credentialRepository = new Repository<CredentialRecord>(
CredentialRecord,
storageService as IndyStorageService<CredentialRecord>
)
this.proofRepository = new Repository<ProofRecord>(ProofRecord, storageService as IndyStorageService<ProofRecord>)
this.provisioningService = new ProvisioningService(this.provisioningRepository, this.agentConfig)
this.connectionService = new ConnectionService(this.wallet, this.agentConfig, this.connectionRepository)
this.basicMessageService = new BasicMessageService(this.basicMessageRepository)
Expand Down
2 changes: 1 addition & 1 deletion src/agent/BaseMessage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Expose } from 'class-transformer'
import { Matches } from 'class-validator'
import { v4 as uuid } from 'uuid'

import { uuid } from '../utils/uuid'
import { Constructor } from '../utils/mixins'

export const MessageIdRegExp = /[-_./a-zA-Z0-9]{8,64}/
Expand Down
2 changes: 1 addition & 1 deletion src/decorators/attachment/Attachment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Expose, Type } from 'class-transformer'
import { IsBase64, IsDate, IsHash, IsInt, IsMimeType, IsOptional, IsString, ValidateNested } from 'class-validator'
import { v4 as uuid } from 'uuid'
import { uuid } from '../../utils/uuid'

export interface AttachmentOptions {
id?: string
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// reflect-metadata used for class-transfomer + class-validator
// reflect-metadata used for class-transformer + class-validator
import 'reflect-metadata'

export { Agent } from './agent/Agent'
Expand Down
25 changes: 15 additions & 10 deletions src/modules/basic-messages/repository/BasicMessageRecord.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
import { v4 as uuid } from 'uuid'
import { BaseRecord, RecordType, Tags } from '../../../storage/BaseRecord'
import { uuid } from '../../../utils/uuid'
import { BaseRecord, Tags } from '../../../storage/BaseRecord'

export interface BasicMessageStorageProps {
id?: string
createdAt?: number
createdAt?: Date
tags: Tags

content: string
sentTime: string
}

export class BasicMessageRecord extends BaseRecord implements BasicMessageStorageProps {
public content: string
public sentTime: string
public content!: string
public sentTime!: string

public static readonly type: RecordType = RecordType.BasicMessageRecord
public static readonly type = 'BasicMessageRecord'
public readonly type = BasicMessageRecord.type

public constructor(props: BasicMessageStorageProps) {
super(props.id ?? uuid(), props.createdAt ?? Date.now())
this.content = props.content
this.sentTime = props.sentTime
this.tags = props.tags
super()

if (props) {
this.id = props.id ?? uuid()
this.createdAt = props.createdAt ?? new Date()
this.content = props.content
this.sentTime = props.sentTime
this.tags = props.tags
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import indy from 'indy-sdk'
import { v4 as uuid } from 'uuid'
import { uuid } from '../../../utils/uuid'
import { IndyWallet } from '../../../wallet/IndyWallet'
import { Wallet } from '../../../wallet/Wallet'
import { ConnectionService } from '../services/ConnectionService'
Expand Down
4 changes: 2 additions & 2 deletions src/modules/connections/models/ConnectionRole.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export enum ConnectionRole {
Inviter = 'INVITER',
Invitee = 'INVITEE',
Inviter = 'inviter',
Invitee = 'invitee',
}
113 changes: 36 additions & 77 deletions src/modules/connections/repository/ConnectionRecord.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { v4 as uuid } from 'uuid'
import { classToPlain, plainToClass } from 'class-transformer'
import { BaseRecord, RecordType, Tags } from '../../../storage/BaseRecord'
import { Type } from 'class-transformer'
import { uuid } from '../../../utils/uuid'
import { BaseRecord, Tags } from '../../../storage/BaseRecord'
import { ConnectionState } from '..'
import { ConnectionInvitationMessage } from '..'
import { ConnectionRole } from '..'
import { JsonTransformer } from '../../../utils/JsonTransformer'
import { DidDoc } from '..'
import { IndyAgentService } from '..'
import type { Did, Verkey } from 'indy-sdk'

interface ConnectionProps {
id?: string
createdAt?: number
createdAt?: Date
did: Did
didDoc: DidDoc
verkey: Verkey
Expand All @@ -20,7 +19,6 @@ interface ConnectionProps {
invitation?: ConnectionInvitationMessage
state: ConnectionState
role: ConnectionRole
endpoint?: string
alias?: string
autoAcceptConnection?: boolean
}
Expand All @@ -37,84 +35,45 @@ export interface ConnectionStorageProps extends ConnectionProps {
}

export class ConnectionRecord extends BaseRecord implements ConnectionStorageProps {
public did: Did
private _didDoc!: Record<string, unknown>
public verkey: Verkey
public theirDid?: Did
private _theirDidDoc?: Record<string, unknown>
private _invitation?: Record<string, unknown>
public state: ConnectionState
public role: ConnectionRole
public endpoint?: string
public state!: ConnectionState
public role!: ConnectionRole

@Type(() => DidDoc)
public didDoc!: DidDoc
public did!: string
public verkey!: string

@Type(() => DidDoc)
public theirDidDoc?: DidDoc
public theirDid?: string

@Type(() => ConnectionInvitationMessage)
public invitation?: ConnectionInvitationMessage
public alias?: string
public autoAcceptConnection?: boolean
public tags: ConnectionTags
public tags!: ConnectionTags

public static readonly type: RecordType = RecordType.ConnectionRecord
public static readonly type: 'ConnectionRecord'
public readonly type = ConnectionRecord.type

public constructor(props: ConnectionStorageProps) {
super(props.id ?? uuid(), props.createdAt ?? Date.now())
this.did = props.did
this.didDoc = props.didDoc
this.verkey = props.verkey
this.theirDid = props.theirDid
this.theirDidDoc = props.theirDidDoc
this.state = props.state
this.role = props.role
this.endpoint = props.endpoint
this.alias = props.alias
this.autoAcceptConnection = props.autoAcceptConnection
this.tags = props.tags
this.invitation = props.invitation

// We need a better approach for this. After retrieving the connection message from
// persistence it is plain json, so we need to transform it to a message class
// if transform all record classes with class transformer this wouldn't be needed anymore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const _invitation = props._invitation
if (_invitation) {
this._invitation = _invitation
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const _didDoc = props._didDoc
if (_didDoc) {
this._didDoc = _didDoc
super()

if (props) {
this.id = props.id ?? uuid()
this.createdAt = props.createdAt ?? new Date()
this.did = props.did
this.didDoc = props.didDoc
this.verkey = props.verkey
this.theirDid = props.theirDid
this.theirDidDoc = props.theirDidDoc
this.state = props.state
this.role = props.role
this.alias = props.alias
this.autoAcceptConnection = props.autoAcceptConnection
this.tags = props.tags
this.invitation = props.invitation
}

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const _theirDidDoc = props._theirDidDoc
if (_theirDidDoc) {
this._theirDidDoc = _theirDidDoc
}
}

public get invitation() {
if (this._invitation) return JsonTransformer.fromJSON(this._invitation, ConnectionInvitationMessage)
}

public set invitation(invitation: ConnectionInvitationMessage | undefined) {
if (invitation) this._invitation = JsonTransformer.toJSON(invitation)
}

public get didDoc() {
return plainToClass(DidDoc, this._didDoc)
}

public set didDoc(didDoc: DidDoc) {
this._didDoc = classToPlain(didDoc)
}

public get theirDidDoc() {
if (this._theirDidDoc) return plainToClass(DidDoc, this._theirDidDoc)
}

public set theirDidDoc(didDoc: DidDoc | undefined) {
this._theirDidDoc = classToPlain(didDoc)
}

public get myKey() {
Expand Down
Loading

0 comments on commit e07b90e

Please sign in to comment.