From f74707dad051ed6861d5695147f34df8731ab045 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Tue, 9 Apr 2024 14:08:56 +0100 Subject: [PATCH] Add BytesValueNode and ConstantValueNode (#198) --- .changeset/tough-planes-call.md | 5 +++ src/nodes/valueNodes/BytesValueNode.ts | 37 ++++++++++++++++++ src/nodes/valueNodes/ConstantValueNode.ts | 38 +++++++++++++++++++ src/nodes/valueNodes/ValueNode.ts | 6 +++ src/nodes/valueNodes/index.ts | 2 + .../js-experimental/renderValueNodeVisitor.ts | 14 ++++++- src/renderers/js/renderValueNodeVisitor.ts | 17 ++++++++- src/renderers/rust/renderValueNodeVisitor.ts | 16 +++++++- src/visitors/getDebugStringVisitor.ts | 2 + src/visitors/identityVisitor.ts | 13 +++++++ src/visitors/mergeVisitor.ts | 9 +++++ .../nodes/valueNodes/BytesValueNode.test.ts | 19 ++++++++++ .../valueNodes/ConstantValueNode.test.ts | 28 ++++++++++++++ 13 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 .changeset/tough-planes-call.md create mode 100644 src/nodes/valueNodes/BytesValueNode.ts create mode 100644 src/nodes/valueNodes/ConstantValueNode.ts create mode 100644 test/visitors/nodes/valueNodes/BytesValueNode.test.ts create mode 100644 test/visitors/nodes/valueNodes/ConstantValueNode.test.ts diff --git a/.changeset/tough-planes-call.md b/.changeset/tough-planes-call.md new file mode 100644 index 000000000..f27143832 --- /dev/null +++ b/.changeset/tough-planes-call.md @@ -0,0 +1,5 @@ +--- +"@metaplex-foundation/kinobi": minor +--- + +Add BytesValueNode and ConstantValueNode diff --git a/src/nodes/valueNodes/BytesValueNode.ts b/src/nodes/valueNodes/BytesValueNode.ts new file mode 100644 index 000000000..a59e1beed --- /dev/null +++ b/src/nodes/valueNodes/BytesValueNode.ts @@ -0,0 +1,37 @@ +import { + getBase16Encoder, + getBase58Encoder, + getBase64Encoder, + getUtf8Encoder, +} from '@solana/codecs-strings'; + +export type BytesEncoding = 'utf8' | 'base16' | 'base58' | 'base64'; + +export interface BytesValueNode { + readonly kind: 'bytesValueNode'; + + // Data. + readonly data: string; + readonly encoding: BytesEncoding; +} + +export function bytesValueNode( + encoding: BytesEncoding, + data: string +): BytesValueNode { + return { kind: 'bytesValueNode', data, encoding }; +} + +export function getBytesFromBytesValueNode(node: BytesValueNode): Uint8Array { + switch (node.encoding) { + case 'utf8': + return getUtf8Encoder().encode(node.data); + case 'base16': + return getBase16Encoder().encode(node.data); + case 'base58': + return getBase58Encoder().encode(node.data); + case 'base64': + default: + return getBase64Encoder().encode(node.data); + } +} diff --git a/src/nodes/valueNodes/ConstantValueNode.ts b/src/nodes/valueNodes/ConstantValueNode.ts new file mode 100644 index 000000000..1727d1346 --- /dev/null +++ b/src/nodes/valueNodes/ConstantValueNode.ts @@ -0,0 +1,38 @@ +import { bytesTypeNode } from '../typeNodes/BytesTypeNode'; +import { StringEncoding, stringTypeNode } from '../typeNodes/StringTypeNode'; +import { TypeNode } from '../typeNodes/TypeNode'; +import { BytesEncoding, bytesValueNode } from './BytesValueNode'; +import { stringValueNode } from './StringValueNode'; +import { ValueNode } from './ValueNode'; + +export interface ConstantValueNode< + TType extends TypeNode = TypeNode, + TValue extends ValueNode = ValueNode, +> { + readonly kind: 'constantValueNode'; + + // Children. + readonly type: TType; + readonly value: TValue; +} + +export function constantValueNode< + TType extends TypeNode, + TValue extends ValueNode, +>(type: TType, value: TValue): ConstantValueNode { + return { kind: 'constantValueNode', type, value }; +} + +export function constantValueNodeFromString( + encoding: StringEncoding, + string: string +) { + return constantValueNode(stringTypeNode(encoding), stringValueNode(string)); +} + +export function constantValueNodeFromBytes( + encoding: BytesEncoding, + data: string +) { + return constantValueNode(bytesTypeNode(), bytesValueNode(encoding, data)); +} diff --git a/src/nodes/valueNodes/ValueNode.ts b/src/nodes/valueNodes/ValueNode.ts index ae1166802..1d2efa0b5 100644 --- a/src/nodes/valueNodes/ValueNode.ts +++ b/src/nodes/valueNodes/ValueNode.ts @@ -1,5 +1,7 @@ import type { ArrayValueNode } from './ArrayValueNode'; import type { BooleanValueNode } from './BooleanValueNode'; +import type { BytesValueNode } from './BytesValueNode'; +import type { ConstantValueNode } from './ConstantValueNode'; import type { EnumValueNode } from './EnumValueNode'; import type { MapEntryValueNode } from './MapEntryValueNode'; import type { MapValueNode } from './MapValueNode'; @@ -16,7 +18,9 @@ import type { TupleValueNode } from './TupleValueNode'; // Standalone Value Node Registration. export type StandaloneValueNode = | ArrayValueNode + | BytesValueNode | BooleanValueNode + | ConstantValueNode | EnumValueNode | MapValueNode | NoneValueNode @@ -29,7 +33,9 @@ export type StandaloneValueNode = | StringValueNode; export const STANDALONE_VALUE_NODE_KINDS = [ 'arrayValueNode', + 'bytesValueNode', 'booleanValueNode', + 'constantValueNode', 'enumValueNode', 'mapValueNode', 'noneValueNode', diff --git a/src/nodes/valueNodes/index.ts b/src/nodes/valueNodes/index.ts index 80b3eb86d..56f55fe21 100644 --- a/src/nodes/valueNodes/index.ts +++ b/src/nodes/valueNodes/index.ts @@ -1,5 +1,7 @@ export * from './ArrayValueNode'; export * from './BooleanValueNode'; +export * from './BytesValueNode'; +export * from './ConstantValueNode'; export * from './EnumValueNode'; export * from './MapEntryValueNode'; export * from './MapValueNode'; diff --git a/src/renderers/js-experimental/renderValueNodeVisitor.ts b/src/renderers/js-experimental/renderValueNodeVisitor.ts index e1221133c..c05527087 100644 --- a/src/renderers/js-experimental/renderValueNodeVisitor.ts +++ b/src/renderers/js-experimental/renderValueNodeVisitor.ts @@ -1,4 +1,9 @@ -import { RegisteredValueNode, isNode, isScalarEnum } from '../../nodes'; +import { + RegisteredValueNode, + getBytesFromBytesValueNode, + isNode, + isScalarEnum, +} from '../../nodes'; import { LinkableDictionary, MainCaseString } from '../../shared'; import { Visitor, visit } from '../../visitors'; import { Fragment, fragment, mergeFragments } from './fragments'; @@ -22,6 +27,13 @@ export function renderValueNodeVisitor(input: { visitBooleanValue(node) { return fragment(JSON.stringify(node.boolean)); }, + visitBytesValue(node) { + const bytes = getBytesFromBytesValueNode(node); + return fragment(`new Uint8Array([${Array.from(bytes).join(', ')}])`); + }, + visitConstantValue() { + throw new Error('Not implemented'); + }, visitEnumValue(node) { const enumName = nameApi.dataType(node.enum.name); const enumFunction = nameApi.discriminatedUnionFunction(node.enum.name); diff --git a/src/renderers/js/renderValueNodeVisitor.ts b/src/renderers/js/renderValueNodeVisitor.ts index f3171e237..5ba887908 100644 --- a/src/renderers/js/renderValueNodeVisitor.ts +++ b/src/renderers/js/renderValueNodeVisitor.ts @@ -1,4 +1,9 @@ -import { RegisteredValueNode, isNode, isScalarEnum } from '../../nodes'; +import { + RegisteredValueNode, + getBytesFromBytesValueNode, + isNode, + isScalarEnum, +} from '../../nodes'; import { LinkableDictionary, MainCaseString, @@ -35,6 +40,16 @@ export function renderValueNodeVisitor(input: { render: JSON.stringify(node.boolean), }; }, + visitBytesValue(node) { + const bytes = getBytesFromBytesValueNode(node); + return { + imports: new JavaScriptImportMap(), + render: `new Uint8Array([${Array.from(bytes).join(', ')}])`, + }; + }, + visitConstantValue() { + throw new Error('Not implemented'); + }, visitEnumValue(node) { const imports = new JavaScriptImportMap(); const enumName = pascalCase(node.enum.name); diff --git a/src/renderers/rust/renderValueNodeVisitor.ts b/src/renderers/rust/renderValueNodeVisitor.ts index ca370caea..a685376c5 100644 --- a/src/renderers/rust/renderValueNodeVisitor.ts +++ b/src/renderers/rust/renderValueNodeVisitor.ts @@ -1,4 +1,10 @@ -import { RegisteredValueNode, ValueNode } from '../../nodes'; +import { + RegisteredValueNode, + ValueNode, + arrayValueNode, + getBytesFromBytesValueNode, + numberValueNode, +} from '../../nodes'; import { pascalCase } from '../../shared'; import { Visitor, visit } from '../../visitors'; import { RustImportMap } from './RustImportMap'; @@ -34,6 +40,14 @@ export function renderValueNodeVisitor(useStr: boolean = false): Visitor< render: JSON.stringify(node.boolean), }; }, + visitBytesValue(node) { + const bytes = getBytesFromBytesValueNode(node); + const numbers = Array.from(bytes).map((b) => numberValueNode(b)); + return visit(arrayValueNode(numbers), this); + }, + visitConstantValue() { + throw new Error('Not implemented'); + }, visitEnumValue(node) { const imports = new RustImportMap(); const enumName = pascalCase(node.enum.name); diff --git a/src/visitors/getDebugStringVisitor.ts b/src/visitors/getDebugStringVisitor.ts index 5a3f1929e..6e513ddac 100644 --- a/src/visitors/getDebugStringVisitor.ts +++ b/src/visitors/getDebugStringVisitor.ts @@ -97,6 +97,8 @@ function getNodeDetails(node: Node): string[] { return [node.string]; case 'booleanValueNode': return [node.boolean ? 'true' : 'false']; + case 'bytesValueNode': + return [node.encoding, node.data]; case 'publicKeyValueNode': return [ ...(node.identifier ? [`${node.identifier}`] : []), diff --git a/src/visitors/identityVisitor.ts b/src/visitors/identityVisitor.ts index af4ef64ee..8cce3f6f4 100644 --- a/src/visitors/identityVisitor.ts +++ b/src/visitors/identityVisitor.ts @@ -19,6 +19,7 @@ import { booleanTypeNode, conditionalValueNode, constantPdaSeedNode, + constantValueNode, dateTimeTypeNode, definedTypeNode, enumEmptyVariantTypeNode, @@ -417,6 +418,18 @@ export function identityVisitor( }; } + if (castedNodeKeys.includes('constantValueNode')) { + visitor.visitConstantValue = function visitConstantValue(node) { + const type = visit(this)(node.type); + if (type === null) return null; + assertIsNode(type, TYPE_NODES); + const value = visit(this)(node.value); + if (value === null) return null; + assertIsNode(value, VALUE_NODES); + return constantValueNode(type, value); + }; + } + if (castedNodeKeys.includes('enumValueNode')) { visitor.visitEnumValue = function visitEnumValue(node) { const enumLink = visit(this)(node.enum); diff --git a/src/visitors/mergeVisitor.ts b/src/visitors/mergeVisitor.ts index d0bff930e..d636517db 100644 --- a/src/visitors/mergeVisitor.ts +++ b/src/visitors/mergeVisitor.ts @@ -219,6 +219,15 @@ export function mergeVisitor( }; } + if (castedNodeKeys.includes('constantValueNode')) { + visitor.visitConstantValue = function visitConstantValue(node) { + return merge(node, [ + ...visit(this)(node.type), + ...visit(this)(node.value), + ]); + }; + } + if (castedNodeKeys.includes('enumValueNode')) { visitor.visitEnumValue = function visitEnumValue(node) { return merge(node, [ diff --git a/test/visitors/nodes/valueNodes/BytesValueNode.test.ts b/test/visitors/nodes/valueNodes/BytesValueNode.test.ts new file mode 100644 index 000000000..9046d19b0 --- /dev/null +++ b/test/visitors/nodes/valueNodes/BytesValueNode.test.ts @@ -0,0 +1,19 @@ +import test from 'ava'; +import { bytesValueNode } from '../../../../src'; +import { + deleteNodesVisitorMacro, + getDebugStringVisitorMacro, + identityVisitorMacro, + mergeVisitorMacro, +} from '../_setup'; + +const node = bytesValueNode('base64', 'SGVsbG8gV29ybGQ='); + +test(mergeVisitorMacro, node, 1); +test(identityVisitorMacro, node); +test(deleteNodesVisitorMacro, node, '[bytesValueNode]', null); +test( + getDebugStringVisitorMacro, + node, + `bytesValueNode [base64.SGVsbG8gV29ybGQ=]` +); diff --git a/test/visitors/nodes/valueNodes/ConstantValueNode.test.ts b/test/visitors/nodes/valueNodes/ConstantValueNode.test.ts new file mode 100644 index 000000000..a9ab4c51e --- /dev/null +++ b/test/visitors/nodes/valueNodes/ConstantValueNode.test.ts @@ -0,0 +1,28 @@ +import test from 'ava'; +import { + constantValueNode, + numberTypeNode, + numberValueNode, +} from '../../../../src'; +import { + deleteNodesVisitorMacro, + getDebugStringVisitorMacro, + identityVisitorMacro, + mergeVisitorMacro, +} from '../_setup'; + +const node = constantValueNode(numberTypeNode('u8'), numberValueNode(42)); + +test(mergeVisitorMacro, node, 3); +test(identityVisitorMacro, node); +test(deleteNodesVisitorMacro, node, '[constantValueNode]', null); +test(deleteNodesVisitorMacro, node, '[numberTypeNode]', null); +test(deleteNodesVisitorMacro, node, '[numberValueNode]', null); +test( + getDebugStringVisitorMacro, + node, + ` +constantValueNode +| numberTypeNode [u8] +| numberValueNode [42]` +);