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: improve sending error handling #1045

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 8 additions & 4 deletions packages/core/src/agent/MessageSender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { TransportSession } from './TransportService'

import { DID_COMM_TRANSPORT_QUEUE, InjectionSymbols } from '../constants'
import { ReturnRouteTypes } from '../decorators/transport/TransportDecorator'
import { AriesFrameworkError } from '../error'
import { AriesFrameworkError, MessageSendingError } from '../error'
import { Logger } from '../logger'
import { DidCommDocumentService } from '../modules/didcomm'
import { getKeyDidMappingByVerificationMethod } from '../modules/dids/domain/key-type'
Expand Down Expand Up @@ -209,8 +209,9 @@ export class MessageSender {

if (!connection.did) {
this.logger.error(`Unable to send message using connection '${connection.id}' that doesn't have a did`)
throw new AriesFrameworkError(
`Unable to send message using connection '${connection.id}' that doesn't have a did`
throw new MessageSendingError(
`Unable to send message using connection '${connection.id}' that doesn't have a did`,
{ outboundMessage }
)
}

Expand Down Expand Up @@ -277,7 +278,10 @@ export class MessageSender {
errors,
connection,
})
throw new AriesFrameworkError(`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`)
throw new MessageSendingError(
`Message is undeliverable to connection ${connection.id} (${connection.theirLabel})`,
{ outboundMessage }
)
}

public async sendMessageToService({
Expand Down
15 changes: 4 additions & 11 deletions packages/core/src/error/MessageSendingError.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import type { AgentMessage } from '../agent/AgentMessage'
import type { BaseRecord } from '../storage/BaseRecord'
import type { OutboundMessage } from '../types'

import { AriesFrameworkError } from './AriesFrameworkError'

export class MessageSendingError extends AriesFrameworkError {
public agentMessage: AgentMessage
public associatedRecord?: BaseRecord
public outboundMessage: OutboundMessage
public constructor(
message: string,
{
agentMessage,
associatedRecord,
cause,
}: { agentMessage: AgentMessage; associatedRecord?: BaseRecord; cause?: Error }
{ outboundMessage, cause }: { outboundMessage: OutboundMessage; associatedRecordId?: string; cause?: Error }
) {
super(message, { cause })
this.agentMessage = agentMessage
this.associatedRecord = associatedRecord
this.outboundMessage = outboundMessage
}
}
13 changes: 3 additions & 10 deletions packages/core/src/modules/basic-messages/BasicMessagesModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { BasicMessageTags } from './repository/BasicMessageRecord'
import { Dispatcher } from '../../agent/Dispatcher'
import { MessageSender } from '../../agent/MessageSender'
import { createOutboundMessage } from '../../agent/helpers'
import { MessageSendingError } from '../../error/MessageSendingError'
import { injectable, module } from '../../plugins'
import { ConnectionService } from '../connections'

Expand Down Expand Up @@ -48,15 +47,9 @@ export class BasicMessagesModule {
connection
)
const outboundMessage = createOutboundMessage(connection, basicMessage)
try {
await this.messageSender.sendMessage(outboundMessage)
} catch (error) {
throw new MessageSendingError(`Error sending basic message: ${error.message}`, {
agentMessage: basicMessage,
associatedRecord: basicMessageRecord,
cause: error,
})
}
outboundMessage.associatedRecordId = basicMessageRecord.id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the id but not the type can make it hard to get the record type in a generic context. A possibility is attachign the whole record, but I like the id appraoch better.. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also hesitating on using the whole record or just the id. Opted to go for the id in order to make this whole outbound message more 'lightweight' and considering that the context would know the type of record to search for. But I don't have a very strong opinion on this. Can change to the whole record if we foresee that it's more convenient.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think my preference would go to attaching the record


await this.messageSender.sendMessage(outboundMessage)
return basicMessageRecord
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getBaseConfig, makeConnection, waitForBasicMessage } from '../../../../
import testLogger from '../../../../tests/logger'
import { Agent } from '../../../agent/Agent'
import { MessageSendingError, RecordNotFoundError } from '../../../error'
import { BasicMessage } from '../messages'
import { BasicMessageRecord } from '../repository'

const faberConfig = getBaseConfig('Faber Basic Messages', {
Expand Down Expand Up @@ -87,20 +88,22 @@ describe('Basic Messages E2E', () => {
} catch (error) {
const thrownError = error as MessageSendingError
expect(thrownError.message).toEqual(
`Error sending basic message: Message is undeliverable to connection ${aliceConnection.id} (${aliceConnection.theirLabel})`
`Message is undeliverable to connection ${aliceConnection.id} (${aliceConnection.theirLabel})`
)
testLogger.test('Error thrown includes the message and recently created record')
expect(thrownError.associatedRecord).toBeInstanceOf(BasicMessageRecord)
expect((thrownError.associatedRecord as BasicMessageRecord).content).toBe('Hello undeliverable')
testLogger.test('Error thrown includes the outbound message and recently created record id')
expect(thrownError.outboundMessage.associatedRecordId).toBeDefined()
expect(thrownError.outboundMessage.payload).toBeInstanceOf(BasicMessage)
expect((thrownError.outboundMessage.payload as BasicMessage).content).toBe('Hello undeliverable')

testLogger.test('Created record can be found and deleted by id')
const storedRecord = await aliceAgent.basicMessages.getById(thrownError.associatedRecord!.id)
expect(storedRecord).toMatchObject(thrownError.associatedRecord!)
const storedRecord = await aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecordId!)
expect(storedRecord).toBeInstanceOf(BasicMessageRecord)
expect(storedRecord.content).toBe('Hello undeliverable')

await aliceAgent.basicMessages.deleteById(storedRecord.id)
await expect(aliceAgent.basicMessages.getById(thrownError.associatedRecord!.id)).rejects.toThrowError(
RecordNotFoundError
)
await expect(
aliceAgent.basicMessages.getById(thrownError.outboundMessage.associatedRecordId!)
).rejects.toThrowError(RecordNotFoundError)
}
spy.mockClear()
})
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface OutboundMessage<T extends AgentMessage = AgentMessage> {
connection: ConnectionRecord
sessionId?: string
outOfBand?: OutOfBandRecord
associatedRecordId?: string
}

export interface OutboundServiceMessage<T extends AgentMessage = AgentMessage> {
Expand Down