[GH-ISSUE #141] Percent sign in search query leads to a 400 #49

Closed
opened 2026-02-27 20:22:48 +03:00 by kerem · 7 comments
Owner

Originally created by @Utagai on GitHub (Oct 19, 2020).
Original GitHub issue: https://github.com/ramsayleung/rspotify/issues/141

I found this while using spotify-tui. After some digging, it seems like the issue is coming from rspotify.

Using a % symbol in your query string when calling the search function leads to a 400. Here's a quick and tiny snippet from spotify-tui:

let search_show = self.spotify.search(
        &search_term,
        SearchType::Show,
        self.small_search_limit,
        0,
        country,
        None,
      );

If &search_term above were to be, e.g., 99% invisible, we get a HTTP 400.

However, if we escape it as 99%25 invisible, the query goes through as expected.

It is unclear to me why we don't see other issues, like, why space ' ' is not necessary to explicitly escape as '%20'. I'm new to rust but I imagine that perhaps space is handled already somehow/somewhere in the stack when we use the RequestBuilder.

Originally created by @Utagai on GitHub (Oct 19, 2020). Original GitHub issue: https://github.com/ramsayleung/rspotify/issues/141 I found this while using `spotify-tui`. After some digging, it seems like the issue is coming from `rspotify`. Using a `%` symbol in your query string when calling the `search` function leads to a 400. Here's a quick and tiny snippet from `spotify-tui`: ```rust let search_show = self.spotify.search( &search_term, SearchType::Show, self.small_search_limit, 0, country, None, ); ``` If `&search_term` above were to be, e.g., `99% invisible`, we get a HTTP 400. However, if we escape it as `99%25 invisible`, the query goes through as expected. It is unclear to me why we don't see _other_ issues, like, why space `' '` is not necessary to explicitly escape as `'%20'`. I'm new to rust but I imagine that perhaps space is handled already somehow/somewhere in the stack when we use the `RequestBuilder`.
kerem 2026-02-27 20:22:48 +03:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@marioortizmanero commented on GitHub (Oct 19, 2020):

I can reproduce your issue. Here's a minimal working example I wrote with 0.10.0:

use rspotify::blocking::client::Spotify;
use rspotify::blocking::oauth2::{SpotifyClientCredentials, SpotifyOAuth};
use rspotify::blocking::util::get_token;
use rspotify::senum::SearchType;

fn main() {
    let mut oauth = SpotifyOAuth::default()
        .scope("user-read-currently-playing")
        .build();
    let token_info = get_token(&mut oauth).unwrap();
    let client_credential = SpotifyClientCredentials::default()
        .token_info(token_info)
        .build();
    let spotify = Spotify::default()
        .client_credentials_manager(client_credential)
        .build();

    let search_term = "99% invisible";
    let search_show = spotify.search(
            &search_term,
            SearchType::Show,
            None,
            0,
            None,
            None,
          );

    println!("result: {:?}", search_show);
}

result: Err(Other(400))

I tried to reproduce after the rewrite in the master branch, using ureq as the client, expecting it to be fixed:


use rspotify::client::SpotifyBuilder;
use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder};
use rspotify::senum::SearchType;

fn main() {
    let creds = CredentialsBuilder::from_env().build().unwrap();
    let oauth = OAuthBuilder::from_env()
        .scope("user-read-playback-state")
        .build()
        .unwrap();
    let mut spotify = SpotifyBuilder::default()
        .credentials(creds)
        .oauth(oauth)
        .build()
        .unwrap();

    // Obtaining the access token
    spotify.request_client_token().unwrap();

    let search_term = "99% invisible";
    let search_show = spotify.search(
            &search_term,
            SearchType::Show,
            None,
            0,
            None,
            None,
          );

    println!("result: {:?}", search_show);
}

result: Err(StatusCode(400, "Bad Request"))

But it seems to be an issue still... Interestingly, this is what happens with reqwest:

use rspotify::client::SpotifyBuilder;
use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder};
use rspotify::senum::SearchType;

#[tokio::main]
async fn main() {
    let creds = CredentialsBuilder::from_env().build().unwrap();
    let oauth = OAuthBuilder::from_env()
        .scope("user-read-playback-state")
        .build()
        .unwrap();
    let mut spotify = SpotifyBuilder::default()
        .credentials(creds)
        .oauth(oauth)
        .build()
        .unwrap();

    // Obtaining the access token
    spotify.request_client_token().await.unwrap();

    let search_term = "99% invisible";
    let search_show = spotify.search(
            &search_term,
            SearchType::Show,
            None,
            0,
            None,
            None,
          ).await;

    println!("result: {:?}", search_show);
}

result: Err(ParseJSON(Error("invalid type: null, expected struct SimplifiedShow", line: 4, column: 20)))

It seems to be related to SearchType::Show, since with SearchType::Artist it does work.

