[GH-ISSUE #389] Scopes!() Disclaimer in documentation #124

Closed
opened 2026-02-27 20:23:15 +03:00 by kerem · 1 comment
Owner

Originally created by @Inheritor-Vision on GitHub (Feb 18, 2023).
Original GitHub issue: https://github.com/ramsayleung/rspotify/issues/389

Hi!

First of all, I am new to Rust, so sorry in advance for any imprecision or mistake. I also do not know if this is a bug or simply disclaimer to add to the documentation.

Summary

scopes!() accept the old way of making scopes (i.e with spaces likescopes!("user-read-recently-played playlist-modify-public playlist-modify-private")) without errors. The mistake will only appears when prompt_for_token() will keep asking for authorization, even if the token is cached. prompt_for_token is probably used once in the code, when creating the Spotify client for the first time. And at this time, it is hard to debug and find that scopes!() is the issue, because everything seems OK on that side.

Long story

It seems like one of the breaking change between 0.10 and 0.11 was to make the creation of scopes behave differently.

0.10:

let mut oauth = SpotifyOAuth::default()
.scope("user-read-recently-played playlist-modify-public playlist-modify-private")
.build();

0.11:

let oauth = OAuth{
	scopes: scopes!("user-read-recently-played", "playlist-modify-public", "playlist-modify-private"),
	..Default::default()
};
let spot = AuthCodeSpotify::new(creds, oauth);

However, doing the following in 0.11 do not produce any visible error:

let oauth = OAuth{
	scopes: scopes!("user-read-recently-played playlist-modify-public playlist-modify-private"),
	..Default::default()
};
let spot = AuthCodeSpotify::new(creds, oauth);

Worse than that, it produces the exact same valid URL from get_authorize_url. So, when used with prompt_for_token, everything works fine for the current session.

If the token is stored:

let config = Config{
	cache_path: path,
	token_cached: true,
	..Default::default()
};

The difference will then appears when the token will be retreived from the cache. Scopes from cache and scopes from the code will be then built differently:

From cache:
{
    "playlist-modify-private",
    "ugc-image-upload",
    "playlist-modify-public",
}
From scopes!
{
    "playlist-modify-public playlist-modify-private ugc-image-upload",
}

Then self.get_oauth().scopes.is_subset() inside read_token_cache inside prompt_for_token will silently return false and act like the token do not have the same scope, hence re-asking for authorization.

Potential fixes

I would see 3 different potential fixes:

  • Make join_scopes!() reject any inputs with a space or, silently split it.
  • Generating a debug log each time scope is considered to be different (i.e close to self.get_oauth().scopes.is_subset() in read_token_cache
  • Put a disclaimer to not put such inputs in scopes!() in the documentation, for newbies like me still taking inspirationg from code prior 0.11

Again, I'm Rust newbie, I hope it helps!

Originally created by @Inheritor-Vision on GitHub (Feb 18, 2023). Original GitHub issue: https://github.com/ramsayleung/rspotify/issues/389 Hi! First of all, I am new to Rust, so sorry in advance for any imprecision or mistake. I also do not know if this is a bug or simply disclaimer to add to the documentation. # Summary `scopes!()` accept the old way of making scopes (i.e with spaces like`scopes!("user-read-recently-played playlist-modify-public playlist-modify-private")`) without errors. The mistake will only appears when `prompt_for_token()` will keep asking for authorization, even if the token is cached. `prompt_for_token` is probably used once in the code, when creating the Spotify client for the first time. And at this time, it is hard to debug and find that `scopes!()` is the issue, because everything seems OK on that side. # Long story It seems like one of the breaking change between 0.10 and 0.11 was to make the creation of *scopes* behave differently. **0.10**: ```Rust let mut oauth = SpotifyOAuth::default() .scope("user-read-recently-played playlist-modify-public playlist-modify-private") .build(); ``` **0.11**: ```Rust let oauth = OAuth{ scopes: scopes!("user-read-recently-played", "playlist-modify-public", "playlist-modify-private"), ..Default::default() }; let spot = AuthCodeSpotify::new(creds, oauth); ``` However, doing the following in **0.11** do not produce any visible error: ```Rust let oauth = OAuth{ scopes: scopes!("user-read-recently-played playlist-modify-public playlist-modify-private"), ..Default::default() }; let spot = AuthCodeSpotify::new(creds, oauth); ``` Worse than that, it produces the exact same valid URL from `get_authorize_url`. So, when used with `prompt_for_token`, everything works fine for the current session. If the token is stored: ``` let config = Config{ cache_path: path, token_cached: true, ..Default::default() }; ``` The difference will then appears when the token will be retreived from the cache. Scopes from cache and scopes from the code will be then built differently: ``` From cache: { "playlist-modify-private", "ugc-image-upload", "playlist-modify-public", } From scopes! { "playlist-modify-public playlist-modify-private ugc-image-upload", } ``` Then `self.get_oauth().scopes.is_subset()` inside `read_token_cache` inside `prompt_for_token` will silently return false and act like the token do not have the same scope, hence re-asking for authorization. # Potential fixes I would see 3 different potential fixes: - Make `join_scopes!()` reject any inputs with a space or, silently split it. - Generating a debug log each time scope is considered to be different (i.e close to `self.get_oauth().scopes.is_subset()` in `read_token_cache` - Put a disclaimer to not put such inputs in `scopes!()` in the documentation, for newbies like me still taking inspirationg from code prior 0.11 Again, I'm Rust newbie, I hope it helps!
kerem 2026-02-27 20:23:15 +03:00
Author
Owner

@ramsayleung commented on GitHub (Feb 22, 2023):

Hi @Inheritor-Vision, thanks for your contribution, since the scopes! macro doesn't reject any inputs with space, then it will cause some subtle bugs here.

In my opinion, I prefer the combination of first solution and third solution, because the second fix doesn't actually fix this problem, it generates some message to help developer to address this subtle problem. As for the third fix, some developers might miss the documentation.

I think the best way to solve a problem is to prevent it from happening in the first place.

PS:
I create a PR to fix this problem :)

<!-- gh-comment-id:1439327236 --> @ramsayleung commented on GitHub (Feb 22, 2023): Hi @Inheritor-Vision, thanks for your contribution, since the `scopes!` macro doesn't reject any inputs with space, then it will cause some subtle bugs here. In my opinion, I prefer the combination of first solution and third solution, because the second fix doesn't actually fix this problem, it generates some message to help developer to address this subtle problem. As for the third fix, some developers might miss the documentation. I think the best way to solve a problem is to prevent it from happening in the first place. PS: I create a PR to fix this problem :)
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#124
No description provided.