samscott89 / serde_qs

Serde support for querystring-style strings
Apache License 2.0
193 stars 68 forks source link

The following string value fails to decode #107

Open assembly-winston opened 5 months ago

assembly-winston commented 5 months ago

I have the following structs to represent a URL-encoded value, produced by converting a JSON into URL-encoded on urldecoder.org.

    use chrono::{DateTime, Utc};
    use serde::{Deserialize, Serialize};

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    pub struct BlockActions {
        pub team: Team,
        pub user: User,
        pub container: Container,
        pub channel: Channel,
        pub actions: Vec<Action>,
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    pub struct Team {
        pub id: String,
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    pub struct User {
        pub id: String,
        pub username: String,
        pub team_id: String,
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    #[serde(tag = "type")]
    pub enum Container {
        #[serde(rename = "message")]
        Message {
            #[serde(rename = "message_ts", with = "super::unix_timestamp")]
            message_timestamp: DateTime<Utc>,

            #[serde(rename = "thread_ts", with = "super::unix_timestamp")]
            thread_timestamp: DateTime<Utc>,
            channel_id: String,
        },
    }

    impl Container {
        pub fn get_thread_ts_in_slack_format(&self) -> String {
            match self {
                Container::Message {
                    thread_timestamp, ..
                } => thread_timestamp.format("%s.%6f").to_string(),
            }
        }
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    pub struct Channel {
        pub id: String,
        pub name: String,
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    #[serde(tag = "type")]
    pub enum Action {
        #[serde(rename = "button")]
        Button {
            #[serde(rename = "action_id")]
            id: String,
            block_id: String,
            text: Text,
            value: String,
        },
    }

    #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
    pub struct Text {
        #[serde(rename = "type")]
        r#type: String,
        text: String,
        emoji: bool,
    }

The text here (all values replaced with samples):

payload=%7B%22type%22%3A%22block_actions%22%2C%22user%22%3A%7B%22id%22%3A%22U1234567890%22%2C%22username%22%3A%22john_doe%22%2C%22name%22%3A%22john_doe%22%2C%22team_id%22%3A%22T1234567890%22%7D%2C%22api_app_id%22%3A%22A1234567890%22%2C%22token%22%3A%22abcdef1234567890abcdef1234567890%22%2C%22container%22%3A%7B%22type%22%3A%22message%22%2C%22message_ts%22%3A%22123456789.123456%22%2C%22channel_id%22%3A%22C1234567890%22%2C%22is_ephemeral%22%3Afalse%2C%22thread_ts%22%3A%22123456789.654321%22%7D%2C%22trigger_id%22%3A%221234567890.0987654321.abcdef1234567890abcdef1234567890%22%2C%22team%22%3A%7B%22id%22%3A%22T1234567890%22%2C%22domain%22%3A%22example-domain%22%7D%2C%22enterprise%22%3Anull%2C%22is_enterprise_install%22%3Afalse%2C%22channel%22%3A%7B%22id%22%3A%22C1234567890%22%2C%22name%22%3A%22example-channel%22%7D%2C%22message%22%3A%7B%22user%22%3A%22U0987654321%22%2C%22type%22%3A%22message%22%2C%22ts%22%3A%22123456789.123456%22%2C%22bot_id%22%3A%22B1234567890%22%2C%22app_id%22%3A%22A1234567890%22%2C%22text%22%3A%22This+is+an+example+text%2C+replacing+the+original+text+with+something+new%2E%22%2C%22team%22%3A%22T1234567890%22%2C%22thread_ts%22%3A%22123456789.654321%22%2C%22parent_user_id%22%3A%22U1234567890%22%2C%22blocks%22%3A%5B%7B%22type%22%3A%22section%22%2C%22block_id%22%3A%22exampleBlockId1%22%2C%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22This+is+an+example+section%2E%22%2C%22verbatim%22%3Afalse%7D%7D%2C%7B%22type%22%3A%22actions%22%2C%22block_id%22%3A%22exampleBlockId2%22%2C%22elements%22%3A%5B%7B%22type%22%3A%22button%22%2C%22action_id%22%3A%22exampleActionId1%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22%3Acomputer%3A+example-button-1%22%2C%22emoji%22%3Atrue%7D%2C%22url%22%3A%22https%3A%5C%2F%5C%2Fexample.com%5C%2Fexample-path-1%22%7D%2C%7B%22type%22%3A%22button%22%2C%22action_id%22%3A%22exampleActionId2%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22%3Aticket%3A+example-ticket-1%22%2C%22emoji%22%3Atrue%7D%2C%22url%22%3A%22https%3A%5C%2F%5C%2Fexample.com%5C%2Fexample-path-2%22%7D%2C%7B%22type%22%3A%22button%22%2C%22action_id%22%3A%22exampleActionId3%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22%3Apage_facing_up%3A++example-button-2%22%2C%22emoji%22%3Atrue%7D%2C%22url%22%3A%22https%3A%5C%2F%5C%2Fexample.com%5C%2Fexample-path-3%22%7D%2C%7B%22type%22%3A%22button%22%2C%22action_id%22%3A%22exampleActionId4%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22%3Aticket%3A+example-ticket-2%22%2C%22emoji%22%3Atrue%7D%2C%22url%22%3A%22https%3A%5C%2F%5C%2Fexample.com%5C%2Fexample-path-4%22%7D%2C%7B%22type%22%3A%22button%22%2C%22action_id%22%3A%22exampleActionId5%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22%3Aticket%3A+example-ticket-3%22%2C%22emoji%22%3Atrue%7D%2C%22url%22%3A%22https%3A%5C%2F%5C%2Fexample.com%5C%2Fexample-path-5%22%7D%5D%7D%2C%7B%22type%22%3A%22divider%22%2C%22block_id%22%3A%22exampleDividerId%22%7D%2C%7B%22type%22%3A%22actions%22%2C%22block_id%22%3A%22exampleBlockId3%22%2C%22elements%22%3A%5B%7B%22type%22%3A%22button%22%2C%22action_id%22%3A%22exampleActionId6%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22%3Athumbsup%3A%22%2C%22emoji%22%3Atrue%7D%2C%22value%22%3A%22thumbs_up%22%7D%2C%7B%22type%22%3A%22button%22%2C%22action_id%22%3A%22exampleActionId7%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22%3Athumbsdown%3A%22%2C%22emoji%22%3Atrue%7D%2C%22value%22%3A%22thumbs_down%22%7D%5D%7D%5D%7D%2C%22state%22%3A%7B%22values%22%3A%7B%7D%7D%2C%22response_url%22%3A%22https%3A%5C%2F%5C%2Fhooks.slack.com%5C%2Factions%5C%2FT1234567890%5C%2F1234567890123%5C%2Fabcdef1234567890abcdef1234567890%22%2C%22actions%22%3A%5B%7B%22action_id%22%3A%22exampleActionId6%22%2C%22block_id%22%3A%22exampleBlockId3%22%2C%22text%22%3A%7B%22type%22%3A%22plain_text%22%2C%22text%22%3A%22%3Athumbsup%3A%22%2C%22emoji%22%3Atrue%7D%2C%22value%22%3A%22thumbs_up%22%2C%22type%22%3A%22button%22%2C%22action_ts%22%3A%221234567890%22%7D%5D%7D
 // I also tried stripping the payload= prefix.
let deserializer = Config::new(10, false);
deserializer.deserialize_str::<BlockActions>(&raw_string).expect("must succeed");

The error:

thread 'api::slack::block_actions::tests::read_block_response' panicked at src/api/slack.rs:560:73:
must succeed: Error("missing field `team`")

Meanwhile, the equivalent JSON succeeds:

{"type":"block_actions","user":{"id":"U1234567890","username":"john_doe","name":"john_doe","team_id":"T1234567890"},"api_app_id":"A1234567890","token":"abcdef1234567890abcdef1234567890","container":{"type":"message","message_ts":"123456789.123456","channel_id":"C1234567890","is_ephemeral":false,"thread_ts":"123456789.654321"},"trigger_id":"1234567890.0987654321.abcdef1234567890abcdef1234567890","team":{"id":"T1234567890","domain":"example-domain"},"enterprise":null,"is_enterprise_install":false,"channel":{"id":"C1234567890","name":"example-channel"},"message":{"user":"U0987654321","type":"message","ts":"123456789.123456","bot_id":"B1234567890","app_id":"A1234567890","text":"This+is+an+example+text,+replacing+the+original+text+with+something+new.","team":"T1234567890","thread_ts":"123456789.654321","parent_user_id":"U1234567890","blocks":[{"type":"section","block_id":"exampleBlockId1","text":{"type":"mrkdwn","text":"This+is+an+example+section.","verbatim":false}},{"type":"actions","block_id":"exampleBlockId2","elements":[{"type":"button","action_id":"exampleActionId1","text":{"type":"plain_text","text":":computer:+example-button-1","emoji":true},"url":"https:\/\/example.com\/example-path-1"},{"type":"button","action_id":"exampleActionId2","text":{"type":"plain_text","text":":ticket:+example-ticket-1","emoji":true},"url":"https:\/\/example.com\/example-path-2"},{"type":"button","action_id":"exampleActionId3","text":{"type":"plain_text","text":":page_facing_up:++example-button-2","emoji":true},"url":"https:\/\/example.com\/example-path-3"},{"type":"button","action_id":"exampleActionId4","text":{"type":"plain_text","text":":ticket:+example-ticket-2","emoji":true},"url":"https:\/\/example.com\/example-path-4"},{"type":"button","action_id":"exampleActionId5","text":{"type":"plain_text","text":":ticket:+example-ticket-3","emoji":true},"url":"https:\/\/example.com\/example-path-5"}]},{"type":"divider","block_id":"exampleDividerId"},{"type":"actions","block_id":"exampleBlockId3","elements":[{"type":"button","action_id":"exampleActionId6","text":{"type":"plain_text","text":":thumbsup:","emoji":true},"value":"thumbs_up"},{"type":"button","action_id":"exampleActionId7","text":{"type":"plain_text","text":":thumbsdown:","emoji":true},"value":"thumbs_down"}]}]},"state":{"values":{}},"response_url":"https:\/\/hooks.slack.com\/actions\/T1234567890\/1234567890123\/abcdef1234567890abcdef1234567890","actions":[{"action_id":"exampleActionId6","block_id":"exampleBlockId3","text":{"type":"plain_text","text":":thumbsup:","emoji":true},"value":"thumbs_up","type":"button","action_ts":"1234567890"}]}