Skip to content

Commit

Permalink
remove unecessary Event Filters
Browse files Browse the repository at this point in the history
  • Loading branch information
LiranCohen committed Jun 20, 2024
1 parent 397ccd1 commit eff6b2b
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 1,644 deletions.
77 changes: 1 addition & 76 deletions json-schemas/interface-methods/events-filter.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,7 @@
"protocol": {
"type": "string"
},
"protocolPath": {
"type": "string"
},
"recipient": {
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/$defs/did"
},
"contextId": {
"type": "string"
},
"schema": {
"type": "string"
},
"recordId": {
"type": "string"
},
"parentId": {
"type": "string"
},
"dataFormat": {
"type": "string"
},
"dataSize": {
"$ref": "https://identity.foundation/dwn/json-schemas/number-range-filter.json"
},
"dateCreated": {
"messageTimestamp": {
"type": "object",
"minProperties": 1,
"additionalProperties": false,
Expand All @@ -59,57 +35,6 @@
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/$defs/date-time"
}
}
},
"datePublished": {
"type": "object",
"minProperties": 1,
"additionalProperties": false,
"properties": {
"from": {
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/$defs/date-time"
},
"to": {
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/$defs/date-time"
}
}
},
"dateUpdated": {
"type": "object",
"minProperties": 1,
"additionalProperties": false,
"properties": {
"from": {
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/$defs/date-time"
},
"to": {
"$ref": "https://identity.foundation/dwn/json-schemas/defs.json#/$defs/date-time"
}
}
}
},
"dependencies": {
"datePublished": {
"oneOf": [
{
"properties": {
"published": {
"enum": [
true
]
}
},
"required": [
"published"
]
},
{
"not": {
"required": [
"published"
]
}
}
]
}
}
}
3 changes: 0 additions & 3 deletions src/interfaces/events-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ export class EventsQuery extends AbstractMessage<EventsQueryMessage>{
if ('protocol' in filter && filter.protocol !== undefined) {
validateProtocolUrlNormalized(filter.protocol);
}
if ('schema' in filter && filter.schema !== undefined) {
validateSchemaUrlNormalized(filter.schema);
}
}

return new EventsQuery(message);
Expand Down
3 changes: 0 additions & 3 deletions src/interfaces/events-subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ export class EventsSubscribe extends AbstractMessage<EventsSubscribeMessage> {
if ('protocol' in filter && filter.protocol !== undefined) {
validateProtocolUrlNormalized(filter.protocol);
}
if ('schema' in filter && filter.schema !== undefined) {
validateSchemaUrlNormalized(filter.schema);
}
}

