Skip to content

Commit 055c139

Browse files
committed
feat(gen): implicits
1 parent 035a804 commit 055c139

File tree

12 files changed

+371
-88
lines changed

12 files changed

+371
-88
lines changed

lib/gen/rust.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function generateRust (schema) {
3535
derive.push('Deserialize_tuple', 'Serialize_tuple')
3636
} else {
3737
// Default representation is map, add regular Serialize/Deserialize
38-
derive.push('Deserialize', 'Serialize')
38+
derive.push('Clone', 'Debug', 'PartialEq', 'Deserialize', 'Serialize')
3939
}
4040
/** @type { { [k in string]: string }[]} */
4141
let annotations = []
@@ -53,6 +53,9 @@ export function generateRust (schema) {
5353
}
5454
typesrc += `pub struct ${typeName} {\n`
5555

56+
// Collect implicit fields for generating default functions
57+
const implicitFields = []
58+
5659
for (let [fieldName, fieldDefn] of Object.entries(typeDefn.struct.fields)) {
5760
/** @type { { [k in string]: string }[]} */
5861
let annotations = []
@@ -141,7 +144,24 @@ export function generateRust (schema) {
141144
const isMapRepr = !typeDefn.struct.representation || 'map' in typeDefn.struct.representation
142145
const isTupleRepr = typeDefn.struct.representation && 'tuple' in typeDefn.struct.representation
143146

144-
if (fieldDefn.optional && fieldDefn.nullable) {
147+
// Check for implicit field value
148+
let hasImplicit = false
149+
let implicitValue = null
150+
const origFieldName = Object.keys(typeDefn.struct.fields)[Object.values(typeDefn.struct.fields).indexOf(fieldDefn)]
151+
if (isMapRepr && typeDefn.struct.representation && 'map' in typeDefn.struct.representation &&
152+
typeDefn.struct.representation.map.fields &&
153+
origFieldName in typeDefn.struct.representation.map.fields &&
154+
'implicit' in typeDefn.struct.representation.map.fields[origFieldName]) {
155+
hasImplicit = true
156+
implicitValue = typeDefn.struct.representation.map.fields[origFieldName].implicit
157+
}
158+
159+
if (hasImplicit) {
160+
// Field with implicit value
161+
const defaultFnName = `default_${typeName.toLowerCase()}_${fieldName.toLowerCase()}`
162+
typesrc += ` #[serde(default = "${defaultFnName}")]\n`
163+
implicitFields.push({ fieldName, fieldType, implicitValue, defaultFnName })
164+
} else if (fieldDefn.optional && fieldDefn.nullable) {
145165
// Optional + nullable: Option<Option<T>>
146166
fieldType = `Option<Option<${fieldType}>>`
147167
if (isMapRepr) {
@@ -166,6 +186,19 @@ export function generateRust (schema) {
166186
}
167187

168188
typesrc += '}\n\n'
189+
190+
// Generate default functions for implicit fields
191+
for (const { fieldType, implicitValue, defaultFnName } of implicitFields) {
192+
typesrc += `fn ${defaultFnName}() -> ${fieldType} {\n`
193+
if (typeof implicitValue === 'string') {
194+
typesrc += ` "${implicitValue}".to_string()\n`
195+
} else if (typeof implicitValue === 'boolean') {
196+
typesrc += ` ${implicitValue}\n`
197+
} else if (typeof implicitValue === 'number') {
198+
typesrc += ` ${implicitValue}\n`
199+
}
200+
typesrc += '}\n\n'
201+
}
169202
} else if ('list' in typeDefn) {
170203
// Handle top-level list type definitions
171204
if (typeof typeDefn.list.valueType !== 'string') {

lib/gen/typescript.js

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,18 @@ export function generateTypeScript (schema) {
3737
/** @type {string[]} */
3838
const fieldValidators = []
3939
let requiredFieldCount = 0
40+
const isMapRepr = !typeDefn.struct.representation || 'map' in typeDefn.struct.representation
4041
for (let [fieldName, fieldDefn] of Object.entries(typeDefn.struct.fields)) {
41-
if (!fieldDefn.optional) {
42+
// Check if field has implicit value
43+
let hasImplicit = false
44+
if (isMapRepr && typeDefn.struct.representation && 'map' in typeDefn.struct.representation &&
45+
typeDefn.struct.representation.map.fields &&
46+
fieldName in typeDefn.struct.representation.map.fields &&
47+
'implicit' in typeDefn.struct.representation.map.fields[fieldName]) {
48+
hasImplicit = true
49+
}
50+
51+
if (!fieldDefn.optional && !hasImplicit) {
4252
requiredFieldCount++
4353
}
4454
/** @type { { [k in string]: string }[]} */
@@ -118,12 +128,23 @@ export function generateTypeScript (schema) {
118128
}
119129
}
120130
fieldName = fixTypeScriptName(annotations, fieldName)
121-
// Handle optional and nullable fields
122-
if (fieldDefn.optional && fieldDefn.nullable) {
131+
132+
// Check for implicit field value (reuse hasImplicit from above)
133+
hasImplicit = false
134+
const origFieldName = Object.keys(typeDefn.struct.fields)[Object.values(typeDefn.struct.fields).indexOf(fieldDefn)]
135+
if (isMapRepr && typeDefn.struct.representation && 'map' in typeDefn.struct.representation &&
136+
typeDefn.struct.representation.map.fields &&
137+
origFieldName in typeDefn.struct.representation.map.fields &&
138+
'implicit' in typeDefn.struct.representation.map.fields[origFieldName]) {
139+
hasImplicit = true
140+
}
141+
142+
// Handle optional and nullable fields (implicit fields are also optional)
143+
if ((fieldDefn.optional || hasImplicit) && fieldDefn.nullable) {
123144
// Both optional and nullable
124145
typesrc += ` ${fieldName}?: ${fieldType} | null${linecomment}\n`
125-
} else if (fieldDefn.optional) {
126-
// Just optional
146+
} else if (fieldDefn.optional || hasImplicit) {
147+
// Just optional (or has implicit value)
127148
typesrc += ` ${fieldName}?: ${fieldType}${linecomment}\n`
128149
} else if (fieldDefn.nullable) {
129150
// Just nullable - field is required but can be null
@@ -132,7 +153,7 @@ export function generateTypeScript (schema) {
132153
// Required field
133154
typesrc += ` ${fieldName}: ${fieldType}${linecomment}\n`
134155
}
135-
const inCheck = !fieldDefn.optional ? `'${fieldName}' in value &&` : `!('${fieldName}' in value) ||`
156+
const inCheck = !fieldDefn.optional && !hasImplicit ? `'${fieldName}' in value &&` : `!('${fieldName}' in value) ||`
136157
if (isMap) {
137158
const kind = fixTypeScriptType(imports, '@ipld/schema/schema-schema.js#KindMap', false)
138159
let valueType = getTypeScriptType([], mapValueType)
@@ -157,7 +178,7 @@ export function generateTypeScript (schema) {
157178
fieldValidators.push(` (${inCheck} (${fieldDefn.nullable ? `value.${fieldName} === null || ` : ''}(Array.isArray(value.${fieldName}) && value.${fieldName}.every(${elementType}.is${elementType}))))`)
158179
}
159180
} else {
160-
fieldValidators.push(` (${inCheck} (${fieldDefn.nullable ? `value.${fieldName} === null || ` : ''}(${fieldType}.is${fieldType}(value.${fieldName}))))`)
181+
fieldValidators.push(` (${inCheck} ${fieldDefn.nullable ? `value.${fieldName} === null || ` : ''}(${fieldType}.is${fieldType}(value.${fieldName})))`)
161182
}
162183
}
163184

test/fixtures/gen/filecoin_miner_types.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,9 @@ export namespace RecoveryDeclaration {
211211
}
212212
const keyCount = Object.keys(value).length
213213
return keyCount === 3 &&
214-
('Deadline' in value && ((KindInt.isKindInt(value.Deadline)))) &&
215-
('Partition' in value && ((KindInt.isKindInt(value.Partition)))) &&
216-
('Sectors' in value && ((KindBytes.isKindBytes(value.Sectors))))
214+
('Deadline' in value && (KindInt.isKindInt(value.Deadline))) &&
215+
('Partition' in value && (KindInt.isKindInt(value.Partition))) &&
216+
('Sectors' in value && (KindBytes.isKindBytes(value.Sectors)))
217217
}
218218
}
219219

@@ -248,9 +248,9 @@ export namespace FaultDeclaration {
248248
}
249249
const keyCount = Object.keys(value).length
250250
return keyCount === 3 &&
251-
('Deadline' in value && ((KindInt.isKindInt(value.Deadline)))) &&
252-
('Partition' in value && ((KindInt.isKindInt(value.Partition)))) &&
253-
('Sectors' in value && ((KindBytes.isKindBytes(value.Sectors))))
251+
('Deadline' in value && (KindInt.isKindInt(value.Deadline))) &&
252+
('Partition' in value && (KindInt.isKindInt(value.Partition))) &&
253+
('Sectors' in value && (KindBytes.isKindBytes(value.Sectors)))
254254
}
255255
}
256256

@@ -286,13 +286,13 @@ export namespace ReplicaUpdate {
286286
}
287287
const keyCount = Object.keys(value).length
288288
return keyCount === 7 &&
289-
('SectorNumber' in value && ((KindInt.isKindInt(value.SectorNumber)))) &&
290-
('Deadline' in value && ((KindInt.isKindInt(value.Deadline)))) &&
291-
('Partition' in value && ((KindInt.isKindInt(value.Partition)))) &&
292-
('NewSealedSectorCID' in value && ((KindLink.isKindLink(value.NewSealedSectorCID)))) &&
289+
('SectorNumber' in value && (KindInt.isKindInt(value.SectorNumber))) &&
290+
('Deadline' in value && (KindInt.isKindInt(value.Deadline))) &&
291+
('Partition' in value && (KindInt.isKindInt(value.Partition))) &&
292+
('NewSealedSectorCID' in value && (KindLink.isKindLink(value.NewSealedSectorCID))) &&
293293
('Deals' in value && ((Array.isArray(value.Deals) && value.Deals.every(KindInt.isKindInt)))) &&
294-
('UpdateProofType' in value && ((KindInt.isKindInt(value.UpdateProofType)))) &&
295-
('ReplicaProof' in value && ((KindBytes.isKindBytes(value.ReplicaProof))))
294+
('UpdateProofType' in value && (KindInt.isKindInt(value.UpdateProofType))) &&
295+
('ReplicaProof' in value && (KindBytes.isKindBytes(value.ReplicaProof)))
296296
}
297297
}
298298
```

test/fixtures/gen/implicits.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# Implicit Field Values
2+
3+
Testing implicit field values in map representation structs.
4+
5+
[testmark]:# (test/schema)
6+
```ipldsch
7+
type Config struct {
8+
version Int (implicit 1)
9+
enabled Bool (implicit false)
10+
name String
11+
timeout Int (implicit 30)
12+
debug Bool
13+
} representation map
14+
15+
type ServerConfig struct {
16+
host String (implicit "localhost")
17+
port Int (implicit 8080)
18+
secure Bool (implicit true)
19+
path String
20+
} representation map
21+
22+
type Defaults struct {
23+
stringVal String (implicit "default")
24+
intVal Int (implicit 42)
25+
boolVal Bool (implicit true)
26+
} representation map
27+
```
28+
29+
[testmark]:# (test/golang)
30+
```go
31+
package main
32+
33+
type Config struct {
34+
version int64
35+
enabled bool
36+
name string
37+
timeout int64
38+
debug bool
39+
}
40+
41+
type ServerConfig struct {
42+
host string
43+
port int64
44+
secure bool
45+
path string
46+
}
47+
48+
type Defaults struct {
49+
stringVal string
50+
intVal int64
51+
boolVal bool
52+
}
53+
```
54+
55+
[testmark]:# (test/rust)
56+
```rust
57+
use serde::{Deserialize, Serialize};
58+
59+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
60+
pub struct Config {
61+
#[serde(default = "default_config_version")]
62+
pub version: i64,
63+
#[serde(default = "default_config_enabled")]
64+
pub enabled: bool,
65+
pub name: String,
66+
#[serde(default = "default_config_timeout")]
67+
pub timeout: i64,
68+
pub debug: bool,
69+
}
70+
71+
fn default_config_version() -> i64 {
72+
1
73+
}
74+
75+
fn default_config_enabled() -> bool {
76+
false
77+
}
78+
79+
fn default_config_timeout() -> i64 {
80+
30
81+
}
82+
83+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
84+
pub struct ServerConfig {
85+
#[serde(default = "default_serverconfig_host")]
86+
pub host: String,
87+
#[serde(default = "default_serverconfig_port")]
88+
pub port: i64,
89+
#[serde(default = "default_serverconfig_secure")]
90+
pub secure: bool,
91+
pub path: String,
92+
}
93+
94+
fn default_serverconfig_host() -> String {
95+
"localhost".to_string()
96+
}
97+
98+
fn default_serverconfig_port() -> i64 {
99+
8080
100+
}
101+
102+
fn default_serverconfig_secure() -> bool {
103+
true
104+
}
105+
106+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
107+
pub struct Defaults {
108+
#[serde(default = "default_defaults_string_val")]
109+
pub string_val: String,
110+
#[serde(default = "default_defaults_int_val")]
111+
pub int_val: i64,
112+
#[serde(default = "default_defaults_bool_val")]
113+
pub bool_val: bool,
114+
}
115+
116+
fn default_defaults_string_val() -> String {
117+
"default".to_string()
118+
}
119+
120+
fn default_defaults_int_val() -> i64 {
121+
42
122+
}
123+
124+
fn default_defaults_bool_val() -> bool {
125+
true
126+
}
127+
```
128+
129+
[testmark]:# (test/typescript)
130+
```typescript
131+
import {
132+
KindBool,
133+
KindInt,
134+
KindMap,
135+
KindString,
136+
} from '@ipld/schema/schema-schema.js'
137+
138+
export type Config = {
139+
version?: KindInt
140+
enabled?: KindBool
141+
name: KindString
142+
timeout?: KindInt
143+
debug: KindBool
144+
}
145+
146+
export namespace Config {
147+
export function isConfig(value: any): value is Config {
148+
if (!KindMap.isKindMap(value)) {
149+
return false
150+
}
151+
const keyCount = Object.keys(value).length
152+
return keyCount >= 2 && keyCount <= 5 &&
153+
(!('version' in value) || (KindInt.isKindInt(value.version))) &&
154+
(!('enabled' in value) || (KindBool.isKindBool(value.enabled))) &&
155+
('name' in value && (KindString.isKindString(value.name))) &&
156+
(!('timeout' in value) || (KindInt.isKindInt(value.timeout))) &&
157+
('debug' in value && (KindBool.isKindBool(value.debug)))
158+
}
159+
}
160+
161+
export type ServerConfig = {
162+
host?: KindString
163+
port?: KindInt
164+
secure?: KindBool
165+
path: KindString
166+
}
167+
168+
export namespace ServerConfig {
169+
export function isServerConfig(value: any): value is ServerConfig {
170+
if (!KindMap.isKindMap(value)) {
171+
return false
172+
}
173+
const keyCount = Object.keys(value).length
174+
return keyCount >= 1 && keyCount <= 4 &&
175+
(!('host' in value) || (KindString.isKindString(value.host))) &&
176+
(!('port' in value) || (KindInt.isKindInt(value.port))) &&
177+
(!('secure' in value) || (KindBool.isKindBool(value.secure))) &&
178+
('path' in value && (KindString.isKindString(value.path)))
179+
}
180+
}
181+
182+
export type Defaults = {
183+
stringVal?: KindString
184+
intVal?: KindInt
185+
boolVal?: KindBool
186+
}
187+
188+
export namespace Defaults {
189+
export function isDefaults(value: any): value is Defaults {
190+
if (!KindMap.isKindMap(value)) {
191+
return false
192+
}
193+
const keyCount = Object.keys(value).length
194+
return keyCount >= 0 && keyCount <= 3 &&
195+
(!('stringVal' in value) || (KindString.isKindString(value.stringVal))) &&
196+
(!('intVal' in value) || (KindInt.isKindInt(value.intVal))) &&
197+
(!('boolVal' in value) || (KindBool.isKindBool(value.boolVal)))
198+
}
199+
}
200+
```

0 commit comments

Comments
 (0)