Skip to content

Commit bd6b54d

Browse files
authored
Merge pull request #2027 from CosmWasm/go-gen-fixes
Go-gen improvements
2 parents 08d5801 + 07fbbf2 commit bd6b54d

15 files changed

+174
-61
lines changed

packages/go-gen/src/go.rs

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl Display for GoField {
5050
self.ty,
5151
self.rust_name
5252
)?;
53-
if self.ty.is_nullable {
53+
if let Nullability::OmitEmpty | Nullability::Nullable = self.ty.nullability {
5454
f.write_str(",omitempty")?;
5555
}
5656
f.write_str("\"`")
@@ -60,10 +60,19 @@ impl Display for GoField {
6060
pub struct GoType {
6161
/// The name of the type in Go
6262
pub name: String,
63-
/// Whether the type should be nullable
64-
/// This will add `omitempty` to the json tag and use a pointer type if
65-
/// the type is not a basic type
66-
pub is_nullable: bool,
63+
/// Whether the type should be nullable / omitempty / etc.
64+
pub nullability: Nullability,
65+
}
66+
67+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68+
pub enum Nullability {
69+
/// The type should be nullable
70+
/// In Go, this will use a pointer type and add `omitempty` to the json tag
71+
Nullable,
72+
/// The type should not be nullable, use the type as is
73+
NonNullable,
74+
/// The type should be nullable by omitting it from the json object if it is empty
75+
OmitEmpty,
6776
}
6877

6978
impl GoType {
@@ -95,7 +104,7 @@ impl GoType {
95104

96105
impl Display for GoType {
97106
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98-
if self.is_nullable && !self.is_basic_type() {
107+
if self.nullability == Nullability::Nullable && !self.is_basic_type() {
99108
// if the type is nullable and not a basic type, use a pointer
100109
f.write_char('*')?;
101110
}
@@ -122,23 +131,23 @@ mod tests {
122131
fn go_type_display_works() {
123132
let ty = GoType {
124133
name: "string".to_string(),
125-
is_nullable: true,
134+
nullability: Nullability::Nullable,
126135
};
127136
let ty2 = GoType {
128137
name: "string".to_string(),
129-
is_nullable: false,
138+
nullability: Nullability::NonNullable,
130139
};
131140
assert_eq!(format!("{}", ty), "string");
132141
assert_eq!(format!("{}", ty2), "string");
133142

134143
let ty = GoType {
135144
name: "FooBar".to_string(),
136-
is_nullable: true,
145+
nullability: Nullability::Nullable,
137146
};
138147
assert_eq!(format!("{}", ty), "*FooBar");
139148
let ty = GoType {
140149
name: "FooBar".to_string(),
141-
is_nullable: false,
150+
nullability: Nullability::NonNullable,
142151
};
143152
assert_eq!(format!("{}", ty), "FooBar");
144153
}
@@ -150,7 +159,7 @@ mod tests {
150159
docs: None,
151160
ty: GoType {
152161
name: "string".to_string(),
153-
is_nullable: true,
162+
nullability: Nullability::Nullable,
154163
},
155164
};
156165
assert_eq!(
@@ -163,7 +172,7 @@ mod tests {
163172
docs: None,
164173
ty: GoType {
165174
name: "string".to_string(),
166-
is_nullable: false,
175+
nullability: Nullability::NonNullable,
167176
},
168177
};
169178
assert_eq!(format!("{}", field), "FooBar string `json:\"foo_bar\"`");
@@ -173,7 +182,7 @@ mod tests {
173182
docs: None,
174183
ty: GoType {
175184
name: "FooBar".to_string(),
176-
is_nullable: true,
185+
nullability: Nullability::Nullable,
177186
},
178187
};
179188
assert_eq!(
@@ -189,7 +198,7 @@ mod tests {
189198
docs: Some("foo_bar is a test field".to_string()),
190199
ty: GoType {
191200
name: "string".to_string(),
192-
is_nullable: true,
201+
nullability: Nullability::Nullable,
193202
},
194203
};
195204
assert_eq!(
@@ -208,7 +217,7 @@ mod tests {
208217
docs: None,
209218
ty: GoType {
210219
name: "string".to_string(),
211-
is_nullable: true,
220+
nullability: Nullability::Nullable,
212221
},
213222
}],
214223
};
@@ -225,7 +234,7 @@ mod tests {
225234
docs: None,
226235
ty: GoType {
227236
name: "string".to_string(),
228-
is_nullable: true,
237+
nullability: Nullability::Nullable,
229238
},
230239
}],
231240
};

packages/go-gen/src/main.rs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ pub fn build_enum_variant(
151151
// we are not interested in that case, so we error out
152152
if let Some(values) = &schema.enum_values {
153153
bail!(
154-
"enum variants {} without inner data not supported",
154+
"enum variant {} without inner data not supported",
155155
values
156156
.iter()
157157
.map(|v| v.to_string())
@@ -186,7 +186,7 @@ pub fn build_enum_variant(
186186
docs,
187187
ty: GoType {
188188
name: ty,
189-
is_nullable: true, // always nullable
189+
nullability: Nullability::Nullable, // always nullable
190190
},
191191
})
192192
}
@@ -251,7 +251,7 @@ mod tests {
251251
Binary []byte `json:"binary"`
252252
Checksum Checksum `json:"checksum"`
253253
HexBinary string `json:"hex_binary"`
254-
NestedBinary []*[]byte `json:"nested_binary"`
254+
NestedBinary Array[*[]byte] `json:"nested_binary"`
255255
Uint128 string `json:"uint128"`
256256
}"#,
257257
);
@@ -436,7 +436,7 @@ mod tests {
436436
compare_codes!(cosmwasm_std::DistributionMsg);
437437
compare_codes!(cosmwasm_std::IbcMsg);
438438
compare_codes!(cosmwasm_std::WasmMsg);
439-
// compare_codes!(cosmwasm_std::GovMsg); // TODO: currently fails because of VoteOption
439+
compare_codes!(cosmwasm_std::GovMsg);
440440
}
441441

442442
#[test]
@@ -456,7 +456,7 @@ mod tests {
456456
code,
457457
r#"
458458
type A struct {
459-
A [][][]*B `json:"a"`
459+
A Array[Array[Array[*B]]] `json:"a"`
460460
}
461461
type B struct { }"#,
462462
);
@@ -470,7 +470,22 @@ mod tests {
470470
code,
471471
r#"
472472
type C struct {
473-
C [][][]*string `json:"c"`
473+
C Array[Array[Array[*string]]] `json:"c"`
474+
}"#,
475+
);
476+
477+
#[cw_serde]
478+
struct D {
479+
d: Option<Vec<String>>,
480+
nested: Vec<Option<Vec<String>>>,
481+
}
482+
let code = generate_go(cosmwasm_schema::schema_for!(D)).unwrap();
483+
assert_code_eq(
484+
code,
485+
r#"
486+
type D struct {
487+
D *[]string `json:"d,omitempty"`
488+
Nested Array[*[]string] `json:"nested"`
474489
}"#,
475490
);
476491
}
@@ -548,4 +563,40 @@ mod tests {
548563
"#,
549564
);
550565
}
566+
567+
#[test]
568+
fn serde_default_works() {
569+
fn default_u32() -> u32 {
570+
42
571+
}
572+
#[cw_serde]
573+
#[derive(Default)]
574+
struct Nested {
575+
a: u32,
576+
}
577+
#[cw_serde]
578+
struct A {
579+
#[serde(default)]
580+
payload: Binary,
581+
#[serde(default = "default_u32")]
582+
int: u32,
583+
#[serde(default)]
584+
nested: Nested,
585+
}
586+
587+
let code = generate_go(cosmwasm_schema::schema_for!(A)).unwrap();
588+
assert_code_eq(
589+
code,
590+
r#"
591+
type A struct {
592+
Int uint32 `json:"int,omitempty"`
593+
Nested Nested `json:"nested,omitempty"`
594+
Payload []byte `json:"payload,omitempty"`
595+
}
596+
type Nested struct {
597+
A uint32 `json:"a"`
598+
}
599+
"#,
600+
);
601+
}
551602
}

packages/go-gen/src/schema.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use inflector::Inflector;
44
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
55

66
use crate::{
7-
go::{GoField, GoStruct, GoType},
7+
go::{GoField, GoStruct, GoType, Nullability},
88
utils::{replace_acronyms, suffixes},
99
};
1010

@@ -36,9 +36,25 @@ pub fn schema_object_type(
3636
type_context: TypeContext,
3737
additional_structs: &mut Vec<GoStruct>,
3838
) -> Result<GoType> {
39-
let mut is_nullable = is_null(schema);
39+
let mut nullability = if is_null(schema) {
40+
Nullability::Nullable
41+
} else {
42+
Nullability::NonNullable
43+
};
44+
45+
// Check for a default value.
46+
// This is the case if the field was annotated with `#[serde(default)]` or variations of it.
47+
// A `null` value is not necessarily allowed in this case,
48+
// so we want to omit the field on the Go side.
49+
if schema
50+
.metadata
51+
.as_ref()
52+
.and_then(|m| m.default.as_ref())
53+
.is_some()
54+
{
55+
nullability = Nullability::OmitEmpty;
56+
}
4057

41-
// if it has a title, use that
4258
let ty = if let Some(title) = schema.metadata.as_ref().and_then(|m| m.title.as_ref()) {
4359
replace_custom_type(title)
4460
} else if let Some(reference) = &schema.reference {
@@ -56,7 +72,7 @@ pub fn schema_object_type(
5672
let nullable = nullable_type(subschemas)?;
5773
if let Some(non_null) = nullable {
5874
ensure!(subschemas.len() == 2, "multiple subschemas in anyOf");
59-
is_nullable = true;
75+
nullability = Nullability::Nullable;
6076
// extract non-null type
6177
let GoType { name, .. } =
6278
schema_object_type(non_null, type_context, additional_structs)?;
@@ -78,7 +94,7 @@ pub fn schema_object_type(
7894

7995
Ok(GoType {
8096
name: ty,
81-
is_nullable,
97+
nullability,
8298
})
8399
}
84100

@@ -197,11 +213,20 @@ pub fn type_from_instance_type(
197213
// for nullable array item types, we have to use a pointer type, even for basic types,
198214
// so we can pass null as elements
199215
// otherwise they would just be omitted from the array
200-
replace_custom_type(&if item_type.is_nullable {
201-
format!("[]*{}", item_type.name)
216+
let maybe_ptr = if item_type.nullability == Nullability::Nullable {
217+
"*"
218+
} else {
219+
""
220+
};
221+
let ty = if t.contains(&InstanceType::Null) {
222+
// if the array itself is nullable, we can use a native Go slice
223+
format!("[]{maybe_ptr}{}", item_type.name)
202224
} else {
203-
format!("[]{}", item_type.name)
204-
})
225+
// if it is not nullable, we enforce empty slices instead of nil using our own type
226+
format!("Array[{maybe_ptr}{}]", item_type.name)
227+
};
228+
229+
replace_custom_type(&ty)
205230
} else {
206231
unreachable!("instance type should be one of the above")
207232
})
@@ -265,6 +290,8 @@ pub fn custom_type_of(ty: &str) -> Option<&str> {
265290
"Int128" => Some("string"),
266291
"Binary" => Some("[]byte"),
267292
"HexBinary" => Some("string"),
293+
"ReplyOn" => Some("replyOn"),
294+
"VoteOption" => Some("voteOption"),
268295
"Checksum" => Some("Checksum"),
269296
"Addr" => Some("string"),
270297
"Decimal" => Some("string"),

packages/go-gen/tests/cosmwasm_std__AllBalanceResponse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// AllBalancesResponse is the expected response to AllBalancesQuery
22
type AllBalancesResponse struct {
3-
Amount []Coin `json:"amount"` // in wasmvm, there is an alias for `[]Coin`
3+
Amount Array[Coin] `json:"amount"`
44
}
55

66
// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int)

packages/go-gen/tests/cosmwasm_std__AllDelegationsResponse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// AllDelegationsResponse is the expected response to AllDelegationsQuery
22
type AllDelegationsResponse struct {
3-
Delegations []Delegation `json:"delegations"` // in wasmvm, there is an alias for `[]Delegation`
3+
Delegations Array[Delegation] `json:"delegations"`
44
}
55

66
// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int)

packages/go-gen/tests/cosmwasm_std__AllValidatorsResponse.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// AllValidatorsResponse is the expected response to AllValidatorsQuery
22
type AllValidatorsResponse struct {
3-
Validators []Validator `json:"validators"` // in wasmvm, there is an alias for `[]Validator`
3+
Validators Array[Validator] `json:"validators"`
44
}
55

66
type Validator struct {
@@ -11,4 +11,4 @@ type Validator struct {
1111
MaxChangeRate string `json:"max_change_rate"`
1212
// decimal string, eg "0.02"
1313
MaxCommission string `json:"max_commission"`
14-
}
14+
}

packages/go-gen/tests/cosmwasm_std__BankMsg.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// SendMsg contains instructions for a Cosmos-SDK/SendMsg
22
// It has a fixed interface here and should be converted into the proper SDK format before dispatching
33
type SendMsg struct {
4-
Amount []Coin `json:"amount"`
5-
ToAddress string `json:"to_address"`
4+
Amount Array[Coin] `json:"amount"`
5+
ToAddress string `json:"to_address"`
66
}
77

88
// BurnMsg will burn the given coins from the contract's account.
99
// There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper.
1010
// Important if a contract controls significant token supply that must be retired.
1111
type BurnMsg struct {
12-
Amount []Coin `json:"amount"`
12+
Amount Array[Coin] `json:"amount"`
1313
}
1414

1515
type BankMsg struct {

packages/go-gen/tests/cosmwasm_std__DelegationResponse.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ type Coin struct {
1010
}
1111

1212
type FullDelegation struct {
13-
AccumulatedRewards []Coin `json:"accumulated_rewards"` // in wasmvm, there is an alias for `[]Coin`
14-
Amount Coin `json:"amount"`
15-
CanRedelegate Coin `json:"can_redelegate"`
16-
Delegator string `json:"delegator"`
17-
Validator string `json:"validator"`
18-
}
13+
AccumulatedRewards Array[Coin] `json:"accumulated_rewards"`
14+
Amount Coin `json:"amount"`
15+
CanRedelegate Coin `json:"can_redelegate"`
16+
Delegator string `json:"delegator"`
17+
Validator string `json:"validator"`
18+
}

0 commit comments

Comments
 (0)