Skip to content

Commit

Permalink
feat(protobuf): load definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
szymonlesisz committed Dec 12, 2024
1 parent e3976a6 commit ea65a43
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/protobuf/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './encode';
export * as Messages from './messages';
export * from './types';
export { parseConfigure, createMessageFromName, createMessageFromType } from './utils';
export { loadDefinitions } from './load-definitions';
export * as MessagesSchema from './messages-schema';
// It's problem to reexport enums when they are under MessagesSchema namespace, check packages/connect/src/types/device.ts
export { DeviceModelInternal } from './messages-schema';
49 changes: 49 additions & 0 deletions packages/protobuf/src/load-definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Root } from 'protobufjs/light';

type Definitions = Record<string, unknown>;

export const loadDefinitions = async (
messages: Root,
packageName: string,
packageLoader: () => Definitions | Promise<Definitions>,
) => {
// check if package already exists
try {
const pkg = messages.lookup(packageName);
if (pkg) {
return;
}
} catch {}

// get current MessageType enum
let enumType;
try {
enumType = messages.lookupEnum('MessageType');
} catch {}

// load definitions
const packageMessages = await packageLoader();
const pkg = messages.define(packageName, packageMessages);
// get package MessageType enum
let packageEnumType;
try {
packageEnumType = pkg.lookupEnum('MessageType');
} catch {}

// merge MessageType enums
if (enumType && packageEnumType) {
try {
// move values from nested enum to top level
Object.keys(packageEnumType.values).forEach(key => {
enumType.add(key, packageEnumType.values[key]);
});
// remove nested enum
pkg.remove(packageEnumType);
} catch (e) {
// remove whole package on merge error
messages.remove(pkg);

throw e;
}
}
};
100 changes: 100 additions & 0 deletions packages/protobuf/tests/load-definitions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import * as ProtoBuf from 'protobufjs/light';

import { loadDefinitions } from '../src/load-definitions';

describe('loadDefinitions', () => {
const createProtobufRoot = () => {
return ProtoBuf.Root.fromJSON({
nested: {
MessageType: {
values: {
Initialize: 0,
},
},
},
});
};

it('merge MessageType enum', async () => {
const root = createProtobufRoot();
await loadDefinitions(root, 'bitcoin', () => {
return Promise.resolve({
MessageType: {
values: {
GetAddress: 29,
},
},
});
});

const messageType = root.lookupEnum('MessageType')?.values;
expect(messageType).toEqual({
Initialize: 0,
GetAddress: 29,
});
});

it('throw on merge MessageType enum', async () => {
const root1 = createProtobufRoot();
await expect(
loadDefinitions(root1, 'bitcoin', () => {
return Promise.resolve({
MessageType: {
values: {
GetAddress: 0,
},
},
});
}),
).rejects.toThrow('duplicate id 0');
expect(root1.lookup('bitcoin')).toBe(null);

await expect(
loadDefinitions(createProtobufRoot(), 'bitcoin', () => {
return Promise.resolve({
MessageType: {
values: {
Initialize: 1,
},
},
});
}),
).rejects.toThrow('duplicate name');
});

it('create MessageType enum', async () => {
const root = createProtobufRoot();
root.remove(root.lookupEnum('MessageType'));

await loadDefinitions(root, 'bitcoin', () => {
return Promise.resolve({
MessageType: {
values: {
GetAddress: 29,
},
},
});
});

const messageType = root.lookupEnum('MessageType')?.values;
expect(messageType).toEqual({
GetAddress: 29,
});
});

it('already loaded', async () => {
const root = createProtobufRoot();
root.define('bitcoin', {
MessageType: {
values: {
GetAddress: 29,
},
},
});

const spy = jest.fn();
await loadDefinitions(root, 'bitcoin', spy);

expect(spy).toHaveBeenCalledTimes(0);
});
});

0 comments on commit ea65a43

Please sign in to comment.