Time.validateTimestamp(message.descriptor.messageTimestamp);
Expand Down
27 changes: 2 additions & 25 deletions src/types/events-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,13 @@ import type { PaginationCursor, RangeCriterion, RangeFilter } from './query-type
/**
* filters used when filtering for any type of Message across interfaces
*/
export type EventsMessageFilter = {
export type EventsFilter = {
interface?: string;
method?: string;
dateUpdated?: RangeCriterion;
};

/**
* We only allow filtering for events by immutable properties, the omitted properties could be different per subsequent writes.
*/
export type EventsRecordsFilter = {
recipient?: string;
protocol?: string;
protocolPath?: string;
contextId?: string;
schema?: string;
recordId?: string;
parentId?: string;
dataFormat?: string;
dataSize?: RangeFilter;
dateCreated?: RangeCriterion;
dateUpdated?: RangeCriterion;
messageTimestamp?: RangeCriterion;
};


/**
* A union type of the different types of filters a user can use when issuing an EventsQuery or EventsSubscribe
* TODO: simplify the EventsFilters to only the necessary in order to reduce complexity https://github.com/TBD54566975/dwn-sdk-js/issues/663
*/
export type EventsFilter = EventsMessageFilter | EventsRecordsFilter;

export type MessageSubscriptionHandler = (event: MessageEvent) => void;

export type EventsSubscribeMessageOptions = {
Expand Down
78 changes: 26 additions & 52 deletions src/utils/events.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { EventsFilter } from '../types/events-types.js';
import type { Filter } from '../types/query-types.js';
import type { EventsFilter, EventsMessageFilter, EventsRecordsFilter } from '../types/events-types.js';

import { FilterUtility } from '../utils/filter.js';
import { PermissionsProtocol } from '../protocols/permissions.js';
import { Records } from '../utils/records.js';
import { isEmptyObject, removeUndefinedProperties } from './object.js';
import { normalizeProtocolUrl } from './url.js';


/**
Expand All @@ -20,13 +21,13 @@ export class Events {

// normalize each filter individually by the type of filter it is.
for (const filter of filters) {
let eventsFilter: EventsFilter;
if (this.isRecordsFilter(filter)) {
eventsFilter = Records.normalizeFilter(filter);
} else {
// no normalization needed
eventsFilter = filter;
}
// normalize the protocol URL if it exists
const protocol = filter.protocol !== undefined ? normalizeProtocolUrl(filter.protocol) : undefined;

const eventsFilter = {
...filter,
protocol,
};

// remove any empty filter properties and do not add if empty
removeUndefinedProperties(eventsFilter);
Expand All @@ -35,7 +36,6 @@ export class Events {
}
}


return eventsQueryFilters;
}

Expand All @@ -54,77 +54,51 @@ export class Events {
// first we check for `EventsRecordsFilter` fields for conversion
// otherwise it is `EventsMessageFilter` fields for conversion
for (const filter of filters) {
if (this.isRecordsFilter(filter)) {

// extract the protocol tag filter from the incoming event record filter
// this filters for permission grants, requests and revocations associated with a targeted protocol
// since permissions are their own protocol, we add an additional tag index when writing the permission messages, so we can filter on it here
const protocolTagFilter = this.extractProtocolTagFilters(filter);
if (protocolTagFilter) {
eventsQueryFilters.push(protocolTagFilter);
}

eventsQueryFilters.push(Records.convertFilter(filter));
} else {
eventsQueryFilters.push(this.convertFilter(filter));
// extract the protocol tag filter from the incoming event record filter
// this filters for permission grants, requests and revocations associated with a targeted protocol
// since permissions are their own protocol, we add an additional tag index when writing the permission messages, so we can filter on it here
const protocolTagFilter = this.extractProtocolTagFilters(filter);
if (protocolTagFilter) {
eventsQueryFilters.push(protocolTagFilter);
}

eventsQueryFilters.push(this.convertFilter(filter));
}

return eventsQueryFilters;
}

private static extractProtocolTagFilters(filter: EventsRecordsFilter): Filter | undefined {
if (filter.protocol !== undefined) {
private static extractProtocolTagFilters(filter: EventsFilter): Filter | undefined {
const { protocol, messageTimestamp } = filter;
if (protocol !== undefined) {
const taggedFilter = {
protocol: PermissionsProtocol.uri,
...Records.convertTagsFilter({ protocol: filter.protocol })
...Records.convertTagsFilter({ protocol })
} as Filter;

if (filter.dateUpdated != undefined) {
const messageTimestampFilter = filter.dateUpdated ? FilterUtility.convertRangeCriterion(filter.dateUpdated) : undefined;
if (messageTimestamp != undefined) {
const messageTimestampFilter = FilterUtility.convertRangeCriterion(messageTimestamp);
if (messageTimestampFilter) {
taggedFilter.messageTimestamp = messageTimestampFilter;
}
}

if (filter.dateCreated !== undefined) {
const dateCreatedFilter = filter.dateCreated ? FilterUtility.convertRangeCriterion(filter.dateCreated) : undefined;
if (dateCreatedFilter) {
taggedFilter.dateCreated = dateCreatedFilter;
}
}

return taggedFilter;
}
}

/**
* Converts an external-facing filter model into an internal-facing filer model used by data store.
*/
private static convertFilter(filter: EventsMessageFilter): Filter {
private static convertFilter(filter: EventsFilter): Filter {
const filterCopy = { ...filter } as Filter;

const { dateUpdated } = filter;
const messageTimestampFilter = dateUpdated ? FilterUtility.convertRangeCriterion(dateUpdated) : undefined;
const { messageTimestamp } = filter;
const messageTimestampFilter = messageTimestamp ? FilterUtility.convertRangeCriterion(messageTimestamp) : undefined;
if (messageTimestampFilter) {
filterCopy.messageTimestamp = messageTimestampFilter;
delete filterCopy.dateUpdated;
}
return filterCopy as Filter;
}

// we deliberately do not check for `dateUpdated` in this filter.
// if it were the only property that matched, it could be handled by `EventsFilter`
private static isRecordsFilter(filter: EventsFilter): filter is EventsRecordsFilter {
return 'author' in filter ||
'dateCreated' in filter ||
'dataFormat' in filter ||
'dataSize' in filter ||
'parentId' in filter ||
'recordId' in filter ||
'schema' in filter ||
'protocol' in filter ||
'protocolPath' in filter ||
'recipient' in filter;
}
}
8 changes: 4 additions & 4 deletions tests/handlers/events-query.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function testEventsQueryHandler(): void {

const { message } = await TestDataGenerator.generateEventsQuery({
author : alice,
filters : [{ schema: 'schema1' }]
filters : [{ protocol: 'http://example.org/protocol/v1' }]
});
const eventsQueryHandler = new EventsQueryHandler(didResolver, eventLog);
const reply = await eventsQueryHandler.handle({ tenant: bob.did, message });
Expand All @@ -77,7 +77,7 @@ export function testEventsQueryHandler(): void {

const { message } = await TestDataGenerator.generateEventsQuery({
author : alice,
filters : [{ schema: 'schema1' }]
filters : [{ protocol: 'http://example.org/protocol/v1' }]
});
(message['descriptor'] as any)['troll'] = 'hehe';

Expand All @@ -93,7 +93,7 @@ export function testEventsQueryHandler(): void {

const { message } = await TestDataGenerator.generateEventsQuery({
author : alice,
filters : [{ schema: 'schema1' }],
filters : [{ protocol: 'http://example.org/protocol/v1' }],
}); // create with filter to prevent failure on .create()
message.descriptor.filters = []; // remove filters
const eventsQueryHandler = new EventsQueryHandler(didResolver, eventLog);
Expand All @@ -108,7 +108,7 @@ export function testEventsQueryHandler(): void {

const { message } = await TestDataGenerator.generateEventsQuery({
author : alice,
filters : [{ schema: 'schema1' }],
filters : [{ protocol: 'http://example.org/protocol/v1' }],
}); // create with filter to prevent failure on .create()
message.descriptor.filters = [{}]; // empty out filter properties
const eventsQueryHandler = new EventsQueryHandler(didResolver, eventLog);
Expand Down
26 changes: 5 additions & 21 deletions tests/interfaces/events-query.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('EventsQuery Message', () => {

const currentTime = Time.getCurrentTimestamp();
const eventsQuery = await EventsQuery.create({
filters : [{ schema: 'anything' }],
filters : [{ protocol: 'http://example.org/protocol/v1' }],
messageTimestamp : currentTime,
signer : Jws.createSigner(alice),
});
Expand All @@ -43,22 +43,6 @@ describe('EventsQuery Message', () => {
expect((message.descriptor.filters![0] as ProtocolsQueryFilter).protocol).to.eq('http://example.com');
});

it('should auto-normalize schema URL', async () => {
const alice = await TestDataGenerator.generatePersona();

const options = {
recipient : alice.did,
signer : Jws.createSigner(alice),
filters : [{ schema: 'example.com/' }],
};
const eventsQuery = await EventsQuery.create(options);

const message = eventsQuery.message as EventsQueryMessage;

expect(message.descriptor.filters?.length).to.equal(1);
expect((message.descriptor.filters![0] as RecordsFilter).schema).to.eq('http://example.com');
});

it('allows query with no filters', async () => {
const alice = await TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
Expand All @@ -83,7 +67,7 @@ describe('EventsQuery Message', () => {

// empty filter gets removed, valid filter remains
const eventsQuery = await EventsQuery.create({
filters : [{ schema: 'schema' },{ }], // one empty filter
filters : [{ protocol: 'http://example.org/protocol/v1' },{ }], // one empty filter
messageTimestamp : currentTime,
signer : Jws.createSigner(alice),
});
Expand All @@ -98,7 +82,7 @@ describe('EventsQuery Message', () => {
const currentTime = Time.getCurrentTimestamp();

const eventsQuery = await EventsQuery.create({
filters : [{ schema: 'anything' }],
filters : [{ protocol: 'http://example.org/protocol/v1' }],
messageTimestamp : currentTime,
signer : Jws.createSigner(alice),
});
Expand All @@ -116,7 +100,7 @@ describe('EventsQuery Message', () => {
const alice = await TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const eventsQuery = await EventsQuery.create({
filters : [{ schema: 'anything' }],
filters : [{ protocol: 'http://example.org/protocol/v1' }],
messageTimestamp : currentTime,
signer : Jws.createSigner(alice),
});
Expand Down Expand Up @@ -144,7 +128,7 @@ describe('EventsQuery Message', () => {
const alice = await TestDataGenerator.generatePersona();
const currentTime = Time.getCurrentTimestamp();
const eventsQuery = await EventsQuery.create({
filters : [{ schema: 'anything' }],
filters : [{ protocol: 'http://example.org/protocol/v1' }],
messageTimestamp : currentTime,
signer : Jws.createSigner(alice),
});
Expand Down
Loading

0 comments on commit eff6b2b

Please sign in to comment.