<!-- gh-comment-id:711898738 --> @marioortizmanero commented on GitHub (Oct 19, 2020): I can reproduce your issue. Here's a minimal working example I wrote with 0.10.0: ```rust use rspotify::blocking::client::Spotify; use rspotify::blocking::oauth2::{SpotifyClientCredentials, SpotifyOAuth}; use rspotify::blocking::util::get_token; use rspotify::senum::SearchType; fn main() { let mut oauth = SpotifyOAuth::default() .scope("user-read-currently-playing") .build(); let token_info = get_token(&mut oauth).unwrap(); let client_credential = SpotifyClientCredentials::default() .token_info(token_info) .build(); let spotify = Spotify::default() .client_credentials_manager(client_credential) .build(); let search_term = "99% invisible"; let search_show = spotify.search( &search_term, SearchType::Show, None, 0, None, None, ); println!("result: {:?}", search_show); } ``` > result: Err(Other(400)) I tried to reproduce after the rewrite in the master branch, using ureq as the client, expecting it to be fixed: ```rust use rspotify::client::SpotifyBuilder; use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; use rspotify::senum::SearchType; fn main() { let creds = CredentialsBuilder::from_env().build().unwrap(); let oauth = OAuthBuilder::from_env() .scope("user-read-playback-state") .build() .unwrap(); let mut spotify = SpotifyBuilder::default() .credentials(creds) .oauth(oauth) .build() .unwrap(); // Obtaining the access token spotify.request_client_token().unwrap(); let search_term = "99% invisible"; let search_show = spotify.search( &search_term, SearchType::Show, None, 0, None, None, ); println!("result: {:?}", search_show); } ``` > result: Err(StatusCode(400, "Bad Request")) But it seems to be an issue still... Interestingly, this is what happens with reqwest: ```rust use rspotify::client::SpotifyBuilder; use rspotify::oauth2::{CredentialsBuilder, OAuthBuilder}; use rspotify::senum::SearchType; #[tokio::main] async fn main() { let creds = CredentialsBuilder::from_env().build().unwrap(); let oauth = OAuthBuilder::from_env() .scope("user-read-playback-state") .build() .unwrap(); let mut spotify = SpotifyBuilder::default() .credentials(creds) .oauth(oauth) .build() .unwrap(); // Obtaining the access token spotify.request_client_token().await.unwrap(); let search_term = "99% invisible"; let search_show = spotify.search( &search_term, SearchType::Show, None, 0, None, None, ).await; println!("result: {:?}", search_show); } ``` > result: Err(ParseJSON(Error("invalid type: null, expected struct SimplifiedShow", line: 4, column: 20))) It seems to be related to `SearchType::Show`, since with `SearchType::Artist` it does work.
Author
Owner

@Utagai commented on GitHub (Oct 19, 2020):

Interesting! I did not realize this was a show-specific bug. Thanks for looking into that.

It seems like you're already taking a look at this, but let me know if you need anything else.

This isn't a major issue with spotify-tui, especially since we can cheekily search "99%25 invisible" and have it still work 😉.

<!-- gh-comment-id:712318088 --> @Utagai commented on GitHub (Oct 19, 2020): Interesting! I did not realize this was a show-specific bug. Thanks for looking into that. It seems like you're already taking a look at this, but let me know if you need anything else. This isn't a major issue with `spotify-tui`, especially since we can cheekily search "99%25 invisible" and have it still work 😉.
Author
Owner

@kstep commented on GitHub (Mar 9, 2021):

I tried to reproduce the bug. With SearchType::Artist it returns an empty result (items: []), so I guess that's why it "works ok".
For SearchType::Show it returns an array of nulls (items: [null, null, null, ...]), hence the invalid type: null, expected struct SimplifiedShow error.

<!-- gh-comment-id:794544546 --> @kstep commented on GitHub (Mar 9, 2021): I tried to reproduce the bug. With `SearchType::Artist` it returns an empty result (`items: []`), so I guess that's why it "works ok". For `SearchType::Show` it returns an array of nulls (`items: [null, null, null, ...]`), hence the `invalid type: null, expected struct SimplifiedShow` error.
Author
Owner

@kstep commented on GitHub (Mar 9, 2021):

It seems the problem is in the invalid auth token.

I tried to reproduce the bug in the developer console (https://developer.spotify.com/console/), and it works just OK, no problems!

Then I tried to make the query in my terminal with curl with an auth token from the developer console, and it worked OK as well.
But then I tried to do it with an auth token I got from the code above (from this issue), and the bug was reproduced (I got items: [null, ...] in response)!

<!-- gh-comment-id:794553112 --> @kstep commented on GitHub (Mar 9, 2021): It seems the problem is in the invalid auth token. I tried to reproduce the bug in the developer console (https://developer.spotify.com/console/), and it works just OK, no problems! Then I tried to make the query in my terminal with curl with an auth token from the developer console, and it worked OK as well. But then I tried to do it with an auth token I got from the code above (from this issue), and the bug was reproduced (I got `items: [null, ...]` in response)!
Author
Owner

@kstep commented on GitHub (Mar 10, 2021):

If the bug is related to the authentication process, is it possible it will be fixed (or otherwise affected) by #173 ?

<!-- gh-comment-id:795356635 --> @kstep commented on GitHub (Mar 10, 2021): If the bug is related to the authentication process, is it possible it will be fixed (or otherwise affected) by #173 ?
Author
Owner

@marioortizmanero commented on GitHub (Mar 10, 2021):

I mean #173 is meant to be a rewrite, but the functionality will be the same. The plan is to copy most of the codebase, only restructuring and tidying it up a bit.

But hey who knows maybe after that it'll magically work :P

I'm waiting to see if #191 can be sorted out beforehand, as two PRs restructuring the project at the same time doesn't sound like a good idea.

<!-- gh-comment-id:795399424 --> @marioortizmanero commented on GitHub (Mar 10, 2021): I mean #173 is meant to be a rewrite, but the functionality will be the same. The plan is to copy most of the codebase, only restructuring and tidying it up a bit. But hey who knows maybe after that it'll magically work :P I'm waiting to see if #191 can be sorted out beforehand, as two PRs restructuring the project at the same time doesn't sound like a good idea.
Author
Owner

@marioortizmanero commented on GitHub (Sep 25, 2021):

Apparently this has been fixed in master. I've added a test in #259.

<!-- gh-comment-id:927167398 --> @marioortizmanero commented on GitHub (Sep 25, 2021): Apparently this has been fixed in `master`. I've added a test in #259.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/rspotify#49
No description provided.