@codama/renderers-rust 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -9
- package/dist/index.node.cjs +116 -35
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +117 -36
- package/dist/index.node.mjs.map +1 -1
- package/dist/templates/instructionsCpiPage.njk +2 -2
- package/dist/templates/instructionsPage.njk +1 -1
- package/dist/types/getRenderMapVisitor.d.ts +4 -2
- package/dist/types/getRenderMapVisitor.d.ts.map +1 -1
- package/dist/types/getTypeManifestVisitor.d.ts +3 -2
- package/dist/types/getTypeManifestVisitor.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/traitOptions.d.ts +39 -0
- package/dist/types/utils/traitOptions.d.ts.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,12 +37,114 @@ codama.accept(renderVisitor(pathToGeneratedFolder, options));
|
|
|
37
37
|
|
|
38
38
|
The `renderVisitor` accepts the following options.
|
|
39
39
|
|
|
40
|
-
| Name | Type | Default
|
|
41
|
-
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
42
|
-
| `deleteFolderBeforeRendering` | `boolean` | `true`
|
|
43
|
-
| `formatCode` | `boolean` | `false`
|
|
44
|
-
| `toolchain` | `string` | `"+stable"`
|
|
45
|
-
| `crateFolder` | `string` | none
|
|
46
|
-
| `linkOverrides` | `Record<'accounts' \| 'definedTypes' \| 'instructions' \| 'pdas' \| 'programs' \| 'resolvers', Record<string, string>>` | `{}`
|
|
47
|
-
| `dependencyMap` | `Record<string, string>` | `{}`
|
|
48
|
-
| `renderParentInstructions` | `boolean` | `false`
|
|
40
|
+
| Name | Type | Default | Description |
|
|
41
|
+
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
42
|
+
| `deleteFolderBeforeRendering` | `boolean` | `true` | Whether the base directory should be cleaned before generating new files. |
|
|
43
|
+
| `formatCode` | `boolean` | `false` | Whether we should use `cargo fmt` to format the generated code. When set to `true`, the `crateFolder` option must be provided. |
|
|
44
|
+
| `toolchain` | `string` | `"+stable"` | The toolchain to use when formatting the generated code. |
|
|
45
|
+
| `crateFolder` | `string` | none | The path to the root folder of the Rust crate. This option is required when `formatCode` is set to `true`. |
|
|
46
|
+
| `linkOverrides` | `Record<'accounts' \| 'definedTypes' \| 'instructions' \| 'pdas' \| 'programs' \| 'resolvers', Record<string, string>>` | `{}` | A object that overrides the import path of link nodes. For instance, `{ definedTypes: { counter: 'hooked' } }` uses the `hooked` folder to import any link node referring to the `counter` type. |
|
|
47
|
+
| `dependencyMap` | `Record<string, string>` | `{}` | A mapping between import aliases and their actual crate name or path in Rust. |
|
|
48
|
+
| `renderParentInstructions` | `boolean` | `false` | When using nested instructions, whether the parent instructions should also be rendered. When set to `false` (default), only the instruction leaves are being rendered. |
|
|
49
|
+
| `traitOptions` | [`TraitOptions`](#trait-options) | `DEFAULT_TRAIT_OPTIONS` | A set of options that can be used to configure how traits are rendered for every Rust types. See [documentation below](#trait-options) for more information. |
|
|
50
|
+
|
|
51
|
+
## Trait Options
|
|
52
|
+
|
|
53
|
+
The Rust renderer provides sensible default traits when generating the various Rust types you client will use. However, you may wish to configure these traits to better suit your needs. The `traitOptions` attribute is here to help you with that. Let's see the various settings it provides.
|
|
54
|
+
|
|
55
|
+
### Default traits
|
|
56
|
+
|
|
57
|
+
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:
|
|
58
|
+
|
|
59
|
+
- `baseDefaults`: The default traits to implement for all types.
|
|
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 }`.
|
|
62
|
+
- `structDefaults`: The default traits to implement for all struct types, in addition to the `baseDefaults` traits.
|
|
63
|
+
|
|
64
|
+
Note that you must provide the fully qualified name of the traits you provide (e.g. `serde::Serialize`). Here are the default values for these attributes:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const traitOptions = {
|
|
68
|
+
baseDefaults: [
|
|
69
|
+
'borsh::BorshSerialize',
|
|
70
|
+
'borsh::BorshDeserialize',
|
|
71
|
+
'serde::Serialize',
|
|
72
|
+
'serde::Deserialize',
|
|
73
|
+
'Clone',
|
|
74
|
+
'Debug',
|
|
75
|
+
'Eq',
|
|
76
|
+
'PartialEq',
|
|
77
|
+
],
|
|
78
|
+
dataEnumDefaults: [],
|
|
79
|
+
scalarEnumDefaults: ['Copy', 'PartialOrd', 'Hash', 'num_derive::FromPrimitive'],
|
|
80
|
+
structDefaults: [],
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Overridden traits
|
|
85
|
+
|
|
86
|
+
In addition to configure the default traits, you may also override the traits for specific types. This will completely replace the default traits for the given type. To do so, you may use the `overrides` attribute of the `traitOptions` object.
|
|
87
|
+
|
|
88
|
+
This attribute is a map where the keys are the names of the types you want to override, and the values are the traits you want to apply to these types. Here is an example:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const traitOptions = {
|
|
92
|
+
overrides: {
|
|
93
|
+
myCustomType: ['Clone', 'my::custom::Trait', 'my::custom::OtherTrait'],
|
|
94
|
+
myTypeWithNoTraits: [],
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Feature Flags
|
|
100
|
+
|
|
101
|
+
You may also configure which traits should be rendered under a feature flag by using the `featureFlags` attribute. This attribute is a map where the keys are feature flag names and the values are the traits that should be rendered under that feature flag. Here is an example:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const traitOptions = {
|
|
105
|
+
featureFlags: { fruits: ['fruits::Apple', 'fruits::Banana'] },
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Now, if at any point, we encounter a `fruits::Apple` or `fruits::Banana` trait to be rendered (either as default traits or as overridden traits), they will be rendered under the `fruits` feature flag. For instance:
|
|
110
|
+
|
|
111
|
+
```rust
|
|
112
|
+
#[cfg_attr(feature = "fruits", derive(fruits::Apple, fruits::Banana))]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
By default, the `featureFlags` option is set to the following:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const traitOptions = {
|
|
119
|
+
featureFlags: { serde: ['serde::Serialize', 'serde::Deserialize'] },
|
|
120
|
+
};
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Note that for feature flags to be effective, they must be added to the `Cargo.toml` file of the generated Rust client.
|
|
124
|
+
|
|
125
|
+
### Using the Fully Qualified Name
|
|
126
|
+
|
|
127
|
+
By default, all traits are imported using the provided Fully Qualified Name which means their short name will be used within the `derive` attributes.
|
|
128
|
+
|
|
129
|
+
However, you may want to avoid importing these traits and use the Fully Qualified Name directly in the generated code. To do so, you may use the `useFullyQualifiedName` attribute of the `traitOptions` object by setting it to `true`:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const traitOptions = {
|
|
133
|
+
useFullyQualifiedName: true,
|
|
134
|
+
};
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Here is an example of rendered traits with this option set to `true` and `false` (which is the default):
|
|
138
|
+
|
|
139
|
+
```rust
|
|
140
|
+
// With `useFullyQualifiedName` set to `false` (default).
|
|
141
|
+
use serde::Serialize;
|
|
142
|
+
use serde::Deserialize;
|
|
143
|
+
// ...
|
|
144
|
+
#[derive(Serialize, Deserialize)]
|
|
145
|
+
|
|
146
|
+
// With `useFullyQualifiedName` set to `true`.
|
|
147
|
+
#[derive(serde::Serialize, serde::Deserialize)]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Note that any trait rendered under a feature flag will always use the Fully Qualified Name in order to ensure we only reference the trait when the feature is enabled.
|
package/dist/index.node.cjs
CHANGED
|
@@ -155,10 +155,109 @@ var render = (template, context, options) => {
|
|
|
155
155
|
env.addFilter("rustDocblock", rustDocblock);
|
|
156
156
|
return env.render(template, context);
|
|
157
157
|
};
|
|
158
|
+
var DEFAULT_TRAIT_OPTIONS = {
|
|
159
|
+
baseDefaults: [
|
|
160
|
+
"borsh::BorshSerialize",
|
|
161
|
+
"borsh::BorshDeserialize",
|
|
162
|
+
"serde::Serialize",
|
|
163
|
+
"serde::Deserialize",
|
|
164
|
+
"Clone",
|
|
165
|
+
"Debug",
|
|
166
|
+
"Eq",
|
|
167
|
+
"PartialEq"
|
|
168
|
+
],
|
|
169
|
+
dataEnumDefaults: [],
|
|
170
|
+
featureFlags: { serde: ["serde::Serialize", "serde::Deserialize"] },
|
|
171
|
+
overrides: {},
|
|
172
|
+
scalarEnumDefaults: ["Copy", "PartialOrd", "Hash", "num_derive::FromPrimitive"],
|
|
173
|
+
structDefaults: [],
|
|
174
|
+
useFullyQualifiedName: false
|
|
175
|
+
};
|
|
176
|
+
function getTraitsFromNodeFactory(options = {}) {
|
|
177
|
+
return (node) => getTraitsFromNode(node, options);
|
|
178
|
+
}
|
|
179
|
+
function getTraitsFromNode(node, userOptions = {}) {
|
|
180
|
+
nodes.assertIsNode(node, ["accountNode", "definedTypeNode"]);
|
|
181
|
+
const options = { ...DEFAULT_TRAIT_OPTIONS, ...userOptions };
|
|
182
|
+
const nodeType = getNodeType(node);
|
|
183
|
+
if (nodeType === "alias") {
|
|
184
|
+
return { imports: new ImportMap(), render: "" };
|
|
185
|
+
}
|
|
186
|
+
const sanitizedOverrides = Object.fromEntries(
|
|
187
|
+
Object.entries(options.overrides).map(([key, value]) => [nodes.camelCase(key), value])
|
|
188
|
+
);
|
|
189
|
+
const nodeOverrides = sanitizedOverrides[node.name];
|
|
190
|
+
const allTraits = nodeOverrides === void 0 ? getDefaultTraits(nodeType, options) : nodeOverrides;
|
|
191
|
+
const partitionedTraits = partitionTraitsInFeatures(allTraits, options.featureFlags);
|
|
192
|
+
let unfeaturedTraits = partitionedTraits[0];
|
|
193
|
+
const featuredTraits = partitionedTraits[1];
|
|
194
|
+
const imports = new ImportMap();
|
|
195
|
+
if (!options.useFullyQualifiedName) {
|
|
196
|
+
unfeaturedTraits = extractFullyQualifiedNames(unfeaturedTraits, imports);
|
|
197
|
+
}
|
|
198
|
+
const traitLines = [
|
|
199
|
+
...unfeaturedTraits.length > 0 ? [`#[derive(${unfeaturedTraits.join(", ")})]
|
|
200
|
+
`] : [],
|
|
201
|
+
...Object.entries(featuredTraits).map(([feature, traits]) => {
|
|
202
|
+
return `#[cfg_attr(feature = "${feature}", derive(${traits.join(", ")}))]
|
|
203
|
+
`;
|
|
204
|
+
})
|
|
205
|
+
];
|
|
206
|
+
return { imports, render: traitLines.join("") };
|
|
207
|
+
}
|
|
208
|
+
function getNodeType(node) {
|
|
209
|
+
if (nodes.isNode(node, "accountNode")) return "struct";
|
|
210
|
+
if (nodes.isNode(node.type, "structTypeNode")) return "struct";
|
|
211
|
+
if (nodes.isNode(node.type, "enumTypeNode")) {
|
|
212
|
+
return nodes.isScalarEnum(node.type) ? "scalarEnum" : "dataEnum";
|
|
213
|
+
}
|
|
214
|
+
return "alias";
|
|
215
|
+
}
|
|
216
|
+
function getDefaultTraits(nodeType, options) {
|
|
217
|
+
switch (nodeType) {
|
|
218
|
+
case "dataEnum":
|
|
219
|
+
return [...options.baseDefaults, ...options.dataEnumDefaults];
|
|
220
|
+
case "scalarEnum":
|
|
221
|
+
return [...options.baseDefaults, ...options.scalarEnumDefaults];
|
|
222
|
+
case "struct":
|
|
223
|
+
return [...options.baseDefaults, ...options.structDefaults];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function partitionTraitsInFeatures(traits, featureFlags) {
|
|
227
|
+
const reverseFeatureFlags = Object.entries(featureFlags).reduce(
|
|
228
|
+
(acc, [feature, traits2]) => {
|
|
229
|
+
for (const trait of traits2) {
|
|
230
|
+
if (!acc[trait]) acc[trait] = feature;
|
|
231
|
+
}
|
|
232
|
+
return acc;
|
|
233
|
+
},
|
|
234
|
+
{}
|
|
235
|
+
);
|
|
236
|
+
const unfeaturedTraits = [];
|
|
237
|
+
const featuredTraits = {};
|
|
238
|
+
for (const trait of traits) {
|
|
239
|
+
const feature = reverseFeatureFlags[trait];
|
|
240
|
+
if (feature === void 0) {
|
|
241
|
+
unfeaturedTraits.push(trait);
|
|
242
|
+
} else {
|
|
243
|
+
if (!featuredTraits[feature]) featuredTraits[feature] = [];
|
|
244
|
+
featuredTraits[feature].push(trait);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return [unfeaturedTraits, featuredTraits];
|
|
248
|
+
}
|
|
249
|
+
function extractFullyQualifiedNames(traits, imports) {
|
|
250
|
+
return traits.map((trait) => {
|
|
251
|
+
const index = trait.lastIndexOf("::");
|
|
252
|
+
if (index === -1) return trait;
|
|
253
|
+
imports.add(trait);
|
|
254
|
+
return trait.slice(index + 2);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
158
257
|
|
|
159
258
|
// src/getTypeManifestVisitor.ts
|
|
160
259
|
function getTypeManifestVisitor(options) {
|
|
161
|
-
const { getImportFrom } = options;
|
|
260
|
+
const { getImportFrom, getTraitsFromNode: getTraitsFromNode2 } = options;
|
|
162
261
|
let parentName = options.parentName ?? null;
|
|
163
262
|
let nestedStruct = options.nestedStruct ?? false;
|
|
164
263
|
let inlineStruct = false;
|
|
@@ -176,13 +275,12 @@ function getTypeManifestVisitor(options) {
|
|
|
176
275
|
visitAccount(account, { self }) {
|
|
177
276
|
parentName = nodes.pascalCase(account.name);
|
|
178
277
|
const manifest = visitorsCore.visit(account.data, self);
|
|
179
|
-
|
|
278
|
+
const traits = getTraitsFromNode2(account);
|
|
279
|
+
manifest.imports.mergeWith(traits.imports);
|
|
180
280
|
parentName = null;
|
|
181
281
|
return {
|
|
182
282
|
...manifest,
|
|
183
|
-
type:
|
|
184
|
-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
185
|
-
${manifest.type}`
|
|
283
|
+
type: traits.render + manifest.type
|
|
186
284
|
};
|
|
187
285
|
},
|
|
188
286
|
visitArrayType(arrayType, { self }) {
|
|
@@ -255,35 +353,11 @@ ${manifest.type}`
|
|
|
255
353
|
visitDefinedType(definedType, { self }) {
|
|
256
354
|
parentName = nodes.pascalCase(definedType.name);
|
|
257
355
|
const manifest = visitorsCore.visit(definedType.type, self);
|
|
356
|
+
const traits = getTraitsFromNode2(definedType);
|
|
357
|
+
manifest.imports.mergeWith(traits.imports);
|
|
258
358
|
parentName = null;
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
traits.push("Copy", "PartialOrd", "Hash", "FromPrimitive");
|
|
262
|
-
manifest.imports.add(["num_derive::FromPrimitive"]);
|
|
263
|
-
}
|
|
264
|
-
const nestedStructs = manifest.nestedStructs.map(
|
|
265
|
-
(struct) => `#[derive(${traits.join(", ")})]
|
|
266
|
-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
267
|
-
${struct}`
|
|
268
|
-
);
|
|
269
|
-
if (!nodes.isNode(definedType.type, ["enumTypeNode", "structTypeNode"])) {
|
|
270
|
-
if (nestedStructs.length > 0) {
|
|
271
|
-
manifest.imports.add(["borsh::BorshSerialize", "borsh::BorshDeserialize"]);
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
...manifest,
|
|
275
|
-
nestedStructs,
|
|
276
|
-
type: `pub type ${nodes.pascalCase(definedType.name)} = ${manifest.type}`
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
manifest.imports.add(["borsh::BorshSerialize", "borsh::BorshDeserialize"]);
|
|
280
|
-
return {
|
|
281
|
-
...manifest,
|
|
282
|
-
nestedStructs,
|
|
283
|
-
type: `#[derive(${traits.join(", ")})]
|
|
284
|
-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
285
|
-
${manifest.type}`
|
|
286
|
-
};
|
|
359
|
+
const renderedType = nodes.isNode(definedType.type, ["enumTypeNode", "structTypeNode"]) ? manifest.type : `pub type ${nodes.pascalCase(definedType.name)} = ${manifest.type};`;
|
|
360
|
+
return { ...manifest, type: `${traits.render}${renderedType}` };
|
|
287
361
|
},
|
|
288
362
|
visitDefinedTypeLink(node) {
|
|
289
363
|
const pascalCaseDefinedType = nodes.pascalCase(node.name);
|
|
@@ -499,11 +573,15 @@ ${variantNames}
|
|
|
499
573
|
const fieldTypes = fields.map((field) => field.type).join("\n");
|
|
500
574
|
const mergedManifest = mergeManifests(fields);
|
|
501
575
|
if (nestedStruct) {
|
|
576
|
+
const nestedTraits = getTraitsFromNode2(
|
|
577
|
+
nodes.definedTypeNode({ name: originalParentName, type: structType })
|
|
578
|
+
);
|
|
579
|
+
mergedManifest.imports.mergeWith(nestedTraits.imports);
|
|
502
580
|
return {
|
|
503
581
|
...mergedManifest,
|
|
504
582
|
nestedStructs: [
|
|
505
583
|
...mergedManifest.nestedStructs,
|
|
506
|
-
|
|
584
|
+
`${nestedTraits.render}pub struct ${nodes.pascalCase(originalParentName)} {
|
|
507
585
|
${fieldTypes}
|
|
508
586
|
}`
|
|
509
587
|
],
|
|
@@ -683,7 +761,8 @@ function getRenderMapVisitor(options = {}) {
|
|
|
683
761
|
const renderParentInstructions = options.renderParentInstructions ?? false;
|
|
684
762
|
const dependencyMap = options.dependencyMap ?? {};
|
|
685
763
|
const getImportFrom = getImportFromFactory(options.linkOverrides ?? {});
|
|
686
|
-
const
|
|
764
|
+
const getTraitsFromNode2 = getTraitsFromNodeFactory(options.traitOptions);
|
|
765
|
+
const typeManifestVisitor = getTypeManifestVisitor({ getImportFrom, getTraitsFromNode: getTraitsFromNode2 });
|
|
687
766
|
return visitorsCore.pipe(
|
|
688
767
|
visitorsCore.staticVisitor(
|
|
689
768
|
() => new renderersCore.RenderMap(),
|
|
@@ -758,6 +837,7 @@ function getRenderMapVisitor(options = {}) {
|
|
|
758
837
|
node.arguments.forEach((argument) => {
|
|
759
838
|
const argumentVisitor = getTypeManifestVisitor({
|
|
760
839
|
getImportFrom,
|
|
840
|
+
getTraitsFromNode: getTraitsFromNode2,
|
|
761
841
|
nestedStruct: true,
|
|
762
842
|
parentName: `${nodes.pascalCase(node.name)}InstructionData`
|
|
763
843
|
});
|
|
@@ -789,6 +869,7 @@ function getRenderMapVisitor(options = {}) {
|
|
|
789
869
|
const struct = nodes.structTypeNodeFromInstructionArgumentNodes(node.arguments);
|
|
790
870
|
const structVisitor = getTypeManifestVisitor({
|
|
791
871
|
getImportFrom,
|
|
872
|
+
getTraitsFromNode: getTraitsFromNode2,
|
|
792
873
|
parentName: `${nodes.pascalCase(node.name)}InstructionData`
|
|
793
874
|
});
|
|
794
875
|
const typeManifest = visitorsCore.visit(struct, structVisitor);
|