Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go-gen improvements #2027

Merged
merged 5 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 25 additions & 16 deletions packages/go-gen/src/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl Display for GoField {
self.ty,
self.rust_name
)?;
if self.ty.is_nullable {
if let Nullability::OmitEmpty | Nullability::Nullable = self.ty.nullability {
f.write_str(",omitempty")?;
}
f.write_str("\"`")
Expand All @@ -60,10 +60,19 @@ impl Display for GoField {
pub struct GoType {
/// The name of the type in Go
pub name: String,
/// Whether the type should be nullable
/// This will add `omitempty` to the json tag and use a pointer type if
/// the type is not a basic type
pub is_nullable: bool,
/// Whether the type should be nullable / omitempty / etc.
pub nullability: Nullability,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Nullability {
/// The type should be nullable
/// In Go, this will use a pointer type and add `omitempty` to the json tag
Nullable,
/// The type should not be nullable, use the type as is
NonNullable,
/// The type should be nullable by omitting it from the json object if it is empty
OmitEmpty,
}

impl GoType {
Expand Down Expand Up @@ -95,7 +104,7 @@ impl GoType {

impl Display for GoType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_nullable && !self.is_basic_type() {
if self.nullability == Nullability::Nullable && !self.is_basic_type() {
// if the type is nullable and not a basic type, use a pointer
f.write_char('*')?;
}
Expand All @@ -122,23 +131,23 @@ mod tests {
fn go_type_display_works() {
let ty = GoType {
name: "string".to_string(),
is_nullable: true,
nullability: Nullability::Nullable,
};
let ty2 = GoType {
name: "string".to_string(),
is_nullable: false,
nullability: Nullability::NonNullable,
};
assert_eq!(format!("{}", ty), "string");
assert_eq!(format!("{}", ty2), "string");

let ty = GoType {
name: "FooBar".to_string(),
is_nullable: true,
nullability: Nullability::Nullable,
};
assert_eq!(format!("{}", ty), "*FooBar");
let ty = GoType {
name: "FooBar".to_string(),
is_nullable: false,
nullability: Nullability::NonNullable,
};
assert_eq!(format!("{}", ty), "FooBar");
}
Expand All @@ -150,7 +159,7 @@ mod tests {
docs: None,
ty: GoType {
name: "string".to_string(),
is_nullable: true,
nullability: Nullability::Nullable,
},
};
assert_eq!(
Expand All @@ -163,7 +172,7 @@ mod tests {
docs: None,
ty: GoType {
name: "string".to_string(),
is_nullable: false,
nullability: Nullability::NonNullable,
},
};
assert_eq!(format!("{}", field), "FooBar string `json:\"foo_bar\"`");
Expand All @@ -173,7 +182,7 @@ mod tests {
docs: None,
ty: GoType {
name: "FooBar".to_string(),
is_nullable: true,
nullability: Nullability::Nullable,
},
};
assert_eq!(
Expand All @@ -189,7 +198,7 @@ mod tests {
docs: Some("foo_bar is a test field".to_string()),
ty: GoType {
name: "string".to_string(),
is_nullable: true,
nullability: Nullability::Nullable,
},
};
assert_eq!(
Expand All @@ -208,7 +217,7 @@ mod tests {
docs: None,
ty: GoType {
name: "string".to_string(),
is_nullable: true,
nullability: Nullability::Nullable,
},
}],
};
Expand All @@ -225,7 +234,7 @@ mod tests {
docs: None,
ty: GoType {
name: "string".to_string(),
is_nullable: true,
nullability: Nullability::Nullable,
},
}],
};
Expand Down
63 changes: 57 additions & 6 deletions packages/go-gen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
// we are not interested in that case, so we error out
if let Some(values) = &schema.enum_values {
bail!(
"enum variants {} without inner data not supported",
"enum variant {} without inner data not supported",

Check warning on line 154 in packages/go-gen/src/main.rs

View check run for this annotation

Codecov / codecov/patch

packages/go-gen/src/main.rs#L154

Added line #L154 was not covered by tests
values
.iter()
.map(|v| v.to_string())
Expand Down Expand Up @@ -186,7 +186,7 @@
docs,
ty: GoType {
name: ty,
is_nullable: true, // always nullable
nullability: Nullability::Nullable, // always nullable
},
})
}
Expand Down Expand Up @@ -251,7 +251,7 @@
Binary []byte `json:"binary"`
Checksum Checksum `json:"checksum"`
HexBinary string `json:"hex_binary"`
NestedBinary []*[]byte `json:"nested_binary"`
NestedBinary Array[*[]byte] `json:"nested_binary"`
Uint128 string `json:"uint128"`
}"#,
);
Expand Down Expand Up @@ -436,7 +436,7 @@
compare_codes!(cosmwasm_std::DistributionMsg);
compare_codes!(cosmwasm_std::IbcMsg);
compare_codes!(cosmwasm_std::WasmMsg);
// compare_codes!(cosmwasm_std::GovMsg); // TODO: currently fails because of VoteOption
compare_codes!(cosmwasm_std::GovMsg);
}

#[test]
Expand All @@ -456,7 +456,7 @@
code,
r#"
type A struct {
A [][][]*B `json:"a"`
A Array[Array[Array[*B]]] `json:"a"`
}
type B struct { }"#,
);
Expand All @@ -470,7 +470,22 @@
code,
r#"
type C struct {
C [][][]*string `json:"c"`
C Array[Array[Array[*string]]] `json:"c"`
}"#,
);

