Skip to content

Commit

Permalink
Add BytesValueNode and ConstantValueNode (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
lorisleiva authored Apr 9, 2024
1 parent 8eaf605 commit f74707d
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/tough-planes-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@metaplex-foundation/kinobi": minor
---

Add BytesValueNode and ConstantValueNode
37 changes: 37 additions & 0 deletions src/nodes/valueNodes/BytesValueNode.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
38 changes: 38 additions & 0 deletions src/nodes/valueNodes/ConstantValueNode.ts
Original file line number Diff line number Diff line change
@@ -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<TType, TValue> {
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));
}
6 changes: 6 additions & 0 deletions src/nodes/valueNodes/ValueNode.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -16,7 +18,9 @@ import type { TupleValueNode } from './TupleValueNode';
// Standalone Value Node Registration.
export type StandaloneValueNode =
| ArrayValueNode
| BytesValueNode
| BooleanValueNode
| ConstantValueNode
| EnumValueNode
| MapValueNode
| NoneValueNode
Expand All @@ -29,7 +33,9 @@ export type StandaloneValueNode =
| StringValueNode;
export const STANDALONE_VALUE_NODE_KINDS = [
'arrayValueNode',
'bytesValueNode',
'booleanValueNode',
'constantValueNode',
'enumValueNode',
'mapValueNode',
'noneValueNode',
Expand Down
2 changes: 2 additions & 0 deletions src/nodes/valueNodes/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
14 changes: 13 additions & 1 deletion src/renderers/js-experimental/renderValueNodeVisitor.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
Expand Down
17 changes: 16 additions & 1 deletion src/renderers/js/renderValueNodeVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { RegisteredValueNode, isNode, isScalarEnum } from '../../nodes';
import {
RegisteredValueNode,
getBytesFromBytesValueNode,
isNode,
isScalarEnum,
} from '../../nodes';
import {
LinkableDictionary,
MainCaseString,
Expand Down Expand Up @@ -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);
Expand Down
16 changes: 15 additions & 1 deletion src/renderers/rust/renderValueNodeVisitor.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/visitors/getDebugStringVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`] : []),
Expand Down
13 changes: 13 additions & 0 deletions src/visitors/identityVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
booleanTypeNode,
conditionalValueNode,
constantPdaSeedNode,
constantValueNode,
dateTimeTypeNode,
definedTypeNode,
enumEmptyVariantTypeNode,
Expand Down Expand Up @@ -417,6 +418,18 @@ export function identityVisitor<TNodeKind extends NodeKind = NodeKind>(
};
}

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);
Expand Down
9 changes: 9 additions & 0 deletions src/visitors/mergeVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@ export function mergeVisitor<TReturn, TNodeKind extends NodeKind = NodeKind>(
};
}

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, [
Expand Down
19 changes: 19 additions & 0 deletions test/visitors/nodes/valueNodes/BytesValueNode.test.ts
Original file line number Diff line number Diff line change
@@ -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=]`
);
28 changes: 28 additions & 0 deletions test/visitors/nodes/valueNodes/ConstantValueNode.test.ts
Original file line number Diff line number Diff line change
@@ -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]`
);

0 comments on commit f74707d

Please sign in to comment.