-
Notifications
You must be signed in to change notification settings - Fork 124
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
Meta-Issue #127
Comments
Hello @Koxiaet! Thanks a lot for compiling a list of issues you found with Rpotify. I completely agree with most of them, I'll just write my thoughts below about some of them:
I recently created an issue that removed lots of unnecessary dependencies and attempted to make this library more modular. It introduced a new feature called
That is something I realized and disliked as well. I opened the #109 issue, which proposes using
I was thinking that we could create a custom
I think that's still useful to have around. I'm betting that the Spotify Web API devs put that parameter in there for a reason, otherwise it wouldn't exist at all. It might change in the future, I wouldn't touch that.
Completely agree. In fact, I would name
The SpotifyClientCredentials/SpotifyOAuth structs are confusing altogether. They share lots of methods. These could be reworked to make them more straightforward.
I'm not sure I fully understand that. We were thinking of creating an automatically refreshing token at #4, if that's what you're talking about. But it's not very clear how it can be implemented yet.
I hadn't even seen these yet lol. I think they can be removed without a problem. I also wanted to suggest more things, like using iterators where possible and trying to make the endpoint implementations as concise as possible. May I edit your post to add these smaller improvements? Respect to how this could be implemented:
|
Thanks for the response!
That's good. However nothing in
The Spotify devs put it in there because it's in the OAuth2 spec. If you think there's a possibility of more methods being added then it'd probably be best to have a
On line 165 of oauth2.rs, if there is no token because it's expired a new token is requested from Spotify, and this function is called on every request, so after the initial token has expired every request becomes two requests. #4 does seem to be very similar to this.
Of course!
Personally I would rather have to do one large migration than have to repeatedly update rspotify every so often. Users probably won't update dependencies that often anyway so if they're released close together it will have the effect of one release.
Unfortunately I'm working on other projects at the moment so I can't, sorry. |
Yes, the documentation could definitely be improved. I'll take a look at what other functions use stdin and add them to a
Ah, thanks for the link. It seems that the token type changes quite a bit how it works, so if they ever changed it we'd have to include some breaking changes anyway. It might be better to just remove it for now.
If I upgraded some library and saw a list of 80+ changes I'd be a bit thrown off... But yeah it might be easier to upgrade at once than mutiple times. |
Hi @Koxiaet, thanks for your suggestion and "complaint", it does point a lot of things out. Let's slow down and take our times to discuss them all one by one.
Yes, the single file
As for the original thought of
It's better to reused the mature library instead of reinventing wheel.
No really,
use Instant is a great idea, but the original reason of using
yes, it is always
well,
You have pointed a lot things out about
It's a good idea, but I prefer to keep
What's the point of this suggestion?
Is there any link broken and inconsistent documentation? Would you like to point it out. |
I've actually spent a lot of time trying to figure out how optional parameters can be implemented for APIs like Rspotify. I was even writing a small blog post with all the options, but I haven't finished it yet. I'll share it when I'm done. |
It's true they don't, but since Spotify doesn't specify a limit we might as well choose the integer type that is the easiest to use.
It's already supported now anyway, but we shouldn't force users to write out the verbose path especially if a less verbose but unambiguous path is possible. It's just a reexport so you'll still be able to write it out in full if you wish, but people can choose which they prefer.
Someone might want to check if two tracks are equal. In fact this could even just check the equality of the IDs for efficiency.
Track link's is broken, and I think there were a couple others too? It's probably best to just search for |
I am a bit unsure about the conclusion that we might as well choose the integer type that is the easiest to use. why would you think
I agreed with the later part,
I don't think it's necessary, probably Spotify will add some new values for |
Regarding this:
We could also do something similar to what's being implemented at #161, which I really like. Just make the thing generic over the type and implement a Loving how we're progressing on this btw!! Not that much work to close it :) |
I think some points can be marked complete now:
|
Sure, updated them |
Didn't mean to close this, whoops |
As for this TODO item, I have tried, the problem I have occured was that not all endpoints need to return a struct implemented #[maybe_async]
pub async fn playlist_replace_tracks<'a>(
&self,
playlist_id: &PlaylistId,
track_ids: impl IntoIterator<Item = &'a TrackId>,
) -> ClientResult<()> {
let uris = track_ids.into_iter().map(|id| id.uri()).collect::<Vec<_>>();
let params = json!({ "uris": uris });
let url = format!("playlists/{}/tracks", playlist_id.id());
self.endpoint_put(&url, ¶ms).await?;
Ok(())
} |
Ok thanks for trying it out! I'll remove it from the list. It's not really that important and not worth dealing with, then. |
As for this TODO Item, we have changed the functions' signature from
There is no /// Generate `length` random chars
pub(in crate) fn generate_random_string(length: usize) -> String {
let alphanum: &[u8] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".as_bytes();
let mut buf = vec![0u8; length];
getrandom(&mut buf).unwrap(); //! unwrap statement
let range = alphanum.len();
buf.iter()
.map(|byte| alphanum[*byte as usize % range] as char)
.collect()
}
async fn request<D>(
&self,
method: Method,
url: &str,
headers: Option<&Headers>,
add_data: D,
) -> ClientResult<String>
where
D: Fn(RequestBuilder) -> RequestBuilder,
{
let mut request = self.client.request(method.clone(), url);
// Setting the headers, if any
if let Some(headers) = headers {
// The headers need to be converted into a `reqwest::HeaderMap`,
// which won't fail as long as its contents are ASCII. This is an
// internal function, so the condition cannot be broken by the user
// and will always be true.
//
// The content-type header will be set automatically.
let headers = headers.try_into().unwrap(); //! unwrap statement
request = request.headers(headers);
}
} I am thinking about is it necessary to remove these two |
and nothing else afaik.
I think it's safe to assume that we can use And the second does have an explanation, so no issue with that. I thought we had more |
I have fixed it in this branch: https://github.com/ramsayleung/rspotify/blob/ramsay_pass_parameters_by_reference/src/client.rs#L862, and I will create a PR after #202 merged
And I am working on it now. But I am not sure if it's worth replacing with // This map is used to store the intermediate data which lives long enough
// to be borrowed into the `param`
let mut map_to_hold_owned_value: HashMap<String, String> = HashMap::new();
attributes
.iter()
// create cartesian product for attributes and prefixes
.flat_map(|attribute| {
prefixes
.iter()
.clone()
.map(move |prefix| format!("{}_{}", prefix, attribute))
})
.for_each(|param| {
if let Some(value) = payload.get(¶m) {
// TODO: not sure if this `to_string` is what we want. It
// might add quotes to the strings.
map_to_hold_owned_value.insert(param, value.to_string());
}
});
for (ref key, ref value) in &map_to_hold_owned_value {
params.insert(key, value);
}
It's nice to have more |
You could've done a
No, the opposite, it's nice not to have them :) |
Now that the auth restructure is done, I will focus on, for now:
If anyone else wants to work on these issues do let me know and we can do so together or I'll just do something else (I don't really mind what to do). I will leave on holidays the second half of July so I won't be able to do much until August. But I expect that we might be able to release v0.11 in late-august or september if everything goes well :) |
Wish you have a great holiday :) |
About this one:
It doesn't really matter now that we have custom ID types. Instead of: let id = ArtistId::from_id(&context.id); The user can just use: let id = ArtistId::from_uri(&context.uri); And use it the exact same way. So I would remove that one off the list. Otherwise we have to manually deserialize and serialize the |
I can't believe we're closing this finally :D |
This is a meta-issue of many issues with rspotify. Some of it's opinionated but most of it I think is just an improvement.
General
rspotify::client::Spotify
). The entire contents of theclient
module should be moved to the crate root to make it easier to access.senum
; this module contains all the enums used (and alsoError
for some reason), but could be split into more fitting modules such asmodel
.cli
/tui
/terminal
/console
module and feature-gated.Spotify::tracks
take in aVec<&str>
/Vec<String>
/&[String]
/&[&str]
, when they should take in animpl IntoIterator<Item = &str>
.String
as parameters instead of&str
.limit
andoffset
parameters should universally beusize
s as they are easier to work with from Rust.Default::default impl
(seeTokenInfo::default
)Spotify::get_uri
should useformat!
:Spotify::get_id
OAuth
TokenInfo
storesexpires_in
as au32
, and additionally has theexpires_at
field. This is not easy to use, instead it should have a singleexpires
field with the typeInstant
.TokenInfo
stores ascope: String
, when it should store ascope: Vec<String>
obtained by splitting the scopes by spaces.TokenInfo
storestoken_type
, which is useless since it is alwaysBearer
.SpotifyClientCredentials
andSpotifyOAuth
have a superfluousSpotify
prefix; justClientCredentials
would be shorter and not duplicate the crate name.SpotifyClientCredentials
contains not only the user's client credentials but also the token info if present, so the name is misleading. In fact theSpotifyOAuth
struct duplicates theclient_id
andclient_secret
fields due to the lack of a realClientCredentials
type.Client
is created every timefetch_access_token
is called. This is very inefficient. Instead, the authorization logic should be moved toSpotify
allowing it to reuse the same client.token_info
is never mutated onSpotifyClientCredentials
. This means that once the initial token has expiredrspotify
will get a new token every time. The solution is to wrapTokenInfo
into aMutex
and mutate it when appropriate.Spotify
contains anaccess_token
twice; once in the struct itself and once inclient_credentials_manager.token_info_access_token
.Utils
convert_map_to_string
is better served byserde_urlencoded
or even better Reqwest'squery
method.generate_random_string
should be private.datetime_to_timestamp
is only necessary due to this library's use of numbers instead ofInstant
s orDuration
s.token
functions should probably be methods ofSpotify
instead of standalone functions.utils
module can be removed entirely, and any remaining functions can simply be placed in the crate root for easy access.Model
model
module is not reexported from the crate root. Havingpub use model::*
andpub use {album, artist, audio, etc}::*
would keep the docs uncluttered and allow users to writerspotify::FullAlbum
instead of the overly verboserspotify::model::album::FullAlbum
.PartialEq
(andEq
if possible) for everything._type
fields on the models are unnecessary data. It would be better to use a macro to create constants that can only serialize to and deserialize as their string, for exampleTypeAlbum
which is represented in JSON as only"album"
. This avoids carrying extra data while still storing thetype
field.Theuri
andhref
fields are unnecessary, since it can be recreated easily. Make these methods instead.copyrights
is represented as aVec<HashMap<String, String>>
; a much more accurate representation isVec<Copyright>
andFullAlbum::release_date
andFullAlbum::release_date_precision
areString
s. Instead, it should be deserialized into a proper date type likeNaiveDate
and aenum DatePrecision { Year, Month, Day }
.SimplifiedEpisode
andFullEpisode
.FullAlbums
,PageSimplifiedAlbums
, etc exist as wrappers. This makes the API more complex. Instead, have that type exist solely in the API function that needs it, and then return aVec<FullAlbum>
. This drastically reduces the number of public API types.Restrictions
is located in thealbum
module, despite it not having any particular relevance to albums.{FullArtist, FullPlaylist}::followers
is aHashMap<String, Option<Value>>
. It should be astruct Followers { total: usize }
.AudioAnalysisSection::mode
andAudioFeatures::mode
aref32
s but should beOption<Mode>
s whereenum Mode { Major, Minor }
as it is more useful.AudioAnalysisSection
andAudioAnalysisSegment
could contain a#[serde::flatten]
edTimeInterval
instead of the having the fields inline.AudioAnalysisMeasure
is identical toTimeInterval
, and so should be replaced by it.(The assumption about how do users use this library is not accurate, since we have no idea about how do users use this library)AudioFeatures::analysis_url
andAudioFeatures::track_href
aren't useful for the user, since they will access it via this library.AudioFeatures::duration_ms
should be replaced with aduration
of typeDuration
.[ ]Context
contains auri
, but since those aren't particularly useful it should be replaced with a transformer into anid
.Actions::disallows
can be replaced with aVec<DisallowKey>
orHashSet<DisallowKey>
by removing all entires whose value isfalse
, which will result in a simpler API.CurrentlyPlayback
should be renamedCurrentPlayback
, sinceCurrentlyPlayback
doesn't make sense.CurrentlyPlayingContext::timestamp
should be aDateTime<Utc>
.CurrentlyPlayingContext::progress_ms
should beprogress: Duration
.CurrentlyPlayback
is a superset ofCurrentlyPlaying
, it should probably contain it instead of containing all its fields.Device
is missing the fieldis_private_session: bool
.Device::id
andDevice::volume_percent
can benull
in the Spotify API, so they should be anOption
.FullPlayingContext
doesn't exist in the Spotify API. It should be replaced withCurrentlyPlaybackContext
.SimplifiedPlayingContext
also doesn't exist, and should be replaced withCurrentlyPlayingContext
.CUDResult
is oddly named; it should refer to playlists in its title.PlaylistResult
might be a better name. But it's not even necessary at all, the functions can just returnString
directly.Single-item modules likeimage
should have their items placed in the above scope.Offset
should be represented as aType
+id
since that is easier for the user.Offset::position
should be aDuration
.Page::{limit, offset, total}
should beusize
s, because they're easier to work with.Page::{next, previous}
are not useful to the user since they'll use this program's API.CursorBasedPage
should also useusize
s.CursorBasedPage
should also not include the URLs.Playing
should be replaced withCurrentlyPlayingContext
, since it is the same.SimplifiedPlaylist::tracks
should be astruct Tracks { total: usize }
; this isn't document but is what the API returns.PlaylistTrack::added_at
can benull
so should beOption
.PlaylistTrack
is poorly named in the Spotify API. It should be calledPlaylistItem
since it can also contain episodes.PlaylistItem
to contain episodes. #194 AllowPlaylistTrack
/PlaylistItem
to contain episodes.RecommendationsSeed::{after_filtering_size, after_relinking_size, initial_pool_size}
should beusize
s since they are easier to use.(You have to specify theSearchResult
should be a struct not an enum, since you can search for multiple items at once.searchType
when usingsearch
endpoint, so you can't search for multiple items at once.)SimplifiedEpisode::duration_ms
should be aDuration
.language
shouldn't be inSimplifiedEpisode
if it's deprecated.ResumePoint::resume_position_ms
should be aDuration
.SimplifiedTrack::disc_number
should be ausize
, for ease of use.SimplfiiedTrack
is missing theis_playable
,linked_from
andrestrictions
fields.SimplifiedTrack::track_number
should be ausize
, again for ease of use.PublicUser::images
should be aVec<Image>
and representNone
as an emptyVec
.PrivateUser
is missing theproduct
field, which should be anenum Subscription { Premium, Free }
.(It's not that necessary)Country
should probably be moved to a separate crate like isocountryIncludeExternal
can just be replaced with abool
in the search function.ClientError
exists I'm not sure on the purpose ofError
andErrorKind
; are they necessary?Internals
bearer_auth
method.Option
andResult
parsing, avoid so manymatch
expressionsHashMap<&str, &str>
instead ofHashMap<String, String>
to avoid so manyto_string
andto_owned
?hashmap
macro likejson
?Is it possible thatendpoint_{get,post,put,delete}
methods also runconvert_result
inside?get_uri
andget_id
unwrap
andpanic
, or attach an explanation next to them.pub(in crate)
s inside alreadypub(in crate)
modules; only use(in crate)
when necessary (reduce their usage as much as possible or it becomes a mess). You can find all instances withrg 'pub\s*\((in crate|crate)\)'
The text was updated successfully, but these errors were encountered: