From 1ad75ec7533487ec3169dc730405b04f622811e6 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 9 Nov 2023 16:39:21 +0100 Subject: [PATCH 01/10] Add more custom types to go-gen --- packages/go-gen/src/schema.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/go-gen/src/schema.rs b/packages/go-gen/src/schema.rs index 496d0954c2..09b7a9aa51 100644 --- a/packages/go-gen/src/schema.rs +++ b/packages/go-gen/src/schema.rs @@ -259,12 +259,14 @@ pub fn documentation(schema: &SchemaObject) -> Option { /// If the given type is not a special type, returns `None`. pub fn custom_type_of(ty: &str) -> Option<&str> { match ty { + "Uint64" => Some("string"), "Uint128" => Some("string"), "Binary" => Some("[]byte"), "HexBinary" => Some("Checksum"), "Addr" => Some("string"), "Decimal" => Some("string"), "Decimal256" => Some("string"), + "Timestamp" => Some("uint64"), _ => None, } } From cf7d85daeb102e7d3b594581ddf8f277c6e329c1 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 9 Nov 2023 16:40:20 +0100 Subject: [PATCH 02/10] Fix go-gen acronym replacement --- packages/go-gen/src/main.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/go-gen/src/main.rs b/packages/go-gen/src/main.rs index 8d3dcc3d40..76b35f8b0a 100644 --- a/packages/go-gen/src/main.rs +++ b/packages/go-gen/src/main.rs @@ -37,7 +37,7 @@ fn generate_go(root: RootSchema) -> Result { for (name, additional_type) in &root.definitions { additional_type .object() - .map(|def| build_type(&replace_acronyms(name), def, &mut types)) + .map(|def| build_type(name, def, &mut types)) .and_then(|r| r) .context("failed to generate additional definitions")?; } @@ -107,7 +107,7 @@ pub fn build_struct( let fields = fields.collect::>>()?; Ok(GoStruct { - name: to_pascal_case(name), + name: replace_acronyms(to_pascal_case(name)), docs, fields, }) @@ -121,6 +121,7 @@ pub fn build_enum<'a>( variants: impl Iterator, additional_structs: &mut Vec, ) -> Result { + let name = replace_acronyms(name); let docs = documentation(enm); // go through all fields @@ -131,18 +132,14 @@ pub fn build_enum<'a>( .with_context(|| format!("expected schema object for enum variants of {name}"))?; // analyze the variant - let variant_field = build_enum_variant(v, name, additional_structs) + let variant_field = build_enum_variant(v, &name, additional_structs) .context("failed to extract enum variant")?; anyhow::Ok(variant_field) }); let fields = fields.collect::>>()?; - Ok(GoStruct { - name: name.to_string(), - docs, - fields, - }) + Ok(GoStruct { name, docs, fields }) } /// Tries to extract the name and type of the given enum variant and returns it as a `GoField`. From f2b28c32d68b8df2324c199bc9df6cabb9e4b166 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 9 Nov 2023 17:55:33 +0100 Subject: [PATCH 03/10] Fix field type name --- packages/go-gen/src/schema.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/go-gen/src/schema.rs b/packages/go-gen/src/schema.rs index 09b7a9aa51..cd17fe16f8 100644 --- a/packages/go-gen/src/schema.rs +++ b/packages/go-gen/src/schema.rs @@ -43,12 +43,12 @@ pub fn schema_object_type( replace_custom_type(title) } else if let Some(reference) = &schema.reference { // if it has a reference, strip the path and use that - replace_custom_type( + replace_custom_type(&replace_acronyms( reference .split('/') .last() .expect("split should always return at least one item"), - ) + )) } else if let Some(t) = &schema.instance_type { type_from_instance_type(schema, type_context, t, additional_structs)? } else if let Some(subschemas) = schema.subschemas.as_ref().and_then(|s| s.any_of.as_ref()) { From 64710d2c6e1f9019b186cdf489335b6613467bec Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 9 Nov 2023 17:55:58 +0100 Subject: [PATCH 04/10] Add some tests for message go code generation --- packages/go-gen/src/main.rs | 10 ++ .../go-gen/tests/cosmwasm_std__BankMsg.go | 24 ++++ .../tests/cosmwasm_std__DistributionMsg.go | 32 +++++ packages/go-gen/tests/cosmwasm_std__IbcMsg.go | 47 +++++++ .../go-gen/tests/cosmwasm_std__StakingMsg.go | 25 ++++ .../go-gen/tests/cosmwasm_std__WasmMsg.go | 115 ++++++++++++++++++ 6 files changed, 253 insertions(+) create mode 100644 packages/go-gen/tests/cosmwasm_std__BankMsg.go create mode 100644 packages/go-gen/tests/cosmwasm_std__DistributionMsg.go create mode 100644 packages/go-gen/tests/cosmwasm_std__IbcMsg.go create mode 100644 packages/go-gen/tests/cosmwasm_std__StakingMsg.go create mode 100644 packages/go-gen/tests/cosmwasm_std__WasmMsg.go diff --git a/packages/go-gen/src/main.rs b/packages/go-gen/src/main.rs index 76b35f8b0a..0811fbed6a 100644 --- a/packages/go-gen/src/main.rs +++ b/packages/go-gen/src/main.rs @@ -428,6 +428,16 @@ mod tests { compare_codes!(cosmwasm_std::WasmQuery); } + #[test] + fn messages_work() { + compare_codes!(cosmwasm_std::BankMsg); + compare_codes!(cosmwasm_std::StakingMsg); + 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 + } + #[test] fn array_item_type_works() { #[cw_serde] diff --git a/packages/go-gen/tests/cosmwasm_std__BankMsg.go b/packages/go-gen/tests/cosmwasm_std__BankMsg.go new file mode 100644 index 0000000000..d94723582e --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__BankMsg.go @@ -0,0 +1,24 @@ +// 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"` +} + +// 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"` +} + +type BankMsg struct { + Send *SendMsg `json:"send,omitempty"` + Burn *BurnMsg `json:"burn,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +} diff --git a/packages/go-gen/tests/cosmwasm_std__DistributionMsg.go b/packages/go-gen/tests/cosmwasm_std__DistributionMsg.go new file mode 100644 index 0000000000..35217b6434 --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__DistributionMsg.go @@ -0,0 +1,32 @@ +// SetWithdrawAddressMsg is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). +// `delegator_address` is automatically filled with the current contract's address. +type SetWithdrawAddressMsg struct { + // Address contains the `delegator_address` of a MsgSetWithdrawAddress + Address string `json:"address"` +} + +// WithdrawDelegatorRewardMsg is translated to a [MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). +// `delegator_address` is automatically filled with the current contract's address. +type WithdrawDelegatorRewardMsg struct { + // Validator contains `validator_address` of a MsgWithdrawDelegatorReward + Validator string `json:"validator"` +} + +// FundCommunityPoolMsg is translated to a [MsgFundCommunityPool](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#LL69C1-L76C2). +// `depositor` is automatically filled with the current contract's address +type FundCommunityPoolMsg struct { + // Amount is the list of coins to be send to the community pool + Amount []Coin `json:"amount"` +} + +type DistributionMsg struct { + SetWithdrawAddress *SetWithdrawAddressMsg `json:"set_withdraw_address,omitempty"` + WithdrawDelegatorReward *WithdrawDelegatorRewardMsg `json:"withdraw_delegator_reward,omitempty"` + FundCommunityPool *FundCommunityPoolMsg `json:"fund_community_pool,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +} diff --git a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go new file mode 100644 index 0000000000..d8165ba957 --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go @@ -0,0 +1,47 @@ +type TransferMsg struct { + Amount Coin `json:"amount"` + ChannelID string `json:"channel_id"` + Memo string `json:"memo,omitempty"` // this is not yet in wasmvm, but will be soon + Timeout IBCTimeout `json:"timeout"` + ToAddress string `json:"to_address"` +} +type SendPacketMsg struct { + ChannelID string `json:"channel_id"` + Data []byte `json:"data"` + Timeout IBCTimeout `json:"timeout"` +} +type CloseChannelMsg struct { + ChannelID string `json:"channel_id"` +} + +type IBCMsg struct { + Transfer *TransferMsg `json:"transfer,omitempty"` + SendPacket *SendPacketMsg `json:"send_packet,omitempty"` + CloseChannel *CloseChannelMsg `json:"close_channel,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +} + +// IBCTimeout is the timeout for an IBC packet. At least one of block and timestamp is required. +type IBCTimeout struct { + Block *IBCTimeoutBlock `json:"block,omitempty"` // in wasmvm, this does not have "omitempty" + // Nanoseconds since UNIX epoch + Timestamp uint64 `json:"timestamp,omitempty"` // wasmvm has a "string" in here too +} + +// IBCTimeoutBlock Height is a monotonically increasing data type +// that can be compared against another Height for the purposes of updating and +// freezing clients. +// Ordering is (revision_number, timeout_height) +type IBCTimeoutBlock struct { + // block height after which the packet times out. + // the height within the given revision + Height uint64 `json:"height"` + // the version that the client is currently on + // (eg. after reseting the chain this could increment 1 as height drops to 0) + Revision uint64 `json:"revision"` +} diff --git a/packages/go-gen/tests/cosmwasm_std__StakingMsg.go b/packages/go-gen/tests/cosmwasm_std__StakingMsg.go new file mode 100644 index 0000000000..e7cc377d1f --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__StakingMsg.go @@ -0,0 +1,25 @@ +type DelegateMsg struct { + Amount Coin `json:"amount"` + Validator string `json:"validator"` +} +type UndelegateMsg struct { + Amount Coin `json:"amount"` + Validator string `json:"validator"` +} +type RedelegateMsg struct { + Amount Coin `json:"amount"` + DstValidator string `json:"dst_validator"` + SrcValidator string `json:"src_validator"` +} + +type StakingMsg struct { + Delegate *DelegateMsg `json:"delegate,omitempty"` + Undelegate *UndelegateMsg `json:"undelegate,omitempty"` + Redelegate *RedelegateMsg `json:"redelegate,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +} diff --git a/packages/go-gen/tests/cosmwasm_std__WasmMsg.go b/packages/go-gen/tests/cosmwasm_std__WasmMsg.go new file mode 100644 index 0000000000..0e7287e61b --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__WasmMsg.go @@ -0,0 +1,115 @@ +// ExecuteMsg is used to call another defined contract on this chain. +// The calling contract requires the callee to be defined beforehand, +// and the address should have been defined in initialization. +// And we assume the developer tested the ABIs and coded them together. +// +// Since a contract is immutable once it is deployed, we don't need to transform this. +// If it was properly coded and worked once, it will continue to work throughout upgrades. +type ExecuteMsg struct { + // ContractAddr is the sdk.AccAddress of the contract, which uniquely defines + // the contract ID and instance ID. The sdk module should maintain a reverse lookup table. + ContractAddr string `json:"contract_addr"` + // Send is an optional amount of coins this contract sends to the called contract + Funds []Coin `json:"funds"` + // Msg is assumed to be a json-encoded message, which will be passed directly + // as `userMsg` when calling `Handle` on the above-defined contract + Msg []byte `json:"msg"` +} + +// InstantiateMsg will create a new contract instance from a previously uploaded CodeID. +// This allows one contract to spawn "sub-contracts". +type InstantiateMsg struct { + // Admin (optional) may be set here to allow future migrations from this address + Admin string `json:"admin,omitempty"` + // CodeID is the reference to the wasm byte code as used by the Cosmos-SDK + CodeID uint64 `json:"code_id"` + // Send is an optional amount of coins this contract sends to the called contract + Funds []Coin `json:"funds"` + // Label is optional metadata to be stored with a contract instance. + Label string `json:"label"` + // Msg is assumed to be a json-encoded message, which will be passed directly + // as `userMsg` when calling `Instantiate` on a new contract with the above-defined CodeID + Msg []byte `json:"msg"` +} + +// Instantiate2Msg will create a new contract instance from a previously uploaded CodeID +// using the predictable address derivation. +type Instantiate2Msg struct { + // Admin (optional) may be set here to allow future migrations from this address + Admin string `json:"admin,omitempty"` + // CodeID is the reference to the wasm byte code as used by the Cosmos-SDK + CodeID uint64 `json:"code_id"` + // Send is an optional amount of coins this contract sends to the called contract + Funds []Coin `json:"funds"` + // Label is optional metadata to be stored with a contract instance. + Label string `json:"label"` + // Msg is assumed to be a json-encoded message, which will be passed directly + // as `userMsg` when calling `Instantiate` on a new contract with the above-defined CodeID + Msg []byte `json:"msg"` + Salt []byte `json:"salt"` +} + +// MigrateMsg will migrate an existing contract from it's current wasm code (logic) +// to another previously uploaded wasm code. It requires the calling contract to be +// listed as "admin" of the contract to be migrated. +type MigrateMsg struct { + // ContractAddr is the sdk.AccAddress of the target contract, to migrate. + ContractAddr string `json:"contract_addr"` + // Msg is assumed to be a json-encoded message, which will be passed directly + // as `userMsg` when calling `Migrate` on the above-defined contract + Msg []byte `json:"msg"` + // NewCodeID is the reference to the wasm byte code for the new logic to migrate to + NewCodeID uint64 `json:"new_code_id"` +} + +// UpdateAdminMsg is the Go counterpart of WasmMsg::UpdateAdmin +// (https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta5/packages/std/src/results/cosmos_msg.rs#L158-L160). +type UpdateAdminMsg struct { + // Admin is the sdk.AccAddress of the new admin. + Admin string `json:"admin"` + // ContractAddr is the sdk.AccAddress of the target contract. + ContractAddr string `json:"contract_addr"` +} + +// ClearAdminMsg is the Go counterpart of WasmMsg::ClearAdmin +// (https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta5/packages/std/src/results/cosmos_msg.rs#L158-L160). +type ClearAdminMsg struct { + // ContractAddr is the sdk.AccAddress of the target contract. + ContractAddr string `json:"contract_addr"` +} + +// The message types of the wasm module. +// +// See https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto +type WasmMsg struct { + // Dispatches a call to another contract at a known address (with known ABI). + // + // This is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address. + Execute *ExecuteMsg `json:"execute,omitempty"` + // Instantiates a new contracts from previously uploaded Wasm code. + // + // The contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2. + // + // This is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address. + Instantiate *InstantiateMsg `json:"instantiate,omitempty"` + // Instantiates a new contracts from previously uploaded Wasm code using a predictable address derivation algorithm implemented in [`cosmwasm_std::instantiate2_address`]. + // + // This is translated to a [MsgInstantiateContract2](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L73-L96). `sender` is automatically filled with the current contract's address. `fix_msg` is automatically set to false. + Instantiate2 *Instantiate2Msg `json:"instantiate2,omitempty"` + // Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior. + // + // Only the contract admin (as defined in wasmd), if any, is able to make this call. + // + // This is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address. + Migrate *MigrateMsg `json:"migrate,omitempty"` + // Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract. + UpdateAdmin *UpdateAdminMsg `json:"update_admin,omitempty"` + // Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract. + ClearAdmin *ClearAdminMsg `json:"clear_admin,omitempty"` +} + +// Coin is a string representation of the sdk.Coin type (more portable than sdk.Int) +type Coin struct { + Amount string `json:"amount"` // string encoing of decimal value, eg. "12.3456" + Denom string `json:"denom"` // type, eg. "ATOM" +} From 3730cb8b172e7c035eb87a3b7f2bf04f6b15aa87 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Fri, 10 Nov 2023 10:05:23 +0100 Subject: [PATCH 05/10] Fix top-level accronym replacement --- packages/go-gen/src/main.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/go-gen/src/main.rs b/packages/go-gen/src/main.rs index 0811fbed6a..84ff3932d9 100644 --- a/packages/go-gen/src/main.rs +++ b/packages/go-gen/src/main.rs @@ -21,16 +21,15 @@ fn main() -> Result<()> { /// Generates the Go code for the given schema fn generate_go(root: RootSchema) -> Result { - let title = replace_acronyms( - root.schema - .metadata - .as_ref() - .and_then(|m| m.title.as_ref()) - .context("failed to get type name")?, - ); + let title = root + .schema + .metadata + .as_ref() + .and_then(|m| m.title.as_ref()) + .context("failed to get type name")?; let mut types = vec![]; - build_type(&title, &root.schema, &mut types) + build_type(title, &root.schema, &mut types) .with_context(|| format!("failed to generate {title}"))?; // go through additional definitions From bd805923d1b0e1891939261188afebe12ea05d5f Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Fri, 10 Nov 2023 10:05:52 +0100 Subject: [PATCH 06/10] Add correct accronym replacement test --- packages/go-gen/src/main.rs | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/go-gen/src/main.rs b/packages/go-gen/src/main.rs index 84ff3932d9..1a4f8278f2 100644 --- a/packages/go-gen/src/main.rs +++ b/packages/go-gen/src/main.rs @@ -472,4 +472,56 @@ mod tests { }"#, ); } + + #[test] + fn accronym_replacement_works() { + #[cw_serde] + struct IbcStruct { + a: IbcSubStruct, + b: IbcSubEnum, + } + #[cw_serde] + enum IbcEnum { + A(IbcSubStruct), + B(IbcSubEnum), + } + #[cw_serde] + struct IbcSubStruct {} + #[cw_serde] + enum IbcSubEnum { + A(String), + } + + let code = generate_go(cosmwasm_schema::schema_for!(IbcStruct)).unwrap(); + assert_code_eq( + code, + r#" + type IBCStruct struct { + A IBCSubStruct `json:"a"` + B IBCSubEnum `json:"b"` + } + type IBCSubEnum struct { + A string `json:"a,omitempty"` + } + type IBCSubStruct struct { + } + "#, + ); + + let code = generate_go(cosmwasm_schema::schema_for!(IbcEnum)).unwrap(); + assert_code_eq( + code, + r#" + type IBCEnum struct { + A *IBCSubStruct `json:"a,omitempty"` + B *IBCSubEnum `json:"b,omitempty"` + } + type IBCSubEnum struct { + A string `json:"a,omitempty"` + } + type IBCSubStruct struct { + } + "#, + ); + } } From e571f0507e744fd36cecdb35422adcaf9036c6db Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Fri, 10 Nov 2023 10:15:32 +0100 Subject: [PATCH 07/10] Add signed custom types --- packages/go-gen/src/schema.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/go-gen/src/schema.rs b/packages/go-gen/src/schema.rs index cd17fe16f8..9ee6a8ffcf 100644 --- a/packages/go-gen/src/schema.rs +++ b/packages/go-gen/src/schema.rs @@ -261,11 +261,15 @@ pub fn custom_type_of(ty: &str) -> Option<&str> { match ty { "Uint64" => Some("string"), "Uint128" => Some("string"), + "Int64" => Some("string"), + "Int128" => Some("string"), "Binary" => Some("[]byte"), "HexBinary" => Some("Checksum"), "Addr" => Some("string"), "Decimal" => Some("string"), "Decimal256" => Some("string"), + "SignedDecimal" => Some("string"), + "SignedDecimal256" => Some("string"), "Timestamp" => Some("uint64"), _ => None, } From 669361d1f2831de467356dbd628ddfbb5345f252 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Fri, 10 Nov 2023 11:20:32 +0100 Subject: [PATCH 08/10] Add optional json annotations for custom types --- packages/go-gen/src/go.rs | 30 +++++++++ packages/go-gen/src/main.rs | 29 ++++++++- packages/go-gen/src/schema.rs | 62 +++++++++++-------- packages/go-gen/tests/cosmwasm_std__IbcMsg.go | 2 +- 4 files changed, 96 insertions(+), 27 deletions(-) diff --git a/packages/go-gen/src/go.rs b/packages/go-gen/src/go.rs index faead10df9..a854c0fb67 100644 --- a/packages/go-gen/src/go.rs +++ b/packages/go-gen/src/go.rs @@ -50,6 +50,9 @@ impl Display for GoField { self.ty, self.rust_name )?; + if let Some(annotations) = &self.ty.json_annotations { + write!(f, ",{}", annotations)?; + } if self.ty.is_nullable { f.write_str(",omitempty")?; } @@ -64,6 +67,8 @@ pub struct GoType { /// 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, + /// Additional json annotations, if any + pub json_annotations: Option, } impl GoType { @@ -123,10 +128,12 @@ mod tests { let ty = GoType { name: "string".to_string(), is_nullable: true, + json_annotations: None, }; let ty2 = GoType { name: "string".to_string(), is_nullable: false, + json_annotations: None, }; assert_eq!(format!("{}", ty), "string"); assert_eq!(format!("{}", ty2), "string"); @@ -134,11 +141,13 @@ mod tests { let ty = GoType { name: "FooBar".to_string(), is_nullable: true, + json_annotations: None, }; assert_eq!(format!("{}", ty), "*FooBar"); let ty = GoType { name: "FooBar".to_string(), is_nullable: false, + json_annotations: None, }; assert_eq!(format!("{}", ty), "FooBar"); } @@ -151,6 +160,7 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: true, + json_annotations: None, }, }; assert_eq!( @@ -164,6 +174,7 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: false, + json_annotations: None, }, }; assert_eq!(format!("{}", field), "FooBar string `json:\"foo_bar\"`"); @@ -174,6 +185,7 @@ mod tests { ty: GoType { name: "FooBar".to_string(), is_nullable: true, + json_annotations: None, }, }; assert_eq!( @@ -190,12 +202,28 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: true, + json_annotations: None, }, }; assert_eq!( format!("{}", field), "// foo_bar is a test field\nFooBar string `json:\"foo_bar,omitempty\"`" ); + + // now with additional json annotations + let field = GoField { + rust_name: "foo_bar".to_string(), + docs: None, + ty: GoType { + name: "uint64".to_string(), + is_nullable: true, + json_annotations: Some("string".to_string()), + }, + }; + assert_eq!( + format!("{}", field), + "FooBar uint64 `json:\"foo_bar,string,omitempty\"`" + ); } #[test] @@ -209,6 +237,7 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: true, + json_annotations: None, }, }], }; @@ -226,6 +255,7 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: true, + json_annotations: None, }, }], }; diff --git a/packages/go-gen/src/main.rs b/packages/go-gen/src/main.rs index 1a4f8278f2..b44459ac9c 100644 --- a/packages/go-gen/src/main.rs +++ b/packages/go-gen/src/main.rs @@ -175,7 +175,11 @@ pub fn build_enum_variant( ); // we can unwrap here, because we checked the length above let (name, schema) = properties.first_key_value().unwrap(); - let GoType { name: ty, .. } = schema_object_type( + let GoType { + name: ty, + json_annotations, + .. + } = schema_object_type( schema.object()?, TypeContext::new(enum_name, name), additional_structs, @@ -187,6 +191,7 @@ pub fn build_enum_variant( ty: GoType { name: ty, is_nullable: true, // always nullable + json_annotations, }, }) } @@ -524,4 +529,26 @@ mod tests { "#, ); } + + #[test] + fn timestamp_works() { + use cosmwasm_std::Timestamp; + + #[cw_serde] + struct A { + a: Timestamp, + b: Option, + } + + let code = generate_go(cosmwasm_schema::schema_for!(A)).unwrap(); + assert_code_eq( + code, + r#" + type A struct { + A uint64 `json:"a,string"` + B uint64 `json:"b,string,omitempty"` + } + "#, + ); + } } diff --git a/packages/go-gen/src/schema.rs b/packages/go-gen/src/schema.rs index 9ee6a8ffcf..e166d54522 100644 --- a/packages/go-gen/src/schema.rs +++ b/packages/go-gen/src/schema.rs @@ -39,7 +39,9 @@ pub fn schema_object_type( let mut is_nullable = is_null(schema); // if it has a title, use that - let ty = if let Some(title) = schema.metadata.as_ref().and_then(|m| m.title.as_ref()) { + let (ty, json_annotations) = 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 { // if it has a reference, strip the path and use that @@ -50,17 +52,25 @@ pub fn schema_object_type( .expect("split should always return at least one item"), )) } else if let Some(t) = &schema.instance_type { - type_from_instance_type(schema, type_context, t, additional_structs)? + ( + type_from_instance_type(schema, type_context, t, additional_structs)?, + None, + ) } else if let Some(subschemas) = schema.subschemas.as_ref().and_then(|s| s.any_of.as_ref()) { // check if one of them is null - let nullable = nullable_type(subschemas)?; + let nullable: Option<&SchemaObject> = nullable_type(subschemas)?; if let Some(non_null) = nullable { ensure!(subschemas.len() == 2, "multiple subschemas in anyOf"); is_nullable = true; // extract non-null type - let GoType { name, .. } = - schema_object_type(non_null, type_context, additional_structs)?; - replace_custom_type(&name) + let GoType { + name, + json_annotations, + .. + } = schema_object_type(non_null, type_context, additional_structs)?; + // let (ty, annotations) = replace_custom_type(&name); + // (ty, annotations.or(json_annotations)) + (name, json_annotations) } else { subschema_type(subschemas, type_context, additional_structs) .context("failed to get type of anyOf subschemas")? @@ -79,6 +89,7 @@ pub fn schema_object_type( Ok(GoType { name: ty, is_nullable, + json_annotations, // TODO: implement }) } @@ -197,11 +208,11 @@ pub fn type_from_instance_type( // 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 { + if item_type.is_nullable { format!("[]*{}", item_type.name) } else { format!("[]{}", item_type.name) - }) + } } else { unreachable!("instance type should be one of the above") }) @@ -233,7 +244,7 @@ pub fn subschema_type( subschemas: &[Schema], type_context: TypeContext, additional_structs: &mut Vec, -) -> Result { +) -> Result<(String, Option)> { ensure!( subschemas.len() == 1, "multiple subschemas are not supported" @@ -257,26 +268,27 @@ pub fn documentation(schema: &SchemaObject) -> Option { /// Maps special types to their Go equivalents. /// If the given type is not a special type, returns `None`. -pub fn custom_type_of(ty: &str) -> Option<&str> { +/// Otherwise, returns a tuple of the Go type name and additional json annotations. +pub fn custom_type_of(ty: &str) -> Option<(&str, Option<&str>)> { match ty { - "Uint64" => Some("string"), - "Uint128" => Some("string"), - "Int64" => Some("string"), - "Int128" => Some("string"), - "Binary" => Some("[]byte"), - "HexBinary" => Some("Checksum"), - "Addr" => Some("string"), - "Decimal" => Some("string"), - "Decimal256" => Some("string"), - "SignedDecimal" => Some("string"), - "SignedDecimal256" => Some("string"), - "Timestamp" => Some("uint64"), + "Uint64" => Some(("string", None)), + "Uint128" => Some(("string", None)), + "Int64" => Some(("string", None)), + "Int128" => Some(("string", None)), + "Binary" => Some(("[]byte", None)), + "HexBinary" => Some(("Checksum", None)), + "Addr" => Some(("string", None)), + "Decimal" => Some(("string", None)), + "Decimal256" => Some(("string", None)), + "SignedDecimal" => Some(("string", None)), + "SignedDecimal256" => Some(("string", None)), + "Timestamp" => Some(("uint64", Some("string"))), _ => None, } } -pub fn replace_custom_type(ty: &str) -> String { +pub fn replace_custom_type(ty: &str) -> (String, Option) { custom_type_of(ty) - .map(|ty| ty.to_string()) - .unwrap_or_else(|| ty.to_string()) + .map(|(ty, json_annotations)| (ty.to_string(), json_annotations.map(String::from))) + .unwrap_or_else(|| (ty.to_string(), None)) } diff --git a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go index d8165ba957..96989755db 100644 --- a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go +++ b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go @@ -30,7 +30,7 @@ type Coin struct { type IBCTimeout struct { Block *IBCTimeoutBlock `json:"block,omitempty"` // in wasmvm, this does not have "omitempty" // Nanoseconds since UNIX epoch - Timestamp uint64 `json:"timestamp,omitempty"` // wasmvm has a "string" in here too + Timestamp uint64 `json:"timestamp,string,omitempty"` // wasmvm has a "string" in here too } // IBCTimeoutBlock Height is a monotonically increasing data type From ac9d84bf63452deb0cd8d31ee8e0678235967936 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 20 Nov 2023 14:47:05 +0100 Subject: [PATCH 09/10] Revert "Add optional json annotations for custom types" This reverts commit 669361d1f2831de467356dbd628ddfbb5345f252. --- packages/go-gen/src/go.rs | 30 --------- packages/go-gen/src/main.rs | 29 +-------- packages/go-gen/src/schema.rs | 62 ++++++++----------- packages/go-gen/tests/cosmwasm_std__IbcMsg.go | 2 +- 4 files changed, 27 insertions(+), 96 deletions(-) diff --git a/packages/go-gen/src/go.rs b/packages/go-gen/src/go.rs index a854c0fb67..faead10df9 100644 --- a/packages/go-gen/src/go.rs +++ b/packages/go-gen/src/go.rs @@ -50,9 +50,6 @@ impl Display for GoField { self.ty, self.rust_name )?; - if let Some(annotations) = &self.ty.json_annotations { - write!(f, ",{}", annotations)?; - } if self.ty.is_nullable { f.write_str(",omitempty")?; } @@ -67,8 +64,6 @@ pub struct GoType { /// 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, - /// Additional json annotations, if any - pub json_annotations: Option, } impl GoType { @@ -128,12 +123,10 @@ mod tests { let ty = GoType { name: "string".to_string(), is_nullable: true, - json_annotations: None, }; let ty2 = GoType { name: "string".to_string(), is_nullable: false, - json_annotations: None, }; assert_eq!(format!("{}", ty), "string"); assert_eq!(format!("{}", ty2), "string"); @@ -141,13 +134,11 @@ mod tests { let ty = GoType { name: "FooBar".to_string(), is_nullable: true, - json_annotations: None, }; assert_eq!(format!("{}", ty), "*FooBar"); let ty = GoType { name: "FooBar".to_string(), is_nullable: false, - json_annotations: None, }; assert_eq!(format!("{}", ty), "FooBar"); } @@ -160,7 +151,6 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: true, - json_annotations: None, }, }; assert_eq!( @@ -174,7 +164,6 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: false, - json_annotations: None, }, }; assert_eq!(format!("{}", field), "FooBar string `json:\"foo_bar\"`"); @@ -185,7 +174,6 @@ mod tests { ty: GoType { name: "FooBar".to_string(), is_nullable: true, - json_annotations: None, }, }; assert_eq!( @@ -202,28 +190,12 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: true, - json_annotations: None, }, }; assert_eq!( format!("{}", field), "// foo_bar is a test field\nFooBar string `json:\"foo_bar,omitempty\"`" ); - - // now with additional json annotations - let field = GoField { - rust_name: "foo_bar".to_string(), - docs: None, - ty: GoType { - name: "uint64".to_string(), - is_nullable: true, - json_annotations: Some("string".to_string()), - }, - }; - assert_eq!( - format!("{}", field), - "FooBar uint64 `json:\"foo_bar,string,omitempty\"`" - ); } #[test] @@ -237,7 +209,6 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: true, - json_annotations: None, }, }], }; @@ -255,7 +226,6 @@ mod tests { ty: GoType { name: "string".to_string(), is_nullable: true, - json_annotations: None, }, }], }; diff --git a/packages/go-gen/src/main.rs b/packages/go-gen/src/main.rs index b44459ac9c..1a4f8278f2 100644 --- a/packages/go-gen/src/main.rs +++ b/packages/go-gen/src/main.rs @@ -175,11 +175,7 @@ pub fn build_enum_variant( ); // we can unwrap here, because we checked the length above let (name, schema) = properties.first_key_value().unwrap(); - let GoType { - name: ty, - json_annotations, - .. - } = schema_object_type( + let GoType { name: ty, .. } = schema_object_type( schema.object()?, TypeContext::new(enum_name, name), additional_structs, @@ -191,7 +187,6 @@ pub fn build_enum_variant( ty: GoType { name: ty, is_nullable: true, // always nullable - json_annotations, }, }) } @@ -529,26 +524,4 @@ mod tests { "#, ); } - - #[test] - fn timestamp_works() { - use cosmwasm_std::Timestamp; - - #[cw_serde] - struct A { - a: Timestamp, - b: Option, - } - - let code = generate_go(cosmwasm_schema::schema_for!(A)).unwrap(); - assert_code_eq( - code, - r#" - type A struct { - A uint64 `json:"a,string"` - B uint64 `json:"b,string,omitempty"` - } - "#, - ); - } } diff --git a/packages/go-gen/src/schema.rs b/packages/go-gen/src/schema.rs index e166d54522..9ee6a8ffcf 100644 --- a/packages/go-gen/src/schema.rs +++ b/packages/go-gen/src/schema.rs @@ -39,9 +39,7 @@ pub fn schema_object_type( let mut is_nullable = is_null(schema); // if it has a title, use that - let (ty, json_annotations) = if let Some(title) = - schema.metadata.as_ref().and_then(|m| m.title.as_ref()) - { + 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 { // if it has a reference, strip the path and use that @@ -52,25 +50,17 @@ pub fn schema_object_type( .expect("split should always return at least one item"), )) } else if let Some(t) = &schema.instance_type { - ( - type_from_instance_type(schema, type_context, t, additional_structs)?, - None, - ) + type_from_instance_type(schema, type_context, t, additional_structs)? } else if let Some(subschemas) = schema.subschemas.as_ref().and_then(|s| s.any_of.as_ref()) { // check if one of them is null - let nullable: Option<&SchemaObject> = nullable_type(subschemas)?; + let nullable = nullable_type(subschemas)?; if let Some(non_null) = nullable { ensure!(subschemas.len() == 2, "multiple subschemas in anyOf"); is_nullable = true; // extract non-null type - let GoType { - name, - json_annotations, - .. - } = schema_object_type(non_null, type_context, additional_structs)?; - // let (ty, annotations) = replace_custom_type(&name); - // (ty, annotations.or(json_annotations)) - (name, json_annotations) + let GoType { name, .. } = + schema_object_type(non_null, type_context, additional_structs)?; + replace_custom_type(&name) } else { subschema_type(subschemas, type_context, additional_structs) .context("failed to get type of anyOf subschemas")? @@ -89,7 +79,6 @@ pub fn schema_object_type( Ok(GoType { name: ty, is_nullable, - json_annotations, // TODO: implement }) } @@ -208,11 +197,11 @@ pub fn type_from_instance_type( // 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 - if item_type.is_nullable { + replace_custom_type(&if item_type.is_nullable { format!("[]*{}", item_type.name) } else { format!("[]{}", item_type.name) - } + }) } else { unreachable!("instance type should be one of the above") }) @@ -244,7 +233,7 @@ pub fn subschema_type( subschemas: &[Schema], type_context: TypeContext, additional_structs: &mut Vec, -) -> Result<(String, Option)> { +) -> Result { ensure!( subschemas.len() == 1, "multiple subschemas are not supported" @@ -268,27 +257,26 @@ pub fn documentation(schema: &SchemaObject) -> Option { /// Maps special types to their Go equivalents. /// If the given type is not a special type, returns `None`. -/// Otherwise, returns a tuple of the Go type name and additional json annotations. -pub fn custom_type_of(ty: &str) -> Option<(&str, Option<&str>)> { +pub fn custom_type_of(ty: &str) -> Option<&str> { match ty { - "Uint64" => Some(("string", None)), - "Uint128" => Some(("string", None)), - "Int64" => Some(("string", None)), - "Int128" => Some(("string", None)), - "Binary" => Some(("[]byte", None)), - "HexBinary" => Some(("Checksum", None)), - "Addr" => Some(("string", None)), - "Decimal" => Some(("string", None)), - "Decimal256" => Some(("string", None)), - "SignedDecimal" => Some(("string", None)), - "SignedDecimal256" => Some(("string", None)), - "Timestamp" => Some(("uint64", Some("string"))), + "Uint64" => Some("string"), + "Uint128" => Some("string"), + "Int64" => Some("string"), + "Int128" => Some("string"), + "Binary" => Some("[]byte"), + "HexBinary" => Some("Checksum"), + "Addr" => Some("string"), + "Decimal" => Some("string"), + "Decimal256" => Some("string"), + "SignedDecimal" => Some("string"), + "SignedDecimal256" => Some("string"), + "Timestamp" => Some("uint64"), _ => None, } } -pub fn replace_custom_type(ty: &str) -> (String, Option) { +pub fn replace_custom_type(ty: &str) -> String { custom_type_of(ty) - .map(|(ty, json_annotations)| (ty.to_string(), json_annotations.map(String::from))) - .unwrap_or_else(|| (ty.to_string(), None)) + .map(|ty| ty.to_string()) + .unwrap_or_else(|| ty.to_string()) } diff --git a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go index 96989755db..d8165ba957 100644 --- a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go +++ b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go @@ -30,7 +30,7 @@ type Coin struct { type IBCTimeout struct { Block *IBCTimeoutBlock `json:"block,omitempty"` // in wasmvm, this does not have "omitempty" // Nanoseconds since UNIX epoch - Timestamp uint64 `json:"timestamp,string,omitempty"` // wasmvm has a "string" in here too + Timestamp uint64 `json:"timestamp,omitempty"` // wasmvm has a "string" in here too } // IBCTimeoutBlock Height is a monotonically increasing data type From f9d365c090d663f46c71b967810cfa683d6afebf Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 20 Nov 2023 16:11:41 +0100 Subject: [PATCH 10/10] Use new Uint64 wasmvm types --- packages/go-gen/src/main.rs | 22 +++++++++++++++++++ packages/go-gen/src/schema.rs | 6 ++--- packages/go-gen/tests/cosmwasm_std__IbcMsg.go | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/go-gen/src/main.rs b/packages/go-gen/src/main.rs index 1a4f8278f2..ab853a47c6 100644 --- a/packages/go-gen/src/main.rs +++ b/packages/go-gen/src/main.rs @@ -524,4 +524,26 @@ mod tests { "#, ); } + + #[test] + fn timestamp_works() { + use cosmwasm_std::Timestamp; + + #[cw_serde] + struct A { + a: Timestamp, + b: Option, + } + + let code = generate_go(cosmwasm_schema::schema_for!(A)).unwrap(); + assert_code_eq( + code, + r#" + type A struct { + A Uint64 `json:"a"` + B *Uint64 `json:"b,omitempty"` + } + "#, + ); + } } diff --git a/packages/go-gen/src/schema.rs b/packages/go-gen/src/schema.rs index 9ee6a8ffcf..97fe28bc5d 100644 --- a/packages/go-gen/src/schema.rs +++ b/packages/go-gen/src/schema.rs @@ -259,9 +259,9 @@ pub fn documentation(schema: &SchemaObject) -> Option { /// If the given type is not a special type, returns `None`. pub fn custom_type_of(ty: &str) -> Option<&str> { match ty { - "Uint64" => Some("string"), + "Uint64" => Some("Uint64"), "Uint128" => Some("string"), - "Int64" => Some("string"), + "Int64" => Some("Int64"), "Int128" => Some("string"), "Binary" => Some("[]byte"), "HexBinary" => Some("Checksum"), @@ -270,7 +270,7 @@ pub fn custom_type_of(ty: &str) -> Option<&str> { "Decimal256" => Some("string"), "SignedDecimal" => Some("string"), "SignedDecimal256" => Some("string"), - "Timestamp" => Some("uint64"), + "Timestamp" => Some("Uint64"), _ => None, } } diff --git a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go index d8165ba957..e9699c1161 100644 --- a/packages/go-gen/tests/cosmwasm_std__IbcMsg.go +++ b/packages/go-gen/tests/cosmwasm_std__IbcMsg.go @@ -30,7 +30,7 @@ type Coin struct { type IBCTimeout struct { Block *IBCTimeoutBlock `json:"block,omitempty"` // in wasmvm, this does not have "omitempty" // Nanoseconds since UNIX epoch - Timestamp uint64 `json:"timestamp,omitempty"` // wasmvm has a "string" in here too + Timestamp *Uint64 `json:"timestamp,omitempty"` } // IBCTimeoutBlock Height is a monotonically increasing data type