-
Notifications
You must be signed in to change notification settings - Fork 110
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
[x/programs] Serialize params on the Rust side #979
Conversation
case float64: | ||
// json unmarshal converts to float64 | ||
cp = append(cp, Parameter{Value: uint64(v), Type: param.Type}) | ||
case int: | ||
if v < 0 { | ||
return nil, fmt.Errorf("%w: %s", errors.New("negative value"), param.Type) | ||
} | ||
cp = append(cp, Parameter{Value: uint64(v), Type: param.Type}) | ||
case string: | ||
number, err := strconv.ParseUint(v, 10, 64) | ||
if err != nil { | ||
return nil, fmt.Errorf("%w: %s", ErrFailedParamTypeCast, param.Type) | ||
} | ||
cp = append(cp, Parameter{Value: number, Type: param.Type}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we were not using this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure there's more unused code lurking too
f5d9746
to
e398f9a
Compare
x/programs/cmd/simulator/src/lib.rs
Outdated
impl Serialize for Param { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: serde::Serializer, | ||
{ | ||
let mut val = serializer.serialize_struct("param", 2)?; | ||
match self { | ||
Param::U64(num) => { | ||
val.serialize_field("type", "u64")?; | ||
val.serialize_field("value", &b64.encode(num.to_le_bytes()))?; | ||
} | ||
Param::String(text) => { | ||
val.serialize_field("type", "string")?; | ||
val.serialize_field("value", &b64.encode(text))?; | ||
} | ||
Param::Id(id) => { | ||
val.serialize_field("type", "id")?; | ||
let id = serde_json::to_vec(&id).map_err(serde::ser::Error::custom)?; | ||
let id = &id[1..id.len() - 1]; // remove quotes | ||
val.serialize_field("value", &b64.encode(id))?; | ||
} | ||
Param::Key(key) => { | ||
let (key, algo) = match key { | ||
Key::Ed25519(key) => (key, "ed25519"), | ||
Key::Secp256r1(key) => (key, "secp256r1"), | ||
}; | ||
val.serialize_field("type", algo)?; | ||
val.serialize_field("value", &b64.encode(key))?; | ||
} | ||
} | ||
val.end() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could not find a way to do all of this with the serde attribute macros in a nice way. It would probably be more verbose if we would have used #[serde(serialize_with = "...")]
in order to support all of the variants, and it's more readable that way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand why everything needs to be base64 encoded. If I had to guess, it's because it's easier to decode everything on the Go side, but you should just be able to switch on the type
tag and only base64 decode where necessary. But IMO, it's an extra step that's unnecessary (and adds a little complexity).
Also, I think it would just be beneficial to use an intermediate type here:
#[derive(Serialize)]
#[serde(rename_all = "lowercase", tag = "type", content = "value")]
enum StringParam {
U64(String),
String(String),
Id(String),
#[serde(untagged)]
Key(Key),
}
If you want, you can define the type inside the Serialize
implementation, do a From
conversion, then just serialize the intermediate type. This should all get optimized away by the compiler, especially when the type is only used in one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's Go making choices for us when dealing with the []byte
type, see https://pkg.go.dev/encoding/json#Marshal
Array and slice values encode as JSON arrays, except that []byte encodes as a base64-encoded string, and a nil slice encodes as the null JSON value.
I found online that people were forking the package to strip out the base64 part but I don't think that we should do that.
ee4c844
to
f64e564
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving this as a comment review since I'll need to re-approve anyway when the merge conflicts are resolved
case float64: | ||
// json unmarshal converts to float64 | ||
cp = append(cp, Parameter{Value: uint64(v), Type: param.Type}) | ||
case int: | ||
if v < 0 { | ||
return nil, fmt.Errorf("%w: %s", errors.New("negative value"), param.Type) | ||
} | ||
cp = append(cp, Parameter{Value: uint64(v), Type: param.Type}) | ||
case string: | ||
number, err := strconv.ParseUint(v, 10, 64) | ||
if err != nil { | ||
return nil, fmt.Errorf("%w: %s", ErrFailedParamTypeCast, param.Type) | ||
} | ||
cp = append(cp, Parameter{Value: number, Type: param.Type}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure there's more unused code lurking too
x/programs/cmd/simulator/src/lib.rs
Outdated
impl Serialize for Param { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: serde::Serializer, | ||
{ | ||
let mut val = serializer.serialize_struct("param", 2)?; | ||
match self { | ||
Param::U64(num) => { | ||
val.serialize_field("type", "u64")?; | ||
val.serialize_field("value", &b64.encode(num.to_le_bytes()))?; | ||
} | ||
Param::String(text) => { | ||
val.serialize_field("type", "string")?; | ||
val.serialize_field("value", &b64.encode(text))?; | ||
} | ||
Param::Id(id) => { | ||
val.serialize_field("type", "id")?; | ||
let id = serde_json::to_vec(&id).map_err(serde::ser::Error::custom)?; | ||
let id = &id[1..id.len() - 1]; // remove quotes | ||
val.serialize_field("value", &b64.encode(id))?; | ||
} | ||
Param::Key(key) => { | ||
let (key, algo) = match key { | ||
Key::Ed25519(key) => (key, "ed25519"), | ||
Key::Secp256r1(key) => (key, "secp256r1"), | ||
}; | ||
val.serialize_field("type", algo)?; | ||
val.serialize_field("value", &b64.encode(key))?; | ||
} | ||
} | ||
val.end() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand why everything needs to be base64 encoded. If I had to guess, it's because it's easier to decode everything on the Go side, but you should just be able to switch on the type
tag and only base64 decode where necessary. But IMO, it's an extra step that's unnecessary (and adds a little complexity).
Also, I think it would just be beneficial to use an intermediate type here:
#[derive(Serialize)]
#[serde(rename_all = "lowercase", tag = "type", content = "value")]
enum StringParam {
U64(String),
String(String),
Id(String),
#[serde(untagged)]
Key(Key),
}
If you want, you can define the type inside the Serialize
implementation, do a From
conversion, then just serialize the intermediate type. This should all get optimized away by the compiler, especially when the type is only used in one place.
6684c5b
to
34e99d2
Compare
2883a5e
to
3e31404
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the base-64 stuff is a little convoluted. This is a good step into cleaning things up, but I think there's still more to do.
fn from(val: &'a Id) -> Self { | ||
&val.0 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
{ | ||
match self { | ||
Param::U64(num) => { | ||
Serialize::serialize(&StringParam::U64(b64.encode(num.to_le_bytes())), serializer) | ||
} | ||
Param::String(text) => { | ||
Serialize::serialize(&StringParam::String(b64.encode(text)), serializer) | ||
} | ||
Param::Id(id) => { | ||
let num: &usize = id.into(); | ||
let id = format!("step_{}", num); | ||
Serialize::serialize(&StringParam::Id(b64.encode(id)), serializer) | ||
} | ||
Param::Key(key) => Serialize::serialize(key, serializer), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little more complex than what I had suggested. Also, you really don't need the step_
. In json, the "type"
field will be "id"
, right? that means that step_
is redundant.
Closes #927