Skip to content

Commit ce131ec

Browse files
committed
Split enumDefault into scalar and data enums
1 parent 346048e commit ce131ec

File tree

3 files changed

+94
-27
lines changed

3 files changed

+94
-27
lines changed

packages/renderers-rust/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ The Rust renderer provides sensible default traits when generating the various R
5757
Using the `traitOptions` attribute, you may configure the default traits that will be applied to every Rust type. These default traits can be configured using 4 different attributes:
5858

5959
- `baseDefaults`: The default traits to implement for all types.
60-
- `enumDefaults`: The default traits to implement for all enum types, in addition to the `baseDefaults` traits.
60+
- `dataEnumDefaults`: The default traits to implement for all data enum types, in addition to the `baseDefaults` traits. Data enums are enums with at least one non-unit variant — e.g. `pub enum Command { Write(String), Quit }`.
61+
- `scalarEnumDefaults`: The default traits to implement for all scalar enum types, in addition to the `baseDefaults` traits. Scalar enums are enums with unit variants only — e.g. `pub enum Feedback { Good, Bad }`.
6162
- `structDefaults`: The default traits to implement for all struct types, in addition to the `baseDefaults` traits.
6263
- `aliasDefaults`: The default traits to implement for all type aliases, in addition to the `baseDefaults` traits.
6364

