Skip to content

Commit

Permalink
Refactor Dwn#processMessage and other top level handlers (#361)
Browse files Browse the repository at this point in the history
* Refactor Dwn#processMessage and other top level handlers

* Lint

* updated synchronizePrunedInitialRecordsWrite() accordingly

---------

Co-authored-by: Henry Tsai <henrytsai@squareup.com>
  • Loading branch information
Diane Huxley and thehenrytsai authored May 12, 2023
1 parent 9f644fa commit 7cec9ee
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 43 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Decentralized Web Node (DWN) SDK

Code Coverage
![Statements](https://img.shields.io/badge/statements-94.72%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.15%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-92.45%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.72%25-brightgreen.svg?style=flat)
![Statements](https://img.shields.io/badge/statements-94.74%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-94.13%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-92.48%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.74%25-brightgreen.svg?style=flat)

## Introduction

Expand Down
90 changes: 54 additions & 36 deletions src/dwn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,29 +93,12 @@ export class Dwn {
* @param tenant The tenant DID to route the given message to.
*/
public async processMessage(tenant: string, rawMessage: any, dataStream?: Readable): Promise<MessageReply> {
const isTenant = await this.tenantGate.isTenant(tenant);
if (!isTenant) {
return new MessageReply({
status: { code: 401, detail: `${tenant} is not a tenant` }
});
const errorMessageReply = await this.preprocessingChecks(tenant, rawMessage);
if (errorMessageReply !== undefined) {
return errorMessageReply;
}

const dwnInterface = rawMessage?.descriptor?.interface;
const dwnMethod = rawMessage?.descriptor?.method;
if (dwnInterface === undefined || dwnMethod === undefined) {
return new MessageReply({
status: { code: 400, detail: `Both interface and method must be present, interface: ${dwnInterface}, method: ${dwnMethod}` }
});
}

try {
// consider to push this down to individual handlers
Message.validateJsonSchema(rawMessage);
} catch (error) {
return MessageReply.fromError(error, 400);
}

const handlerKey = dwnInterface + dwnMethod;
const handlerKey = rawMessage.descriptor.interface + rawMessage.descriptor.method;
const methodHandlerReply = await this.methodHandlers[handlerKey].handle({
tenant,
message: rawMessage as BaseMessage,
Expand All @@ -129,50 +112,85 @@ export class Dwn {
* Handles a `RecordsRead` message.
*/
public async handleRecordsRead(tenant: string, message: RecordsReadMessage): Promise<RecordsReadReply> {
const reply = await this.processMessage(tenant, message);
return reply as RecordsReadReply;
const errorMessageReply = await this.preprocessingChecks(tenant, message, DwnInterfaceName.Records, DwnMethodName.Read);
if (errorMessageReply !== undefined) {
return errorMessageReply;
}

const handler = new RecordsReadHandler(this.didResolver, this.messageStore, this.dataStore);
return handler.handle({ tenant, message });
}

/**
* Handles a `MessagesGet` message.
*/
public async handleMessagesGet(tenant: string, message: MessagesGetMessage): Promise<MessagesGetReply> {
const reply = await this.processMessage(tenant, message);
return reply as MessagesGetReply;
const errorMessageReply = await this.preprocessingChecks(tenant, message, DwnInterfaceName.Messages, DwnMethodName.Get);
if (errorMessageReply !== undefined) {
return errorMessageReply;
}

const handler = new MessagesGetHandler(this.didResolver, this.messageStore, this.dataStore);
return handler.handle({ tenant, message });
}

/**
* Privileged method for writing a pruned initial `RecordsWrite` to a DWN without needing to supply associated data.
*/
public async synchronizePrunedInitialRecordsWrite(tenant: string, message: RecordsWriteMessage): Promise<MessagesGetReply> {
const errorMessageReply = await this.preprocessingChecks(tenant, message, DwnInterfaceName.Records, DwnMethodName.Write);
if (errorMessageReply !== undefined) {
return errorMessageReply;
}

const methodHandlerReply = await this.prunedInitialRecordsWriteHandler.handle({ tenant, message });
return methodHandlerReply;
}

/**
* Common checks for handlers.
*/
private async preprocessingChecks(
tenant: string,
rawMessage: any,
expectedInterface?: DwnInterfaceName,
expectedMethod?: DwnMethodName
): Promise<MessageReply | undefined> {
const isTenant = await this.tenantGate.isTenant(tenant);
if (!isTenant) {
return new MessageReply({
status: { code: 401, detail: `${tenant} is not a tenant` }
});
}

// DWN interface and method check mainly for pure JS
const dwnInterface = message?.descriptor?.interface;
const dwnMethod = message?.descriptor?.method;
if (dwnInterface !== DwnInterfaceName.Records || dwnMethod !== DwnMethodName.Write) {
// Verify interface and method
const dwnInterface = rawMessage?.descriptor?.interface;
const dwnMethod = rawMessage?.descriptor?.method;
if (dwnInterface === undefined || dwnMethod === undefined) {
return new MessageReply({
status: { code: 400, detail: `Both interface and method must be present, interface: ${dwnInterface}, method: ${dwnMethod}` }
});
}
if (expectedInterface !== undefined && expectedInterface !== dwnInterface) {
return new MessageReply({
status: {
code : 400,
detail : `Invalid DWN interface or method: expecting ${DwnInterfaceName.Records}${DwnMethodName.Write}, got ${dwnInterface}${dwnMethod}.`
}
status: { code: 400, detail: `Expected interface ${expectedInterface}, received ${dwnInterface}` }
});
}
if (expectedMethod !== undefined && expectedMethod !== dwnMethod) {
return new MessageReply({
status: { code: 400, detail: `Expected method ${expectedInterface}${expectedMethod}, received ${dwnInterface}${dwnMethod}` }
});
}

// validate message structure
try {
// consider to push this down to individual handlers
Message.validateJsonSchema(message);
Message.validateJsonSchema(rawMessage);
} catch (error) {
return MessageReply.fromError(error, 400);
}

const methodHandlerReply = await this.prunedInitialRecordsWriteHandler.handle({ tenant, message });
return methodHandlerReply;
return undefined;
}

public async dump(): Promise<void> {
Expand Down
47 changes: 41 additions & 6 deletions tests/dwn.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { Encoder } from '../src/index.js';
import { EventLogLevel } from '../src/event-log/event-log-level.js';
import { MessageStoreLevel } from '../src/store/message-store-level.js';
import { TestDataGenerator } from './utils/test-data-generator.js';
import { DwnInterfaceName, Message } from '../src/core/message.js';
import { DwnInterfaceName, DwnMethodName, Message } from '../src/core/message.js';
import { Jws, RecordsRead } from '../src/index.js';

chai.use(chaiAsPromised);

Expand Down Expand Up @@ -165,8 +166,24 @@ describe('DWN', () => {
});
});

describe('handleRecordsRead', () => {
it('should return error if preprocessing checks fail', async () => {
const alice = await DidKeyResolver.generate();

const recordsRead = await RecordsRead.create({
recordId : 'recordId-doesnt-matter',
authorizationSignatureInput : Jws.createSignatureInput(alice)
});
(recordsRead.message as any).descriptor.method = 'Write'; // Will cause interface and method check to fail
const reply = await dwn.handleRecordsRead(alice.did, recordsRead.message);

expect(reply.status.code).to.not.equal(200);
});
});

describe('handleMessagesGet', () => {
it('increases test coverage :)', async () => {
// increases test coverage :)
it('runs successfully', async () => {
const did = await DidKeyResolver.generate();
const alice = await TestDataGenerator.generatePersona(did);
const messageCids: string[] = [];
Expand Down Expand Up @@ -199,6 +216,24 @@ describe('DWN', () => {
expect(messageReply.messageCid).to.equal(cid);
}
});

it('should return error if preprocessing checks fail', async () => {
const alice = await DidKeyResolver.generate();

const { recordsWrite } = await TestDataGenerator.generateRecordsWrite({
requester: alice
});

const messageCids = [await Message.getCid(recordsWrite.message)];
const { messagesGet } = await TestDataGenerator.generateMessagesGet({
requester: alice,
messageCids
});
(messagesGet.message as any).descriptor.interface = 'Protocols'; // Will cause interface and method check to fail
const reply = await dwn.handleMessagesGet(alice.did, messagesGet.message);

expect(reply.status.code).to.not.equal(200);
});
});

describe('synchronizePrunedInitialRecordsWrite()', () => {
Expand Down Expand Up @@ -294,21 +329,21 @@ describe('DWN', () => {
const alice = await DidKeyResolver.generate();
const reply1 = await dwn.synchronizePrunedInitialRecordsWrite(alice.did, undefined as unknown as RecordsWriteMessage ); // missing message
expect(reply1.status.code).to.equal(400);
expect(reply1.status.detail).to.contain('Invalid DWN interface or method');
expect(reply1.status.detail).to.contain('Both interface and method must be present');

const reply2 = await dwn.synchronizePrunedInitialRecordsWrite(
alice.did,
{ descriptor: { interface: 'IncorrectInterface' } } as RecordsWriteMessage
{ descriptor: { interface: 'IncorrectInterface', method: DwnMethodName.Write } } as RecordsWriteMessage
);
expect(reply2.status.code).to.equal(400);
expect(reply2.status.detail).to.contain('Invalid DWN interface or method');
expect(reply2.status.detail).to.contain(`Expected interface ${DwnInterfaceName.Records}`);

const reply3 = await dwn.synchronizePrunedInitialRecordsWrite(
alice.did,
{ descriptor: { interface: DwnInterfaceName.Records, method: 'IncorrectMethod' } } as RecordsWriteMessage
);
expect(reply3.status.code).to.equal(400);
expect(reply3.status.detail).to.contain('Invalid DWN interface or method');
expect(reply3.status.detail).to.contain(`Expected method ${DwnInterfaceName.Records}${DwnMethodName.Write}`);
});
});
});

0 comments on commit 7cec9ee

Please sign in to comment.