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

Serializer unable to serialize nested enums, Error:"top-level serializer supports only maps and structs" #118

Open
Orkking2 opened this issue Jun 11, 2024 · 1 comment

Comments

@Orkking2
Copy link

I was trying to find a way to encode URL parameters into definite enums so there's no guessing if a parameter is valid or not, but the solution I figured out doesn't work :(

Here is the code and its output. As you can see, it works with the JSON serializer and formats the output into a map, but the url_encoder doesn't exhibit the same behaviour?

A possible (dirty) solution to my problem in particular would be to first serialize my custom type into a JSON value and then pass that into the URL serializer. There isn't really a problem with this solution besides being clunky and unintuitive, but I was wondering if the url_encoder could be expanded to include serialization targets that are able to be coerced into struct/map representations. Just like how the JSON output is a dictionary, so too could the url_encoder represent this case as a map.

I'm not super familiar with how serializers work, sorry 😞

use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::to_value;

#[derive(Serialize, Deserialize)]
enum Params {
  EnumA(EnumA),
  EnumB(EnumB),
}

trait IntoParam {
  fn into_param(self) -> Params;
}

// trait FromParam {
//   fn from_param(param: Params) -> Self;
// }

#[derive(Serialize, Deserialize)]
// #[serde(tag = "EnumA")]
enum EnumA {
  VarA,
  VarB,
}

impl IntoParam for EnumA {
  fn into_param(self) -> Params {
    Params::EnumA(self)
  }
}

#[derive(Serialize, Deserialize)]
// #[serde(tag = "EnumB")]
enum EnumB {
  VarC,
  #[serde(untagged)]
  Base(EnumA),
}

impl IntoParam for EnumB {
  fn into_param(self) -> Params {
    Params::EnumB(self)
  }
}

fn main() -> () {
  println!("EnumA::VarA.into_param(), {}", to_value(EnumA::VarA.into_param()).unwrap().to_string());
  println!("EnumA::VarB.into_param(), {}", to_value(EnumA::VarB.into_param()).unwrap().to_string());
  println!("EnumB::VarC.into_param(), {}", to_value(EnumB::VarC.into_param()).unwrap().to_string());
  println!("EnumB::Base(EnumA::VarA).into_param(), {}", to_value(EnumB::Base(EnumA::VarA).into_param()).unwrap().to_string());
  
  let client = Client::new();
  let request = client.get("https://google.com").query(&EnumB::Base(EnumA::VarA).into_param()).build().unwrap();
  println!("Reqwest url, {}", request.url())
}

EnumA::VarA.into_param(), {"EnumA":"VarA"}
EnumA::VarB.into_param(), {"EnumA":"VarB"}
EnumB::VarC.into_param(), {"EnumB":"VarC"}
EnumB::Base(EnumA::VarA).into_param(), {"EnumB":"VarA"}
thread 'main' panicked at src/scripts/analyze_endpoints.rs:58:104:
called Result::unwrap() on an Err value: reqwest::Error { kind: Builder, source: Custom("top-level serializer supports only maps and structs") }
stack backtrace:
0: rust_begin_unwind
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/std/src/panicking.rs:645:5
1: core::panicking::panic_fmt
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/panicking.rs:72:14
2: core::result::unwrap_failed
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/result.rs:1654:5
3: core::result::Result<T,E>::unwrap
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/result.rs:1077:23
4: analyze_endpoints::main
at ./src/scripts/analyze_endpoints.rs:58:17
5: core::ops::function::FnOnce::call_once
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with RUST_BACKTRACE=full for a verbose backtrace.

@Orkking2
Copy link
Author

Orkking2 commented Jun 11, 2024

Note that the following executes with no errors and produces the indented output:

use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::to_value;

#[derive(Serialize, Deserialize)]
enum Params {
  EnumA(EnumA),
  EnumB(EnumB),
}

trait IntoParam {
  fn into_param(self) -> Params;
}

// trait FromParam {
//   fn from_param(param: Params) -> Self;
// }

#[derive(Serialize, Deserialize)]
// #[serde(tag = "EnumA")]
enum EnumA {
  VarA,
  VarB,
}

impl IntoParam for EnumA {
  fn into_param(self) -> Params {
    Params::EnumA(self)
  }
}

#[derive(Serialize, Deserialize)]
// #[serde(tag = "EnumB")]
enum EnumB {
  VarC,
  #[serde(untagged)]
  Base(EnumA),
}

impl IntoParam for EnumB {
  fn into_param(self) -> Params {
    Params::EnumB(self)
  }
}

fn main() -> () {
  println!("EnumA::VarA.into_param(), {}", to_value(EnumA::VarA.into_param()).unwrap().to_string());
  println!("EnumA::VarB.into_param(), {}", to_value(EnumA::VarB.into_param()).unwrap().to_string());
  println!("EnumB::VarC.into_param(), {}", to_value(EnumB::VarC.into_param()).unwrap().to_string());
  println!("EnumB::Base(EnumA::VarA).into_param(), {}", to_value(EnumB::Base(EnumA::VarA).into_param()).unwrap().to_string());
  
  let client = Client::new();
  let request = client.get("https://google.com").query(&to_value(EnumB::Base(EnumA::VarA).into_param()).unwrap()).build().unwrap();
  println!("Reqwest url, {}", request.url())
}

EnumA::VarA.into_param(), {"EnumA":"VarA"}
EnumA::VarB.into_param(), {"EnumA":"VarB"}
EnumB::VarC.into_param(), {"EnumB":"VarC"}
EnumB::Base(EnumA::VarA).into_param(), {"EnumB":"VarA"}
Reqwest url, https://google.com/?EnumB=VarA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant