I'm learning Rust, but I'm not sure about the most elegant or "rusty" way of doing some things:
I'm retrieving data from an API that, on some endpoints returns a JSON object ({ value: "resource A" }), but in other occasions, it returns a JSON object wrapped by another object ({ error: false, data: { value: "resource A" } }).
I'm using Restson to retrieve that data.
My question is: what is the most elegant way to deal with different responses? I don't know how to use some kind of abstract Response that could accept both kind of JSON responses.
I mean, in this case I'm implementing 2 traits, but both of them have the same content, so, to me, it smells like there is something wrong there.
This is a simplified example, so typos could exist:
use restson::{RestPath, RestClient, Error};
#[derive(Debug, Serialize, Deserialize)]
struct Response<T> {
error: bool,
data: T
}
#[derive(Debug, Serialize, Deserialize)]
struct ResourceA {
value: String,
}
// HERE: How do I remove this duplication?
impl<'a> RestPath<(&'a str, String)> for ResourceA {
fn get_path(params: (i8, String, &str)) -> Result<String, Error> {
let (resource, id) = params;
Ok(format!("{}/{}", resource, id))
}
}
impl<'a, T> RestPath<(&'a str, String)> for Response<T> {
fn get_path(params: (&str, String)) -> Result<String, Error> {
let (resource, id) = params;
Ok(format!("{}/{}", resource, id))
}
}
pub struct Client {
client: RestClient,
}
impl Client {
pub fn new() -> Client {
Client {
client: RestClient::new("http://my.client").unwrap(),
}
}
pub fn get_resource_a(&mut self, id: String) -> ResourceA {
let params = ("a", id);
self.client.get(params).unwrap()
}
pub fn get_resource_a2(&mut self, id: String) -> ResourceA {
let params = ("a2", id);
let response: Response<ResourceA> = self.api_client.get(params).unwrap();
response.data
}
}
You have a response with two variants, so an enum based solution may be considered:
#[derive(Debug, Serialize, Deserialize)]
struct ResourceA {
value: String,
}
#[derive(Debug, Serialize, Deserialize]
#[serde(untagged)]
pub enum Response {
ErrAndValue{error: bool, data: ResourceA},
Simple(ResourceA),
}
I've used the untagged annotation to conform the json format:
{ value: "resource A" }
{ error: false, data: { value: "resource A" } }
Then your RestPath impl reduce to:
impl<'a> RestPath<(&'a str, String)> for Response {
fn get_path(params: (&str, String)) -> Result<String, Error> {
let (resource, id) = params;
Ok(format!("{}/{}", resource, id))
}
}
Related
I wonder whether there's a way to preserve the original String using serde_json? Consider this example:
#[derive(Debug, Serialize, Deserialize)]
struct User {
#[serde(skip)]
pub raw: String,
pub id: u64,
pub login: String,
}
{
"id": 123,
"login": "johndoe"
}
My structure would end up containing such values:
User {
raw: String::from(r#"{"id": 123,"login": "johndoe"}"#),
id: 1,
login: String::from("johndoe")
}
Currently, I'm doing that by deserializing into Value, then deserializing this value into the User structure and assigning Value to the raw field, but that doesn't seem right, perhaps there's a better way to do so?
This solution uses the RawValue type from serde_json to first get the original input string. Then a new Deserializer is created from that String to deserialize the User type.
This solution can work with readers, by using Box<serde_json::value::RawValue> as an intermediary type and it can also work with struct which borrow from the input, by using &'de serde_json::value::RawValue as the intermediary. You can test it in the solution by (un-)commenting the borrow field.
use std::marker::PhantomData;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(remote = "Self")]
struct User<'a> {
#[serde(skip)]
pub raw: String,
pub id: u64,
pub login: String,
// Test for borrowing input data
// pub borrow: &'a str,
#[serde(skip)]
pub ignored: PhantomData<&'a ()>,
}
impl serde::Serialize for User<'_> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Self::serialize(self, serializer)
}
}
impl<'a, 'de> serde::Deserialize<'de> for User<'a>
where
'de: 'a,
{
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::Error;
// Deserializing a `&'a RawValue` would also work here
// but then you loose support for deserializing from readers
let raw: Box<serde_json::value::RawValue> = Box::deserialize(deserializer)?;
// Use this line instead if you have a struct which borrows from the input
// let raw = <&'de serde_json::value::RawValue>::deserialize(deserializer)?;
let mut raw_value_deserializer = serde_json::Deserializer::from_str(raw.get());
let mut user =
User::deserialize(&mut raw_value_deserializer).map_err(|err| D::Error::custom(err))?;
user.raw = raw.get().to_string();
Ok(user)
}
}
fn main() {
// Test serialization
let u = User {
raw: String::new(),
id: 456,
login: "USERNAME".to_string(),
// Test for borrowing input data
// borrow: "foobar",
ignored: PhantomData,
};
let json = serde_json::to_string(&u).unwrap();
println!("{}", json);
// Test deserialization
let u2: User = serde_json::from_str(&json).unwrap();
println!("{:#?}", u2);
}
Test on the Playground.
I am trying to add the Deserialize attribute to struct A. Content's items contain Button like CallButton like below
let mut buttons: Vec<Box<dyn erased_serde::Serialize>> = Vec::new();
buttons.push(Box::new(
CallButton::new("LABEL".to_string())
.set_label("CALL LABEL".to_string())
.set_msg("MESSAGE".to_string()),
));
How can I make this also deserializable so I can deserialize it? Is there a better way? I think I'm using erased_serde wrong.
use serde::{Deserialize, Serialize};
#[allow(patterns_in_fns_without_body)]
pub trait Button: Serialize {
fn new(label: String) -> Self;
fn set_label(mut self, label: String) -> Self;
fn set_msg(mut self, msg: String) -> Self;
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CallButton {
label: String,
action: String,
phone_number: String,
#[serde(skip_serializing_if = "Option::is_none")]
message_text: Option<String>,
}
impl Button for CallButton {
fn new(label: String) -> Self {
CallButton {
label: label,
action: "phone".to_string(),
phone_number: "0".to_string(),
message_text: None,
}
}
fn set_label(mut self, label: String) -> Self {
self.label = label;
self
}
fn set_msg(mut self, msg: String) -> Self {
self.message_text = Some(msg);
self
}
}
// other buttons implementing Button trait...
#[derive(Serialize, Deserialize)] // trying to add Deserialize
#[serde(deny_unknown_fields)]
pub struct A{
content: Content,
}
#[derive(Serialize, Deserialize)] // trying to add Deserialize
#[serde(deny_unknown_fields)]
pub struct Content{
#[serde(skip_serializing_if = "Vec::is_empty")]
items: Vec<Box<dyn erased_serde::Serialize>>, // ! HERE GOES ERROR
}
Error: the trait bound dyn erased_serde::Serialize: db::models::_::_serde::Deserialize<'_> is not satisfied
Maybe I'll have to implement Deserialize strictly
impl<'de> Deserialize<'de> for CarouselContent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let mut content = Content {
items: Vec::new(),
};
let s: Map<String, Value> = Map::deserialize(deserializer)?;
// dont know what to do
Ok(content)
}
}
Just changed everything into enums and it works, thanks for reading!
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum Button {
Call(CallButton),
Share(ShareButton),
}
#[derive(Debug, Deserialize)]
struct S3StorageConfig {
url: String,
}
#[derive(Debug, Deserialize)]
struct LocalStorageConfig {
root: std::path::PathBuf,
}
#[derive(Debug, Deserialize)]
struct StorageConfig {
storage_type: String
}
#[derive(Debug, Deserialize)]
pub struct Config {
storages: Vec<StorageConfig>
}
impl Config {
pub fn new(path:Option<std::path::PathBuf>) -> Result<Self, config::ConfigError> {
let mut cfg = config::Config::default();
if let Some(file_path) = path {
cfg.merge(config::File::from(file_path)).unwrap();
}
cfg.merge(config::Environment::with_prefix("datastore"))?;
cfg.try_into()
}
}
Suppose I want to have a config that has
[[storages]]
type: s3
url: ...
and
[[storages]]
type: local
root: ...
And when config does try_into, it is able to find these structs and assign them to the correct structs, by grace of the type field.
What magic would I need to do to make this happen?
Thanks,
So, I'm not 100% sure what you're trying to achieve here but you can serialize/deserialize into the types that you want with serde and using an enum instead.
Ex:
// This enum takes the place of your 'S3StorageConfig' and 'LocalStorageConfig'.
#[derive( Serialize, Deserialize, Debug )]
#[serde( tag = "type" )]
enum Storage {
Cloud{ url: String },
Local{ root: PathBuf },
}
fn main( ) {
let vec = vec![
Storage::Cloud{ url: "www.youtube.com".to_string( ) },
Storage::Local{ root: PathBuf::from( "C:\\Windows\\Fonts" ) },
];
let storage = serde_json::to_string( &vec ).unwrap( );
let vec: Vec<Storage> = serde_json::from_str( &storage ).unwrap( );
println!( "{:#?}", vec );
}
Now you will return a Storage enum variant from your Config class.
You wont need to impl TryInto if this is the direction you decide to take.
impl Config {
pub fn new( ) -> Result<Storage, config::ConfigError> {
// Read in the file here and use 'serde' to deserialize the
// content of the file into the correct enum variant that you
// can now return.
}
}
In the actix-web documentation is only an example of how to receive uniquely named query params.
But how can I receive multiple query params of the same name? For example:
http://localhost:8088/test?id=1&id=2&id=3
How do I have to change following code so it accepts multiple ids and how can I read them?
use actix_web::web;
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
id: String,
}
#[get("/test")]
async fn index(info: web::Query<Info>) -> impl Responder {
println!("Id: {}!", info.id);
"ok"
}
Having a look at this question, it seems like there is no definitive standard for what you want. I dont know if actix has such an extractor. I would work on my Deserialize impl.
use std::fmt;
use serde::de::{ Deserialize, Deserializer, Visitor, MapAccess};
impl<'de> Deserialize<'de> for Info {
fn deserialize<D>(deserializer: D) -> Result<Info, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> Visitor<'de> for FieldVisitor {
type Value = Info;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`id`")
}
fn visit_map<V>(self, mut map: V) -> Result<Info, V::Error>
where
V: MapAccess<'de>
{
let mut ids: Vec<String> = Vec::default();
while let Some(key) = map.next_key()? {
match key {
"id" => {
ids.push(map.next_value::<String>()?)
}
_ => unreachable!()
}
}
Ok(Info {
id: ids
})
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
#[derive(Debug)]
struct Info {
id: Vec<String>,
}
I want to return a different IMAP connection depending on the secure variable, but imap::client::Client returns a different type if we use SSL or not. All of the functions in Client are implemented by impl<T: Read + Write> Client<T>.
Is there a better and way more efficient solution possible?
use error::*;
use imap::client::Client;
use openssl::ssl::{SslConnectorBuilder, SslMethod, SslStream};
use std::net::TcpStream;
pub enum ConnectionResult {
Normal(Client<TcpStream>),
Secure(Client<SslStream<TcpStream>>),
}
/// Mail account
#[derive(Debug, Deserialize)]
pub struct Account {
pub username: String,
pub password: String,
pub domain: String,
pub port: u16,
pub secure: bool,
}
impl Account {
pub fn connect(&self) -> Result<ConnectionResult> {
if self.secure {
let ssl_connector = SslConnectorBuilder::new(SslMethod::tls())
.chain_err(|| "fail with ssl")?
.build();
let mut imap_socket = Client::secure_connect(
(self.domain.as_str(), self.port),
&self.domain,
ssl_connector,
);
imap_socket
.login(&self.username, &self.password)
.chain_err(|| "fail when login")?;
Ok(ConnectionResult::Secure(imap_socket))
} else {
let mut imap_socket = Client::connect((self.domain.as_str(), self.port))?;
imap_socket
.login(&self.username, &self.password)
.chain_err(|| "fail when login")?;
Ok(ConnectionResult::Normal(imap_socket))
}
}
I just want to return a Client struct, not a enum that holds different Clients:
pub fn connect<T: Read + Write>(&self) -> Result<Client<T>> {
// But this won't work
}
Your approach does not seem bad. My suggest is to use composition: encapsulate your enum in a struct and do the thing you want in each method with a match:
enum ConnectionResult { // not pub because intern implementation
Normal(Client<TcpStream>),
Secure(Client<SslStream<TcpStream>>),
}
pub struct MyClient {
connection_result: ConnectionResult,
// other fields
}
impl MyClient {
pub fn do_something(&self) {
match connection_result {
Normal(client) => // do something with normal client
Secure(client) => // do something with secure client
}
}
}
From the user point of view, there is no difference between the two clients.
If you do not want this solution, you can use the nightly -> impl feature:
#![feature(conservative_impl_trait)]
trait MyClient {
// the methods you need
}
struct NormalClient {
client: Client<TcpStream>,
/*etc.*/
}
struct SecureClient {
client: Client<SslStream<TcpStream>>,
/*etc.*/
}
impl MyClient for NormalClient { /*etc.*/ }
impl MyClient for SecureClient { /*etc.*/ }
fn get_client() -> impl MyClient { /*etc.*/ }