#[cw_serde]
struct D {
d: Option<Vec<String>>,
nested: Vec<Option<Vec<String>>>,
}
let code = generate_go(cosmwasm_schema::schema_for!(D)).unwrap();
assert_code_eq(
code,
r#"
type D struct {
D *[]string `json:"d,omitempty"`
Nested Array[*[]string] `json:"nested"`
}"#,
);
}
Expand Down Expand Up @@ -548,4 +563,40 @@
"#,
);
}

#[test]
fn serde_default_works() {
fn default_u32() -> u32 {
42
}
#[cw_serde]
#[derive(Default)]
struct Nested {
a: u32,
}
#[cw_serde]
struct A {
#[serde(default)]
payload: Binary,
#[serde(default = "default_u32")]
int: u32,
#[serde(default)]
nested: Nested,
}

let code = generate_go(cosmwasm_schema::schema_for!(A)).unwrap();
assert_code_eq(
code,
r#"
type A struct {
Int uint32 `json:"int,omitempty"`
Nested Nested `json:"nested,omitempty"`
Payload []byte `json:"payload,omitempty"`
}
type Nested struct {
A uint32 `json:"a"`
}
"#,
);
}
}
45 changes: 36 additions & 9 deletions packages/go-gen/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};

use crate::{
go::{GoField, GoStruct, GoType},
go::{GoField, GoStruct, GoType, Nullability},
utils::{replace_acronyms, suffixes},
};

Expand Down Expand Up @@ -36,9 +36,25 @@
type_context: TypeContext,
additional_structs: &mut Vec<GoStruct>,
) -> Result<GoType> {
let mut is_nullable = is_null(schema);
let mut nullability = if is_null(schema) {
Nullability::Nullable
} else {
Nullability::NonNullable
};

// Check for a default value.

Check warning on line 45 in packages/go-gen/src/schema.rs

View check run for this annotation

Codecov / codecov/patch

packages/go-gen/src/schema.rs#L43-L45

Added lines #L43 - L45 were not covered by tests
// This is the case if the field was annotated with `#[serde(default)]` or variations of it.
// A `null` value is not necessarily allowed in this case,

Check warning on line 47 in packages/go-gen/src/schema.rs

View check run for this annotation

Codecov / codecov/patch

packages/go-gen/src/schema.rs#L47

Added line #L47 was not covered by tests
// so we want to omit the field on the Go side.
if schema
.metadata
.as_ref()
.and_then(|m| m.default.as_ref())
.is_some()
{
nullability = Nullability::OmitEmpty;
}

// if it has a title, use that
let ty = if let Some(title) = schema.metadata.as_ref().and_then(|m| m.title.as_ref()) {
replace_custom_type(title)
} else if let Some(reference) = &schema.reference {
Expand All @@ -56,7 +72,7 @@
let nullable = nullable_type(subschemas)?;
if let Some(non_null) = nullable {
ensure!(subschemas.len() == 2, "multiple subschemas in anyOf");
is_nullable = true;
nullability = Nullability::Nullable;
// extract non-null type
let GoType { name, .. } =
schema_object_type(non_null, type_context, additional_structs)?;
Expand All @@ -78,7 +94,7 @@

Ok(GoType {
name: ty,
is_nullable,
nullability,
})
}

Expand Down Expand Up @@ -197,11 +213,20 @@
// for nullable array item types, we have to use a pointer type, even for basic types,
// so we can pass null as elements
// otherwise they would just be omitted from the array
replace_custom_type(&if item_type.is_nullable {
format!("[]*{}", item_type.name)
let maybe_ptr = if item_type.nullability == Nullability::Nullable {
"*"
} else {
""
};
let ty = if t.contains(&InstanceType::Null) {
// if the array itself is nullable, we can use a native Go slice
format!("[]{maybe_ptr}{}", item_type.name)
} else {
format!("[]{}", item_type.name)
})
// if it is not nullable, we enforce empty slices instead of nil using our own type
format!("Array[{maybe_ptr}{}]", item_type.name)
};

replace_custom_type(&ty)
} else {
unreachable!("instance type should be one of the above")
})
Expand Down Expand Up @@ -265,6 +290,8 @@
"Int128" => Some("string"),
"Binary" => Some("[]byte"),
"HexBinary" => Some("string"),
"ReplyOn" => Some("replyOn"),
"VoteOption" => Some("voteOption"),
"Checksum" => Some("Checksum"),
"Addr" => Some("string"),
"Decimal" => Some("string"),
Expand Down
2 changes: 1 addition & 1 deletion packages/go-gen/tests/cosmwasm_std__AllBalanceResponse.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// AllBalancesResponse is the expected response to AllBalancesQuery
type AllBalancesResponse struct {
Amount []Coin `json:"amount"` // in wasmvm, there is an alias for `[]Coin`
Amount Array[Coin] `json:"amount"`
}

// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// AllDelegationsResponse is the expected response to AllDelegationsQuery
type AllDelegationsResponse struct {
Delegations []Delegation `json:"delegations"` // in wasmvm, there is an alias for `[]Delegation`
Delegations Array[Delegation] `json:"delegations"`
}

// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int)
Expand Down
4 changes: 2 additions & 2 deletions packages/go-gen/tests/cosmwasm_std__AllValidatorsResponse.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// AllValidatorsResponse is the expected response to AllValidatorsQuery
type AllValidatorsResponse struct {
Validators []Validator `json:"validators"` // in wasmvm, there is an alias for `[]Validator`
Validators Array[Validator] `json:"validators"`
}

type Validator struct {
Expand All @@ -11,4 +11,4 @@ type Validator struct {
MaxChangeRate string `json:"max_change_rate"`
// decimal string, eg "0.02"
MaxCommission string `json:"max_commission"`
}
}
6 changes: 3 additions & 3 deletions packages/go-gen/tests/cosmwasm_std__BankMsg.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// SendMsg contains instructions for a Cosmos-SDK/SendMsg
// It has a fixed interface here and should be converted into the proper SDK format before dispatching
type SendMsg struct {
Amount []Coin `json:"amount"`
ToAddress string `json:"to_address"`
Amount Array[Coin] `json:"amount"`
ToAddress string `json:"to_address"`
}

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

type BankMsg struct {
Expand Down
12 changes: 6 additions & 6 deletions packages/go-gen/tests/cosmwasm_std__DelegationResponse.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ type Coin struct {
}

type FullDelegation struct {
AccumulatedRewards []Coin `json:"accumulated_rewards"` // in wasmvm, there is an alias for `[]Coin`
Amount Coin `json:"amount"`
CanRedelegate Coin `json:"can_redelegate"`
Delegator string `json:"delegator"`
Validator string `json:"validator"`
}
AccumulatedRewards Array[Coin] `json:"accumulated_rewards"`
Amount Coin `json:"amount"`
CanRedelegate Coin `json:"can_redelegate"`
Delegator string `json:"delegator"`
Validator string `json:"validator"`
}
Loading
Loading