@@ -67,7 +68,8 @@ Note that you must provide the fully qualified name of the traits you provide (e
6768
const traitOptions = {
6869
aliasDefaults: [],
6970
baseDefaults: ['borsh::BorshSerialize', 'borsh::BorshDeserialize', 'Clone', 'Debug', 'Eq', 'PartialEq'],
70-
enumDefaults: ['Copy', 'PartialOrd', 'Hash', 'num_derive::FromPrimitive'],
71+
dataEnumDefaults: [],
72+
scalarEnumDefaults: ['Copy', 'PartialOrd', 'Hash', 'num_derive::FromPrimitive'],
7173
structDefaults: ['serde::Serialize', 'serde::Deserialize'],
7274
};
7375
```

packages/renderers-rust/src/utils/traitOptions.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AccountNode, assertIsNode, camelCase, DefinedTypeNode, isNode } from '@codama/nodes';
1+
import { AccountNode, assertIsNode, camelCase, DefinedTypeNode, isNode, isScalarEnum } from '@codama/nodes';
22

33
import { ImportMap } from '../ImportMap';
44

@@ -7,8 +7,11 @@ export type TraitOptions = {
77
aliasDefaults?: string[];
88
/** The default traits to implement for all types. */
99
baseDefaults?: string[];
10-
/** The default traits to implement for enums only — on top of the base defaults. */
11-
enumDefaults?: string[];
10+
/**
11+
* The default traits to implement for data enums only — on top of the base defaults.
12+
* Data enums are enums with at least one non-unit variant.
13+
*/
14+
dataEnumDefaults?: string[];
1215
/**
1316
* The mapping of feature flags to traits.
1417
* For each entry, the traits will be rendered within a
@@ -17,6 +20,11 @@ export type TraitOptions = {
1720
featureFlags?: Record<string, string[]>;
1821
/** The complete trait overrides of specific types. */
1922
overrides?: Record<string, string[]>;
23+
/**
24+
* The default traits to implement for scalar enums only — on top of the base defaults.
25+
* Scalar enums are enums with no variants or only unit variants.
26+
*/
27+
scalarEnumDefaults?: string[];
2028
/** The default traits to implement for structs only — on top of the base defaults. */
2129
structDefaults?: string[];
2230
/** Whether or not to use the fully qualified name for traits, instead of importing them. */
@@ -26,9 +34,10 @@ export type TraitOptions = {
2634
export const DEFAULT_TRAIT_OPTIONS: Required<TraitOptions> = {
2735
aliasDefaults: [],
2836
baseDefaults: ['borsh::BorshSerialize', 'borsh::BorshDeserialize', 'Clone', 'Debug', 'Eq', 'PartialEq'],
29-
enumDefaults: ['Copy', 'PartialOrd', 'Hash', 'num_derive::FromPrimitive'],
37+
dataEnumDefaults: [],
3038
featureFlags: { serde: ['serde::Serialize', 'serde::Deserialize'] },
3139
overrides: {},
40+
scalarEnumDefaults: ['Copy', 'PartialOrd', 'Hash', 'num_derive::FromPrimitive'],
3241
structDefaults: ['serde::Serialize', 'serde::Deserialize'],
3342
useFullyQualifiedName: false,
3443
};
@@ -79,22 +88,29 @@ export function getTraitsFromNode(
7988
return { imports, render: traitLines.join('') };
8089
}
8190

82-
function getNodeType(node: AccountNode | DefinedTypeNode): 'alias' | 'enum' | 'struct' {
91+
function getNodeType(node: AccountNode | DefinedTypeNode): 'alias' | 'dataEnum' | 'scalarEnum' | 'struct' {
8392
if (isNode(node, 'accountNode')) return 'struct';
8493
if (isNode(node.type, 'structTypeNode')) return 'struct';
85-
if (isNode(node.type, 'enumTypeNode')) return 'enum';
94+
if (isNode(node.type, 'enumTypeNode')) {
95+
return isScalarEnum(node.type) ? 'scalarEnum' : 'dataEnum';
96+
}
8697
return 'alias';
8798
}
8899

89100
function getDefaultTraits(
90-
nodeType: 'alias' | 'enum' | 'struct',
91-
options: Pick<Required<TraitOptions>, 'aliasDefaults' | 'baseDefaults' | 'enumDefaults' | 'structDefaults'>,
101+
nodeType: 'alias' | 'dataEnum' | 'scalarEnum' | 'struct',
102+
options: Pick<
103+
Required<TraitOptions>,
104+
'aliasDefaults' | 'baseDefaults' | 'dataEnumDefaults' | 'scalarEnumDefaults' | 'structDefaults'
105+
>,
92106
): string[] {
93107
switch (nodeType) {
94108
case 'alias':
95109
return [...options.baseDefaults, ...options.aliasDefaults];
96-
case 'enum':
97-
return [...options.baseDefaults, ...options.enumDefaults];
110+
case 'dataEnum':
111+
return [...options.baseDefaults, ...options.dataEnumDefaults];
112+
case 'scalarEnum':
113+
return [...options.baseDefaults, ...options.scalarEnumDefaults];
98114
case 'struct':
99115
default:
100116
return [...options.baseDefaults, ...options.structDefaults];

packages/renderers-rust/test/utils/traitOptions.test.ts

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
accountNode,
33
definedTypeNode,
44
enumEmptyVariantTypeNode,
5+
enumStructVariantTypeNode,
56
enumTypeNode,
67
numberTypeNode,
78
structFieldTypeNode,
@@ -12,7 +13,30 @@ import { describe, expect, test } from 'vitest';
1213
import { getTraitsFromNode, TraitOptions } from '../../src/utils';
1314

1415
describe('default values', () => {
15-
test('it defaults to a set of traits for enums', () => {
16+
test('it defaults to a set of traits for data enums', () => {
17+
// Given a data enum defined type.
18+
const node = definedTypeNode({
19+
name: 'Command',
20+
type: enumTypeNode([
21+
enumStructVariantTypeNode(
22+
'Play',
23+
structTypeNode([structFieldTypeNode({ name: 'guess', type: numberTypeNode('u16') })]),
24+
),
25+
enumEmptyVariantTypeNode('Quit'),
26+
]),
27+
});
28+
29+
// When we get the traits from the node using the default options.
30+
const { render, imports } = getTraitsFromNode(node);
31+
32+
// Then we expect the following traits to be rendered.
33+
expect(render).toBe(`#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)]\n`);
34+
35+
// And the following imports to be used.
36+
expect([...imports.imports]).toStrictEqual(['borsh::BorshSerialize', 'borsh::BorshDeserialize']);
37+
});
38+
39+
test('it defaults to a set of traits for scalar enums', () => {
1640
// Given a scalar enum defined type.
1741
const node = definedTypeNode({
1842
name: 'Feedback',
@@ -124,30 +148,55 @@ describe('default values', () => {
124148
const RESET_OPTIONS: Required<TraitOptions> = {
125149
aliasDefaults: [],
126150
baseDefaults: [],
127-
enumDefaults: [],
151+
dataEnumDefaults: [],
128152
featureFlags: {},
129153
overrides: {},
154+
scalarEnumDefaults: [],
130155
structDefaults: [],
131156
useFullyQualifiedName: false,
132157
};
133158

134159
describe('base traits', () => {
135-
test('it uses both the base and enum traits', () => {
160+
test('it uses both the base and data enum traits', () => {
161+
// Given a data enum defined type.
162+
const node = definedTypeNode({
163+
name: 'Command',
164+
type: enumTypeNode([
165+
enumStructVariantTypeNode(
166+
'Play',
167+
structTypeNode([structFieldTypeNode({ name: 'guess', type: numberTypeNode('u16') })]),
168+
),
169+
enumEmptyVariantTypeNode('Quit'),
170+
]),
171+
});
172+
173+
// When we get the traits from the node using custom base and data enum defaults.
174+
const { render } = getTraitsFromNode(node, {
175+
...RESET_OPTIONS,
176+
baseDefaults: ['MyBaseTrait'],
177+
dataEnumDefaults: ['MyDataEnumTrait'],
178+
});
179+
180+
// Then we expect both the base and data enum traits to be rendered.
181+
expect(render).toBe(`#[derive(MyBaseTrait, MyDataEnumTrait)]\n`);
182+
});
183+
184+
test('it uses both the base and scalar enum traits', () => {
136185
// Given a scalar enum defined type.
137186
const node = definedTypeNode({
138187
name: 'Feedback',
139188
type: enumTypeNode([enumEmptyVariantTypeNode('Good'), enumEmptyVariantTypeNode('Bad')]),
140189
});
141190

142-
// When we get the traits from the node using custom base and enum defaults.
191+
// When we get the traits from the node using custom base and scalar enum defaults.
143192
const { render } = getTraitsFromNode(node, {
144193
...RESET_OPTIONS,
145194
baseDefaults: ['MyBaseTrait'],
146-
enumDefaults: ['MyEnumTrait'],
195+
scalarEnumDefaults: ['MyScalarEnumTrait'],
147196
});
148197

149-
// Then we expect both the base and enum traits to be rendered.
150-
expect(render).toBe(`#[derive(MyBaseTrait, MyEnumTrait)]\n`);
198+
// Then we expect both the base and scalar enum traits to be rendered.
199+
expect(render).toBe(`#[derive(MyBaseTrait, MyScalarEnumTrait)]\n`);
151200
});
152201

153202
test('it uses both the base and struct traits', () => {
@@ -197,23 +246,23 @@ describe('base traits', () => {
197246
});
198247

199248
// When we get the traits from the node such that:
200-
// - We provide custom base and enum defaults.
249+
// - We provide custom base and scalar enum defaults.
201250
// - We provide custom feature flags for traits in both categories.
202251
const { render } = getTraitsFromNode(node, {
203252
...RESET_OPTIONS,
204253
baseDefaults: ['MyBaseTrait', 'MyNonFeatureTrait'],
205-
enumDefaults: ['MyEnumTrait'],
206254
featureFlags: {
207255
base: ['MyBaseTrait'],
208-
enum: ['MyEnumTrait'],
256+
enum: ['MyScalarEnumTrait'],
209257
},
258+
scalarEnumDefaults: ['MyScalarEnumTrait'],
210259
});
211260

212261
// Then we expect both the base and enum traits to be rendered as separate feature flags.
213262
expect(render).toBe(
214263
`#[derive(MyNonFeatureTrait)]\n` +
215264
`#[cfg(feature = "base", derive(MyBaseTrait))]\n` +
216-
`#[cfg(feature = "enum", derive(MyEnumTrait))]\n`,
265+
`#[cfg(feature = "enum", derive(MyScalarEnumTrait))]\n`,
217266
);
218267
});
219268

@@ -229,16 +278,16 @@ describe('base traits', () => {
229278
const { render } = getTraitsFromNode(node, {
230279
...RESET_OPTIONS,
231280
baseDefaults: ['MyBaseTrait'],
232-
enumDefaults: ['MyEnumTrait'],
233281
featureFlags: {
234282
base: ['MyBaseTrait'],
235-
enum: ['MyEnumTrait'],
283+
enum: ['MyScalarEnumTrait'],
236284
},
285+
scalarEnumDefaults: ['MyScalarEnumTrait'],
237286
});
238287

239288
// Then we expect the following traits to be rendered.
240289
expect(render).toBe(
241-
`#[cfg(feature = "base", derive(MyBaseTrait))]\n#[cfg(feature = "enum", derive(MyEnumTrait))]\n`,
290+
`#[cfg(feature = "base", derive(MyBaseTrait))]\n#[cfg(feature = "enum", derive(MyScalarEnumTrait))]\n`,
242291
);
243292
});
244293
});
@@ -257,8 +306,8 @@ describe('overridden traits', () => {
257306
const { render } = getTraitsFromNode(node, {
258307
...RESET_OPTIONS,
259308
baseDefaults: ['MyBaseTrait'],
260-
enumDefaults: ['MyEnumTrait'],
261309
overrides: { feedback: ['MyFeedbackTrait'] },
310+
scalarEnumDefaults: ['MyScalarEnumTrait'],
262311
});
263312

264313
// Then we expect only the feedback traits to be rendered.

0 commit comments

Comments
 (0)