Skip to content

Commit

Permalink
New type safe system with traits
Browse files Browse the repository at this point in the history
  • Loading branch information
GartoxFR committed Oct 19, 2022
1 parent 82878ed commit 7995a07
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 67 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ log = "0.4.14"
maybe-async = "0.2.6"
serde = { version = "1.0.130", default-features = false }
serde_json = "1.0.67"
strum = { version = "0.24.0", features = ["derive"] }
sha2 = "0.10.0"
thiserror = "1.0.29"
url = "2.2.2"
Expand Down
8 changes: 5 additions & 3 deletions examples/search_query.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use rspotify::search::SearchQuery;
use rspotify::search::{
AlbumSearchFilter, ArtistSearchFilter, SearchQuery, TrackSearchFilter, Tracks,
};

#[tokio::main]
async fn main() {
let query: String = SearchQuery::default()
.any("any")
let query: String = SearchQuery::<Tracks>::default()
.any("Exemple any")
.artist("Lisa")
.track("Demon slayer")
.album("Another Album")
Expand Down
18 changes: 18 additions & 0 deletions rspotify-model/src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::{
SimplifiedShow,
};

use strum::Display;

/// Search for playlists
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct SearchPlaylists {
Expand Down Expand Up @@ -60,3 +62,19 @@ pub enum SearchResult {
#[serde(rename = "episodes")]
Episodes(Page<SimplifiedEpisode>),
}

#[derive(Debug, Display, PartialEq, Eq, Hash)]
#[strum(serialize_all = "snake_case")]
pub enum SearchFilter {
Album,
Artist,
Track,
Year,
Upc,
#[strum(serialize = "tag:hipster")]
TagHipster,
#[strum(serialize = "tag:new")]
TagNew,
Isrc,
Genre,
}
8 changes: 3 additions & 5 deletions src/clients/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::{
http::{BaseHttpClient, Form, Headers, HttpClient, Query},
join_ids,
model::*,
search::SearchQuery,
sync::Mutex,
util::build_map,
ClientResult, Config, Credentials, Token,
Expand Down Expand Up @@ -437,9 +436,9 @@ where
/// relevant audio content that is hosted externally.
///
/// [Reference](https://developer.spotify.com/documentation/web-api/reference/#/operations/search)
async fn search<T: Into<String> + Send>(
async fn search(
&self,
q: T,
q: &str,
_type: SearchType,
market: Option<Market>,
include_external: Option<IncludeExternal>,
Expand All @@ -448,9 +447,8 @@ where
) -> ClientResult<SearchResult> {
let limit = limit.map(|s| s.to_string());
let offset = offset.map(|s| s.to_string());
let q: String = q.into();
let params = build_map([
("q", Some(&q)),
("q", Some(q)),
("type", Some(_type.into())),
("market", market.map(Into::into)),
("include_external", include_external.map(Into::into)),
Expand Down
251 changes: 202 additions & 49 deletions src/search/mod.rs
Original file line number Diff line number Diff line change
@@ -1,87 +1,234 @@
use std::collections::HashMap;
use std::marker::PhantomData;

use strum::Display;

#[derive(Debug, Display, PartialEq, Eq, Hash)]
#[strum(serialize_all = "snake_case")]
pub enum SearchFilter {
Album,
Artist,
Track,
Year,
Upc,
#[strum(serialize = "tag:hipster")]
TagHipster,
#[strum(serialize = "tag:new")]
TagNew,
Isrc,
Genre,
}

#[derive(Debug, Default)]
pub struct SearchQuery {
no_filter_query: String,
query_map: HashMap<SearchFilter, String>,
}

impl SearchQuery {
pub fn any<T: Into<String>>(&mut self, str: T) -> &mut Self {
self.no_filter_query = str.into();
use rspotify_model::{SearchFilter, SearchType};

pub struct Artists;
pub struct Albums;
pub struct Playlists;
pub struct Tracks;
pub struct Shows;
pub struct Episodes;

pub struct SearchQuery<T> {
base_search: String,
filters: HashMap<SearchFilter, String>,
pub search_type: SearchType,
_marker: PhantomData<T>,
}

impl YearSearchFilter for SearchQuery<Albums> {}
impl YearSearchFilter for SearchQuery<Artists> {}
impl YearSearchFilter for SearchQuery<Tracks> {}

impl ArtistSearchFilter for SearchQuery<Albums> {}
impl ArtistSearchFilter for SearchQuery<Artists> {}
impl ArtistSearchFilter for SearchQuery<Tracks> {}

impl AlbumSearchFilter for SearchQuery<Albums> {}
impl AlbumSearchFilter for SearchQuery<Tracks> {}

impl GenreSearchFilter for SearchQuery<Artists> {}
impl GenreSearchFilter for SearchQuery<Tracks> {}

impl TrackSearchFilter for SearchQuery<Tracks> {}

impl IsrcSearchFilter for SearchQuery<Tracks> {}

impl UpcSearchFilter for SearchQuery<Albums> {}

impl TagHipsterSearchFilter for SearchQuery<Albums> {}

impl TagNewSearchFilter for SearchQuery<Albums> {}

pub trait BaseSearchQuery {
fn add_filter(&mut self, search_filter: SearchFilter, str: String);
}

impl<T> BaseSearchQuery for SearchQuery<T> {
fn add_filter(&mut self, search_filter: SearchFilter, str: String) {
self.filters.insert(search_filter, str);
}
}

pub trait ArtistSearchFilter: BaseSearchQuery + Sized {
fn artist<T: Into<String>>(mut self, str: T) -> Self {
self.add_filter(SearchFilter::Artist, str.into());
self
}
}

pub fn album<T: Into<String>>(&mut self, str: T) -> &mut Self {
self.query_map.insert(SearchFilter::Album, str.into());
pub trait AlbumSearchFilter: BaseSearchQuery + Sized {
fn album<T: Into<String>>(mut self, str: T) -> Self {
self.add_filter(SearchFilter::Album, str.into());
self
}
}

pub fn artist<T: Into<String>>(&mut self, str: T) -> &mut Self {
self.query_map.insert(SearchFilter::Artist, str.into());
pub trait TrackSearchFilter: BaseSearchQuery + Sized {
fn track<T: Into<String>>(mut self, str: T) -> Self {
self.add_filter(SearchFilter::Track, str.into());
self
}
}

pub fn track<T: Into<String>>(&mut self, str: T) -> &mut Self {
self.query_map.insert(SearchFilter::Track, str.into());
pub trait YearSearchFilter: BaseSearchQuery + Sized {
fn year<T: Into<String>>(mut self, str: T) -> Self {
self.add_filter(SearchFilter::Year, str.into());
self
}
}

pub fn year<T: Into<String>>(&mut self, str: T) -> &mut Self {
self.query_map.insert(SearchFilter::Year, str.into());
pub trait UpcSearchFilter: BaseSearchQuery + Sized {
fn upc<T: Into<String>>(mut self, str: T) -> Self {
self.add_filter(SearchFilter::Upc, str.into());
self
}
}

pub fn upc<T: Into<String>>(&mut self, str: T) -> &mut Self {
self.query_map.insert(SearchFilter::Upc, str.into());
pub trait GenreSearchFilter: BaseSearchQuery + Sized {
fn genre<T: Into<String>>(mut self, str: T) -> Self {
self.add_filter(SearchFilter::Genre, str.into());
self
}
}

pub fn tag_new(&mut self) -> &mut Self {
self.query_map.insert(SearchFilter::TagNew, "".into());
pub trait TagHipsterSearchFilter: BaseSearchQuery + Sized {
fn tag_hipster<T: Into<String>>(mut self) -> Self {
self.add_filter(SearchFilter::TagHipster, "".into());
self
}
}

pub fn tag_hipster(&mut self) -> &mut Self {
self.query_map.insert(SearchFilter::TagHipster, "".into());
pub trait TagNewSearchFilter: BaseSearchQuery + Sized {
fn tag_new<T: Into<String>>(mut self) -> Self {
self.add_filter(SearchFilter::TagNew, "".into());
self
}
}

pub fn isrc<T: Into<String>>(&mut self, str: T) -> &mut Self {
self.query_map.insert(SearchFilter::Isrc, str.into());
pub trait IsrcSearchFilter: BaseSearchQuery + Sized {
fn isrc<T: Into<String>>(mut self, str: T) -> Self {
self.add_filter(SearchFilter::Isrc, str.into());
self
}
}

pub fn genre<T: Into<String>>(&mut self, str: T) -> &mut Self {
self.query_map.insert(SearchFilter::Genre, str.into());
impl<T> SearchQuery<T> {
pub fn any(mut self, str: impl Into<String>) -> Self {
self.base_search = str.into();
self
}
}

impl From<&mut SearchQuery> for String {
fn from(val: &mut SearchQuery) -> Self {
let mut rep = val.no_filter_query.clone();
impl Default for SearchQuery<Playlists> {
fn default() -> Self {
Self::new()
}
}

impl Default for SearchQuery<Albums> {
fn default() -> Self {
Self::new()
}
}

impl Default for SearchQuery<Artists> {
fn default() -> Self {
Self::new()
}
}

impl Default for SearchQuery<Tracks> {
fn default() -> Self {
Self::new()
}
}

impl Default for SearchQuery<Shows> {
fn default() -> Self {
Self::new()
}
}

impl Default for SearchQuery<Episodes> {
fn default() -> Self {
Self::new()
}
}

impl SearchQuery<Playlists> {
pub fn new() -> Self {
SearchQuery {
base_search: String::default(),
filters: HashMap::default(),
search_type: SearchType::Playlist,
_marker: PhantomData::default(),
}
}
}

impl SearchQuery<Albums> {
pub fn new() -> Self {
SearchQuery {
base_search: String::default(),
filters: HashMap::default(),
search_type: SearchType::Album,
_marker: PhantomData::default(),
}
}
}

impl SearchQuery<Artists> {
pub fn new() -> Self {
SearchQuery {
base_search: String::default(),
filters: HashMap::default(),
search_type: SearchType::Artist,
_marker: PhantomData::default(),
}
}
}

impl SearchQuery<Tracks> {
pub fn new() -> Self {
SearchQuery {
base_search: String::default(),
filters: HashMap::default(),
search_type: SearchType::Track,
_marker: PhantomData::default(),
}
}
}

impl SearchQuery<Shows> {
pub fn new() -> Self {
SearchQuery {
base_search: String::default(),
filters: HashMap::default(),
search_type: SearchType::Show,
_marker: PhantomData::default(),
}
}
}

impl SearchQuery<Episodes> {
pub fn new() -> Self {
SearchQuery {
base_search: String::default(),
filters: HashMap::default(),
search_type: SearchType::Episode,
_marker: PhantomData::default(),
}
}
}

impl<T> From<&SearchQuery<T>> for String {
fn from(val: &SearchQuery<T>) -> Self {
let mut rep = val.base_search.clone();
rep.push(' ');
rep.push_str(
val.query_map
val.filters
.iter()
.map(|entry| match entry.0 {
SearchFilter::TagNew | SearchFilter::TagHipster => format!("{} ", entry.0),
Expand All @@ -94,3 +241,9 @@ impl From<&mut SearchQuery> for String {
rep
}
}

impl<T> From<SearchQuery<T>> for String {
fn from(val: SearchQuery<T>) -> Self {
(&val).into()
}
}
Loading

0 comments on commit 7995a07

Please sign in to comment.