mirror of
https://github.com/ramsayleung/rspotify.git
synced 2026-04-25 23:45:52 +03:00
[GH-ISSUE #127] Meta-Issue #42
Labels
No labels
Stale
bug
discussion
enhancement
good first issue
good first issue
help wanted
pull-request
question
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/rspotify#42
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @Kestrer on GitHub (Sep 13, 2020).
Original GitHub issue: https://github.com/ramsayleung/rspotify/issues/127
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 theclientmodule should be moved to the crate root to make it easier to access.senum; this module contains all the enums used (and alsoErrorfor some reason), but could be split into more fitting modules such asmodel.cli/tui/terminal/consolemodule and feature-gated.Spotify::trackstake in aVec<&str>/Vec<String>/&[String]/&[&str], when they should take in animpl IntoIterator<Item = &str>.Stringas parameters instead of&str.limitandoffsetparameters should universally beusizes as they are easier to work with from Rust.Default::default impl(seeTokenInfo::default)Spotify::get_urishould useformat!:Spotify::get_idOAuth
TokenInfostoresexpires_inas au32, and additionally has theexpires_atfield. This is not easy to use, instead it should have a singleexpiresfield with the typeInstant.TokenInfostores ascope: String, when it should store ascope: Vec<String>obtained by splitting the scopes by spaces.TokenInfostorestoken_type, which is useless since it is alwaysBearer.SpotifyClientCredentialsandSpotifyOAuthhave a superfluousSpotifyprefix; justClientCredentialswould be shorter and not duplicate the crate name.SpotifyClientCredentialscontains not only the user's client credentials but also the token info if present, so the name is misleading. In fact theSpotifyOAuthstruct duplicates theclient_idandclient_secretfields due to the lack of a realClientCredentialstype.Clientis created every timefetch_access_tokenis called. This is very inefficient. Instead, the authorization logic should be moved toSpotifyallowing it to reuse the same client.token_infois never mutated onSpotifyClientCredentials. This means that once the initial token has expiredrspotifywill get a new token every time. The solution is to wrapTokenInfointo aMutexand mutate it when appropriate.Spotifycontains anaccess_tokentwice; once in the struct itself and once inclient_credentials_manager.token_info_access_token.Utils
convert_map_to_stringis better served byserde_urlencodedor even better Reqwest'squerymethod.generate_random_stringshould be private.datetime_to_timestampis only necessary due to this library's use of numbers instead ofInstants orDurations.tokenfunctions should probably be methods ofSpotifyinstead of standalone functions.utilsmodule can be removed entirely, and any remaining functions can simply be placed in the crate root for easy access.Model
modelmodule 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::FullAlbuminstead of the overly verboserspotify::model::album::FullAlbum.PartialEq(andEqif possible) for everything._typefields 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 exampleTypeAlbumwhich is represented in JSON as only"album". This avoids carrying extra data while still storing thetypefield.Theuriandhreffields are unnecessary, since it can be recreated easily. Make these methods instead.copyrightsis represented as aVec<HashMap<String, String>>; a much more accurate representation isVec<Copyright>andFullAlbum::release_dateandFullAlbum::release_date_precisionareStrings. Instead, it should be deserialized into a proper date type likeNaiveDateand aenum DatePrecision { Year, Month, Day }.SimplifiedEpisodeandFullEpisode.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.Restrictionsis located in thealbummodule, despite it not having any particular relevance to albums.{FullArtist, FullPlaylist}::followersis aHashMap<String, Option<Value>>. It should be astruct Followers { total: usize }.AudioAnalysisSection::modeandAudioFeatures::modearef32s but should beOption<Mode>s whereenum Mode { Major, Minor }as it is more useful.AudioAnalysisSectionandAudioAnalysisSegmentcould contain a#[serde::flatten]edTimeIntervalinstead of the having the fields inline.AudioAnalysisMeasureis 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_urlandAudioFeatures::track_hrefaren't useful for the user, since they will access it via this library.AudioFeatures::duration_msshould be replaced with adurationof typeDuration.[ ]Contextcontains auri, but since those aren't particularly useful it should be replaced with a transformer into anid.Actions::disallowscan be replaced with aVec<DisallowKey>orHashSet<DisallowKey>by removing all entires whose value isfalse, which will result in a simpler API.CurrentlyPlaybackshould be renamedCurrentPlayback, sinceCurrentlyPlaybackdoesn't make sense.CurrentlyPlayingContext::timestampshould be aDateTime<Utc>.CurrentlyPlayingContext::progress_msshould beprogress: Duration.CurrentlyPlaybackis a superset ofCurrentlyPlaying, it should probably contain it instead of containing all its fields.Deviceis missing the fieldis_private_session: bool.Device::idandDevice::volume_percentcan benullin the Spotify API, so they should be anOption.FullPlayingContextdoesn't exist in the Spotify API. It should be replaced withCurrentlyPlaybackContext.SimplifiedPlayingContextalso doesn't exist, and should be replaced withCurrentlyPlayingContext.CUDResultis oddly named; it should refer to playlists in its title.PlaylistResultmight be a better name. But it's not even necessary at all, the functions can just returnStringdirectly.Single-item modules likeimageshould have their items placed in the above scope.Offsetshould be represented as aType+idsince that is easier for the user.Offset::positionshould be aDuration.Page::{limit, offset, total}should beusizes, because they're easier to work with.Page::{next, previous}are not useful to the user since they'll use this program's API.CursorBasedPageshould also useusizes.CursorBasedPageshould also not include the URLs.Playingshould be replaced withCurrentlyPlayingContext, since it is the same.SimplifiedPlaylist::tracksshould be astruct Tracks { total: usize }; this isn't document but is what the API returns.PlaylistTrack::added_atcan benullso should beOption.PlaylistTrackis poorly named in the Spotify API. It should be calledPlaylistItemsince it can also contain episodes.PlaylistTrack/PlaylistItemto contain episodes.RecommendationsSeed::{after_filtering_size, after_relinking_size, initial_pool_size}should beusizes since they are easier to use.(You have to specify theSearchResultshould be a struct not an enum, since you can search for multiple items at once.searchTypewhen usingsearchendpoint, so you can't search for multiple items at once.)SimplifiedEpisode::duration_msshould be aDuration.languageshouldn't be inSimplifiedEpisodeif it's deprecated.ResumePoint::resume_position_msshould be aDuration.SimplifiedTrack::disc_numbershould be ausize, for ease of use.SimplfiiedTrackis missing theis_playable,linked_fromandrestrictionsfields.SimplifiedTrack::track_numbershould be ausize, again for ease of use.PublicUser::imagesshould be aVec<Image>and representNoneas an emptyVec.PrivateUseris missing theproductfield, which should be anenum Subscription { Premium, Free }.(It's not that necessary)Countryshould probably be moved to a separate crate like isocountryIncludeExternalcan just be replaced with aboolin the search function.ClientErrorexists I'm not sure on the purpose ofErrorandErrorKind; are they necessary?Internals
bearer_authmethod.OptionandResultparsing, avoid so manymatchexpressionsHashMap<&str, &str>instead ofHashMap<String, String>to avoid so manyto_stringandto_owned?hashmapmacro likejson?Is it possible thatendpoint_{get,post,put,delete}methods also runconvert_resultinside?get_uriandget_idunwrapandpanic, 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)\)'@marioortizmanero commented on GitHub (Sep 13, 2020):
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
browser(it was originally going to becli), which is activated by default and can be used to disable theutil::request_tokenand similar methods for those that don't need CLI functionalities. Here's the related issue: #108 and the PR that implemented it: #110 (it's still in master, unreleased). I figured that these functions were used so much thatbrowsershould be enabled by default, but I would love to hear what you think about that.That is something I realized and disliked as well. I opened the #109 issue, which proposes using
derive_builderto have a consistent pattern. But I think it doesn't play well with the blocking module when trying to avoid code repetition. Once #120 is done I can figure that out.I was thinking that we could create a custom
scopestruct/enum to hold these values instead of strings. But it might be overkill, I'm not really sure.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
SpotifyClientCredentialsjustCredentials. They're obviously for the client.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:
@Kestrer commented on GitHub (Sep 13, 2020):
Thanks for the response!
That's good. However nothing in
util::request_tokensays "I read from stdin", so especially ifbrowseris activated by default users could very likely accidentally use it in an app that shouldn't read from stdin - this is why having a CLI-dedicated module might be a good idea, or renaming it to something liketerminal_request_token.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
#[non_exhaustive] enum TokenType { Bearer }.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.
@marioortizmanero commented on GitHub (Sep 14, 2020):
Yes, the documentation could definitely be improved. I'll take a look at what other functions use stdin and add them to a
clifeature.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.
@ramsayleung commented on GitHub (Sep 18, 2020):
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
senumis a little bit clumsy, I would be a good idea to split it up.As for the original thought of
limitandoffsetparameters, I have explained it in this issue: https://github.com/ramsayleung/rspotify/pull/120#issuecomment-683361179It's better to reused the mature library instead of reinventing wheel.
No really,
generate_random_stringanddatetime_to_timestampare used byblockingmodule andasyncmodule, which should not be private. And it's unnecessary to maintain two set of utility functions, I have removed some of them as much as possible.use Instant is a great idea, but the original reason of using
expires_atandexpires_inisserdedoesn't support to deserialize/serializeInstantnatively.yes, it is always
Bearerfor now, it's a field designed to support other type in the feature. If it'sBearerforever, Spotify API doesn't need this field: https://developer.spotify.com/documentation/general/guides/authorization-guide/well,
limit,offset,totalare allu32now, and the difference betweenu32andusizeis thatu32is fixed bytes length (width),usizedepends on architecture (x64 or x86), andlimit,offset,totalhave nothing to do with the size of address space, so I prefer to keep them asu32You have pointed a lot things out about
Duration, the reason why I preferredu32toDurationis thatu32is more friendly withserde. Thedurationfield returned from most of endpoints is integer, it's not that easy todeserializeto aDurationstruct.It's a good idea, but I prefer to keep
modelmodule name likerspotify::model::FullAlbumWhat's the point of this suggestion?
Is there any link broken and inconsistent documentation? Would you like to point it out.
@marioortizmanero commented on GitHub (Sep 18, 2020):
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.
@Kestrer commented on GitHub (Sep 23, 2020):
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
]h,] hetc in all the source code.@ramsayleung commented on GitHub (Oct 24, 2020):
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
usizeis the easiest to use? Sorry for not getting your point.Check this post for more details, I32 vs isize, u32 vs usize, I agreed with cuviper's point:
I agreed with the later part,
release_date_precisionshould be aenum DatePrecision { Year, Month, Day }., but as forrelease_date, it's not that easy to use a date type likeNaiveDatewhich could be "1981-12-15". "1981", "1981-12", depending on the precision, butNaiveDatecan't have a empty month and empty day.I don't think it's necessary, probably Spotify will add some new values for
include_external, thenboolwill not be a good way to go.@marioortizmanero commented on GitHub (Feb 25, 2021):
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
get_type()method or something like that to access the type if necessary. It might be a better idea because it doesn't need macros, theAlbumvariants are all grouped up in the same struct instead of multiple ones, and it's type safe.Loving how we're progressing on this btw!! Not that much work to close it :)
@kstep commented on GitHub (Mar 9, 2021):
I think some points can be marked complete now:
@marioortizmanero commented on GitHub (Mar 9, 2021):
Sure, updated them
@marioortizmanero commented on GitHub (Apr 20, 2021):
Didn't mean to close this, whoops
@ramsayleung commented on GitHub (Apr 20, 2021):
As for this TODO item, I have tried, the problem I have occured was that not all endpoints need to return a struct implemented
Deserializetrait, which means it doesn't need to callconvert_resultfunction. If we runconvert_resultinsideendpoint_{get,post,put,delete}methods, We have to distinguish them betweenDeserialized structureand something like(). The below code doesn't callconvert_resultfunction:@marioortizmanero commented on GitHub (Apr 20, 2021):
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.
@ramsayleung commented on GitHub (Apr 23, 2021):
As for this TODO Item, we have changed the functions' signature from
Vec<String>/&[String]toimpl IntoIterator<Item = &'a str>, except for the fields which containsVec<T>insrc/modelmodule, is there anything else we need to change toIterator?There is no
panicstatement left in this library, as for theunwrapstatement, there are two usages:I am thinking about is it necessary to remove these two
unwrapstatements, what do you guys think?@marioortizmanero commented on GitHub (Apr 23, 2021):
and nothing else afaik.
I think it's safe to assume that we can use
unwrap()in the first one, because even therandcrate does.And the second does have an explanation, so no issue with that.
I thought we had more
unwraps, that's nice :)@ramsayleung commented on GitHub (Apr 24, 2021):
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
iterator:It's nice to have more
unwraps:) ?@marioortizmanero commented on GitHub (Apr 24, 2021):
You could've done a
collectintoHashMap<String, String>instead of using afor_each. Also, does theflat_mapreally need.clone()? can't you just removemovefrom the closure?No, the opposite, it's nice not to have them :)
@marioortizmanero commented on GitHub (Jul 8, 2021):
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 :)
@ramsayleung commented on GitHub (Jul 8, 2021):
Wish you have a great holiday :)
@marioortizmanero commented on GitHub (Sep 25, 2021):
About this one:
It doesn't really matter now that we have custom ID types. Instead of:
The user can just use:
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
Contextstruct, which is a pain and not really worth it considering the usage is basically the same, and that it would add an unnecessary overhead when deserializing.@marioortizmanero commented on GitHub (Oct 8, 2021):
I can't believe we're closing this finally :D