Skip to content

Commit

Permalink
feat: createStructWithAdditionalProperties superstruct factory (#334)
Browse files Browse the repository at this point in the history
  • Loading branch information
khanti42 authored Aug 26, 2024
1 parent 75237eb commit b393fe8
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 1 deletion.
52 changes: 51 additions & 1 deletion packages/starknet-snap/src/utils/superstruct.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { constants } from 'starknet';
import { StructError, assert } from 'superstruct';
import { StructError, assert, object, number, string } from 'superstruct';

import transactionExample from '../__tests__/fixture/transactionExample.json';
import typedDataExample from '../__tests__/fixture/typedDataExample.json';
Expand All @@ -11,6 +11,7 @@ import {
CairoVersionStruct,
CallDataStruct,
ChainIdStruct,
createStructWithAdditionalProperties,
TxVersionStruct,
TypeDataStruct,
} from './superstruct';
Expand Down Expand Up @@ -174,3 +175,52 @@ describe('CallDataStruct', () => {
);
});
});

describe('createStructWithAdditionalProperties', () => {
const predefinedProperties = object({
name: string(),
age: number(),
});

const additionalPropertyTypes = string(); // Additional properties should be strings
const ExtendedPropStruct = createStructWithAdditionalProperties(
predefinedProperties,
additionalPropertyTypes,
);
it('should validate predefined properties correctly', () => {
const validData = {
name: 'John',
age: 30,
};
const [error, result] = ExtendedPropStruct.validate(validData);

expect(error).toBeUndefined();
expect(result).toStrictEqual(validData);
});

it('should validate additional properties correctly', () => {
const validDataWithExtra = {
name: 'John',
age: 30,
nickname: 'Johnny',
};

const [error, result] = ExtendedPropStruct.validate(validDataWithExtra);

expect(error).toBeUndefined();
expect(result).toStrictEqual(validDataWithExtra);
});

it('should fail validation if additional properties are of the wrong type', () => {
const invalidData = {
name: 'John',
age: 30,
nickname: 12345, // Invalid type for additional property
};

const [error] = ExtendedPropStruct.validate(invalidData);

expect(error).toBeDefined();
expect(error?.message).toContain('Expected a string, but received');
});
});
38 changes: 38 additions & 0 deletions packages/starknet-snap/src/utils/superstruct.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { union } from '@metamask/snaps-sdk';
import { constants, validateAndParseAddress } from 'starknet';
import type { Struct } from 'superstruct';
import {
boolean,
enums,
Expand All @@ -11,6 +12,8 @@ import {
any,
number,
array,
dynamic,
assign,
} from 'superstruct';

import { CAIRO_VERSION_LEGACY, CAIRO_VERSION } from './constants';
Expand Down Expand Up @@ -91,3 +94,38 @@ export const CairoVersionStruct = enums([CAIRO_VERSION, CAIRO_VERSION_LEGACY]);
export const TxVersionStruct = enums(
Object.values(constants.TRANSACTION_VERSION),
);

/**
* Creates a struct that combines predefined properties with additional dynamic properties.
*
* This function generates a Superstruct schema that includes both the predefined properties
* and any additional properties found in the input. The additional properties are validated
* according to the specified `additionalPropertyTypes`, or `any` if not provided.
*
* @param predefinedProperties - A Superstruct schema defining the base set of properties that are expected.
* @param additionalPropertyTypes - A Superstruct schema that defines the types for any additional properties.
* Defaults to `any`, allowing any additional properties.
* @returns A dynamic struct that first validates against the predefined properties and then
* includes any additional properties that match the `additionalPropertyTypes` schema.
*/
export const createStructWithAdditionalProperties = (
predefinedProperties: Struct<any, any>,
additionalPropertyTypes: Struct<any, any> = any(),
) => {
return dynamic((value) => {
if (typeof value !== 'object' || value === null) {
return predefinedProperties;
}

const additionalProperties = Object.keys(value).reduce<
Record<string, Struct>
>((schema, key) => {
if (!(key in predefinedProperties.schema)) {
schema[key] = additionalPropertyTypes;
}
return schema;
}, {});

return assign(predefinedProperties, object(additionalProperties));
});
};

0 comments on commit b393fe8

Please sign in to comment.