diff --git a/account.go b/account.go index 738b808..05935dc 100644 --- a/account.go +++ b/account.go @@ -7,20 +7,9 @@ import ( // AccountService handles communication with the account // related methods of the Reddit API. -type AccountService interface { - Info(ctx context.Context) (*User, *Response, error) - Karma(ctx context.Context) ([]SubredditKarma, *Response, error) - Settings(ctx context.Context) (*Settings, *Response, error) - UpdateSettings(ctx context.Context, settings *Settings) (*Settings, *Response, error) - Trophies(ctx context.Context) ([]Trophy, *Response, error) -} - -// AccountServiceOp implements the AccountService interface. -type AccountServiceOp struct { - client *Client -} - -var _ AccountService = &AccountServiceOp{} +// +// Reddit API docs: https://www.reddit.com/dev/api/#section_account +type AccountService service type rootSubredditKarma struct { Kind string `json:"kind,omitempty"` @@ -219,7 +208,7 @@ type Settings struct { } // Info returns some general information about your account. -func (s *AccountServiceOp) Info(ctx context.Context) (*User, *Response, error) { +func (s *AccountService) Info(ctx context.Context) (*User, *Response, error) { path := "api/v1/me" req, err := s.client.NewRequest(http.MethodGet, path, nil) @@ -237,7 +226,7 @@ func (s *AccountServiceOp) Info(ctx context.Context) (*User, *Response, error) { } // Karma returns a breakdown of your karma per subreddit. -func (s *AccountServiceOp) Karma(ctx context.Context) ([]SubredditKarma, *Response, error) { +func (s *AccountService) Karma(ctx context.Context) ([]SubredditKarma, *Response, error) { path := "api/v1/me/karma" req, err := s.client.NewRequest(http.MethodGet, path, nil) @@ -255,7 +244,7 @@ func (s *AccountServiceOp) Karma(ctx context.Context) ([]SubredditKarma, *Respon } // Settings returns your account settings. -func (s *AccountServiceOp) Settings(ctx context.Context) (*Settings, *Response, error) { +func (s *AccountService) Settings(ctx context.Context) (*Settings, *Response, error) { path := "api/v1/me/prefs" req, err := s.client.NewRequest(http.MethodGet, path, nil) @@ -273,7 +262,7 @@ func (s *AccountServiceOp) Settings(ctx context.Context) (*Settings, *Response, } // UpdateSettings updates your account settings and returns the modified version. -func (s *AccountServiceOp) UpdateSettings(ctx context.Context, settings *Settings) (*Settings, *Response, error) { +func (s *AccountService) UpdateSettings(ctx context.Context, settings *Settings) (*Settings, *Response, error) { path := "api/v1/me/prefs" req, err := s.client.NewRequest(http.MethodPatch, path, settings) @@ -291,7 +280,7 @@ func (s *AccountServiceOp) UpdateSettings(ctx context.Context, settings *Setting } // Trophies returns a list of your trophies. -func (s *AccountServiceOp) Trophies(ctx context.Context) ([]Trophy, *Response, error) { +func (s *AccountService) Trophies(ctx context.Context) ([]Trophy, *Response, error) { path := "api/v1/me/trophies" req, err := s.client.NewRequest(http.MethodGet, path, nil) diff --git a/account_test.go b/account_test.go index f8b83d1..4192009 100644 --- a/account_test.go +++ b/account_test.go @@ -97,7 +97,7 @@ var expectedSettings = &Settings{ EnableVideoAutoplay: Bool(true), } -func TestAccountServiceOp_Info(t *testing.T) { +func TestAccountService_Info(t *testing.T) { setup() defer teardown() @@ -113,7 +113,7 @@ func TestAccountServiceOp_Info(t *testing.T) { assert.Equal(t, expectedInfo, info) } -func TestAccountServiceOp_Karma(t *testing.T) { +func TestAccountService_Karma(t *testing.T) { setup() defer teardown() @@ -129,7 +129,7 @@ func TestAccountServiceOp_Karma(t *testing.T) { assert.Equal(t, expectedKarma, karma) } -func TestAccountServiceOp_Settings(t *testing.T) { +func TestAccountService_Settings(t *testing.T) { setup() defer teardown() @@ -145,7 +145,7 @@ func TestAccountServiceOp_Settings(t *testing.T) { assert.Equal(t, expectedSettings, settings) } -func TestAccountServiceOp_UpdateSettings(t *testing.T) { +func TestAccountService_UpdateSettings(t *testing.T) { setup() defer teardown() diff --git a/comment.go b/comment.go index f4fdc69..c8b498e 100644 --- a/comment.go +++ b/comment.go @@ -9,28 +9,14 @@ import ( // CommentService handles communication with the comment // related methods of the Reddit API. -type CommentService interface { - Submit(ctx context.Context, id string, text string) (*Comment, *Response, error) - Edit(ctx context.Context, id string, text string) (*Comment, *Response, error) - Delete(ctx context.Context, id string) (*Response, error) +type CommentService service - Save(ctx context.Context, id string) (*Response, error) - Unsave(ctx context.Context, id string) (*Response, error) -} - -// CommentServiceOp implements the CommentService interface. -type CommentServiceOp struct { - client *Client -} - -var _ CommentService = &CommentServiceOp{} - -func (s *CommentServiceOp) isCommentID(id string) bool { +func (s *CommentService) isCommentID(id string) bool { return strings.HasPrefix(id, kindComment+"_") } // Submit submits a comment as a reply to a post or to another comment. -func (s *CommentServiceOp) Submit(ctx context.Context, id string, text string) (*Comment, *Response, error) { +func (s *CommentService) Submit(ctx context.Context, id string, text string) (*Comment, *Response, error) { path := "api/comment" form := url.Values{} @@ -55,7 +41,7 @@ func (s *CommentServiceOp) Submit(ctx context.Context, id string, text string) ( // Edit edits the comment with the id provided. // todo: don't forget to do this for posts -func (s *CommentServiceOp) Edit(ctx context.Context, id string, text string) (*Comment, *Response, error) { +func (s *CommentService) Edit(ctx context.Context, id string, text string) (*Comment, *Response, error) { if !s.isCommentID(id) { return nil, nil, fmt.Errorf("must provide comment id (starting with %s_); id provided: %q", kindComment, id) } @@ -84,7 +70,7 @@ func (s *CommentServiceOp) Edit(ctx context.Context, id string, text string) (*C // Delete deletes a comment via the id. // todo: don't forget to do this for posts. -func (s *CommentServiceOp) Delete(ctx context.Context, id string) (*Response, error) { +func (s *CommentService) Delete(ctx context.Context, id string) (*Response, error) { if !s.isCommentID(id) { return nil, fmt.Errorf("must provide comment id (starting with %s_); id provided: %q", kindComment, id) } @@ -108,7 +94,7 @@ func (s *CommentServiceOp) Delete(ctx context.Context, id string) (*Response, er } // Save saves a comment. -func (s *CommentServiceOp) Save(ctx context.Context, id string) (*Response, error) { +func (s *CommentService) Save(ctx context.Context, id string) (*Response, error) { if !s.isCommentID(id) { return nil, fmt.Errorf("must provide comment id (starting with %s_); id provided: %q", kindComment, id) } @@ -132,7 +118,7 @@ func (s *CommentServiceOp) Save(ctx context.Context, id string) (*Response, erro } // Unsave unsaves a comment. -func (s *CommentServiceOp) Unsave(ctx context.Context, id string) (*Response, error) { +func (s *CommentService) Unsave(ctx context.Context, id string) (*Response, error) { if !s.isCommentID(id) { return nil, fmt.Errorf("must provide comment id (starting with t1_); id provided: %q", id) } diff --git a/comment_test.go b/comment_test.go index 25c6602..5023d6a 100644 --- a/comment_test.go +++ b/comment_test.go @@ -38,11 +38,11 @@ var expectedCommentSubmitOrEdit = &Comment{ PostID: "t3_link1", } -func TestCommentServiceOp_Submit(t *testing.T) { +func TestCommentService_Submit(t *testing.T) { setup() defer teardown() - commentBlob := readFileContents(t, "testdata/comment-submit-edit.json") + blob := readFileContents(t, "testdata/comment-submit-edit.json") mux.HandleFunc("/api/comment", func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) @@ -57,7 +57,7 @@ func TestCommentServiceOp_Submit(t *testing.T) { assert.NoError(t, err) assert.Equal(t, form, r.PostForm) - fmt.Fprint(w, commentBlob) + fmt.Fprint(w, blob) }) comment, _, err := client.Comment.Submit(ctx, "t1_test", "test comment") @@ -65,11 +65,11 @@ func TestCommentServiceOp_Submit(t *testing.T) { assert.Equal(t, expectedCommentSubmitOrEdit, comment) } -func TestCommentServiceOp_Edit(t *testing.T) { +func TestCommentService_Edit(t *testing.T) { setup() defer teardown() - commentBlob := readFileContents(t, "testdata/comment-submit-edit.json") + blob := readFileContents(t, "testdata/comment-submit-edit.json") mux.HandleFunc("/api/editusertext", func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodPost, r.Method) @@ -84,7 +84,7 @@ func TestCommentServiceOp_Edit(t *testing.T) { assert.NoError(t, err) assert.Equal(t, form, r.PostForm) - fmt.Fprint(w, commentBlob) + fmt.Fprint(w, blob) }) _, _, err := client.Comment.Edit(ctx, "t3_test", "test comment") @@ -95,7 +95,7 @@ func TestCommentServiceOp_Edit(t *testing.T) { assert.Equal(t, expectedCommentSubmitOrEdit, comment) } -func TestCommentServiceOp_Delete(t *testing.T) { +func TestCommentService_Delete(t *testing.T) { setup() defer teardown() @@ -120,7 +120,7 @@ func TestCommentServiceOp_Delete(t *testing.T) { assert.Equal(t, http.StatusOK, res.StatusCode) } -func TestCommentServiceOp_Save(t *testing.T) { +func TestCommentService_Save(t *testing.T) { setup() defer teardown() @@ -133,8 +133,6 @@ func TestCommentServiceOp_Save(t *testing.T) { err := r.ParseForm() assert.NoError(t, err) assert.Equal(t, form, r.PostForm) - - fmt.Fprint(w, `{}`) }) _, err := client.Comment.Save(ctx, "t3_test") @@ -145,7 +143,7 @@ func TestCommentServiceOp_Save(t *testing.T) { assert.Equal(t, http.StatusOK, res.StatusCode) } -func TestCommentServiceOp_Unsave(t *testing.T) { +func TestCommentService_Unsave(t *testing.T) { setup() defer teardown() @@ -158,8 +156,6 @@ func TestCommentServiceOp_Unsave(t *testing.T) { err := r.ParseForm() assert.NoError(t, err) assert.Equal(t, form, r.PostForm) - - fmt.Fprint(w, `{}`) }) _, err := client.Comment.Unsave(ctx, "t3_test") diff --git a/flair.go b/flair.go index 86fdac2..1c1fa3a 100644 --- a/flair.go +++ b/flair.go @@ -6,19 +6,11 @@ import ( "net/http" ) -// FlairService handles communication with the user -// related methods of the Reddit API -type FlairService interface { - GetFromSubreddit(ctx context.Context, name string) ([]Flair, *Response, error) - GetFromSubredditV2(ctx context.Context, name string) ([]FlairV2, *Response, error) -} - -// FlairServiceOp implements the FlairService interface -type FlairServiceOp struct { - client *Client -} - -var _ FlairService = &FlairServiceOp{} +// FlairService handles communication with the flair +// related methods of the Reddit API. +// +// Reddit API docs: https://www.reddit.com/dev/api/#section_flair +type FlairService service // Flair is a flair on Reddit type Flair struct { @@ -38,7 +30,7 @@ type FlairV2 struct { } // GetFromSubreddit returns the flairs from the subreddit -func (s *FlairServiceOp) GetFromSubreddit(ctx context.Context, name string) ([]Flair, *Response, error) { +func (s *FlairService) GetFromSubreddit(ctx context.Context, name string) ([]Flair, *Response, error) { path := fmt.Sprintf("r/%s/api/user_flair", name) req, err := s.client.NewRequest(http.MethodGet, path, nil) @@ -56,7 +48,7 @@ func (s *FlairServiceOp) GetFromSubreddit(ctx context.Context, name string) ([]F } // GetFromSubredditV2 returns the flairs from the subreddit -func (s *FlairServiceOp) GetFromSubredditV2(ctx context.Context, name string) ([]FlairV2, *Response, error) { +func (s *FlairService) GetFromSubredditV2(ctx context.Context, name string) ([]FlairV2, *Response, error) { path := fmt.Sprintf("r/%s/api/user_flair_v2", name) req, err := s.client.NewRequest(http.MethodGet, path, nil) diff --git a/flair_test.go b/flair_test.go index 015de01..6702893 100644 --- a/flair_test.go +++ b/flair_test.go @@ -40,15 +40,15 @@ var expectedFlairsV2 = []FlairV2{ }, } -func TestFlairServiceOp_GetFlairs(t *testing.T) { +func TestFlairService_GetFlairs(t *testing.T) { setup() defer teardown() - flairsBlob := readFileContents(t, "testdata/flairs.json") + blob := readFileContents(t, "testdata/flairs.json") mux.HandleFunc("/r/subreddit/api/user_flair", func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodGet, r.Method) - fmt.Fprint(w, flairsBlob) + fmt.Fprint(w, blob) }) flairs, _, err := client.Flair.GetFromSubreddit(ctx, "subreddit") @@ -56,15 +56,15 @@ func TestFlairServiceOp_GetFlairs(t *testing.T) { assert.Equal(t, expectedFlairs, flairs) } -func TestFlairServiceOp_GetFlairsV2(t *testing.T) { +func TestFlairService_GetFlairsV2(t *testing.T) { setup() defer teardown() - flairsV2Blob := readFileContents(t, "testdata/flairs-v2.json") + blob := readFileContents(t, "testdata/flairs-v2.json") mux.HandleFunc("/r/subreddit/api/user_flair_v2", func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodGet, r.Method) - fmt.Fprint(w, flairsV2Blob) + fmt.Fprint(w, blob) }) flairs, _, err := client.Flair.GetFromSubredditV2(ctx, "subreddit") diff --git a/geddit.go b/geddit.go index b557e76..1c1827b 100644 --- a/geddit.go +++ b/geddit.go @@ -91,21 +91,28 @@ type Client struct { // This is the client's user ID in Reddit's database. redditID string - Account AccountService - Comment CommentService - Flair FlairService - Listings ListingsService - Post PostService - Search SearchService - Subreddit SubredditService - User UserService - Vote VoteService + // Reuse a single struct instead of allocating one for each service on the heap. + common service + + Account *AccountService + Comment *CommentService + Flair *FlairService + Listings *ListingsService + Post *PostService + Search *SearchService + Subreddit *SubredditService + User *UserService + Vote *VoteService oauth2Transport *oauth2.Transport onRequestCompleted RequestCompletionCallback } +type service struct { + client *Client +} + // OnRequestCompleted sets the client's request completion callback. func (c *Client) OnRequestCompleted(rc RequestCompletionCallback) { c.onRequestCompleted = rc @@ -121,15 +128,16 @@ func newClient(httpClient *http.Client) *Client { c := &Client{client: httpClient, BaseURL: baseURL, TokenURL: tokenURL} - c.Account = &AccountServiceOp{client: c} - c.Comment = &CommentServiceOp{client: c} - c.Flair = &FlairServiceOp{client: c} - c.Listings = &ListingsServiceOp{client: c} - c.Post = &PostServiceOp{client: c} - c.Search = &SearchServiceOp{client: c} - c.Subreddit = &SubredditServiceOp{client: c} - c.User = &UserServiceOp{client: c} - c.Vote = &VoteServiceOp{client: c} + c.common.client = c + c.Account = (*AccountService)(&c.common) + c.Comment = (*CommentService)(&c.common) + c.Flair = (*FlairService)(&c.common) + c.Listings = (*ListingsService)(&c.common) + c.Post = (*PostService)(&c.common) + c.Search = (*SearchService)(&c.common) + c.Subreddit = (*SubredditService)(&c.common) + c.User = (*UserService)(&c.common) + c.Vote = (*VoteService)(&c.common) return c } diff --git a/geddit_test.go b/geddit_test.go index 7697a25..06ebeb9 100644 --- a/geddit_test.go +++ b/geddit_test.go @@ -66,10 +66,12 @@ func readFileContents(t *testing.T, filepath string) string { func testClientServices(t *testing.T, c *Client) { services := []string{ + "Account", "Comment", "Flair", "Listings", "Post", + "Search", "Subreddit", "User", "Vote", diff --git a/listings.go b/listings.go index 9688915..45d186e 100644 --- a/listings.go +++ b/listings.go @@ -8,23 +8,14 @@ import ( "strings" ) -// ListingsService handles communication with the post +// ListingsService handles communication with the listing // related methods of the Reddit API. -type ListingsService interface { - Get(ctx context.Context, ids ...string) ([]Post, []Comment, []Subreddit, *Response, error) - GetPosts(ctx context.Context, ids ...string) ([]Post, *Response, error) - GetPost(ctx context.Context, id string) (*PostAndComments, *Response, error) -} - -// ListingsServiceOp implements the Vote interface. -type ListingsServiceOp struct { - client *Client -} - -var _ ListingsService = &ListingsServiceOp{} +// +// Reddit API docs: https://www.reddit.com/dev/api/#section_listings +type ListingsService service // Get returns posts, comments, and subreddits from their IDs. -func (s *ListingsServiceOp) Get(ctx context.Context, ids ...string) ([]Post, []Comment, []Subreddit, *Response, error) { +func (s *ListingsService) Get(ctx context.Context, ids ...string) ([]Post, []Comment, []Subreddit, *Response, error) { type query struct { IDs []string `url:"id,comma"` } @@ -54,7 +45,7 @@ func (s *ListingsServiceOp) Get(ctx context.Context, ids ...string) ([]Post, []C } // GetPosts returns posts from their full IDs. -func (s *ListingsServiceOp) GetPosts(ctx context.Context, ids ...string) ([]Post, *Response, error) { +func (s *ListingsService) GetPosts(ctx context.Context, ids ...string) ([]Post, *Response, error) { if len(ids) == 0 { return nil, nil, errors.New("must provide at least 1 id") } @@ -77,7 +68,7 @@ func (s *ListingsServiceOp) GetPosts(ctx context.Context, ids ...string) ([]Post // GetPost returns a post with its comments. // The id here is the ID36 of the post, not its full id. // Example: instead of t3_abc123, use abc123. -func (s *ListingsServiceOp) GetPost(ctx context.Context, id string) (*PostAndComments, *Response, error) { +func (s *ListingsService) GetPost(ctx context.Context, id string) (*PostAndComments, *Response, error) { path := fmt.Sprintf("comments/%s", id) req, err := s.client.NewRequest(http.MethodGet, path, nil) if err != nil { diff --git a/post.go b/post.go index 51678ca..47336b5 100644 --- a/post.go +++ b/post.go @@ -9,31 +9,9 @@ import ( "github.com/google/go-querystring/query" ) -// PostService handles communication with the link (post) +// PostService handles communication with the post // related methods of the Reddit API. -type PostService interface { - SubmitSelf(ctx context.Context, opts SubmitSelfOptions) (*Submitted, *Response, error) - SubmitURL(ctx context.Context, opts SubmitURLOptions) (*Submitted, *Response, error) - - EnableReplies(ctx context.Context, id string) (*Response, error) - DisableReplies(ctx context.Context, id string) (*Response, error) - - MarkNSFW(ctx context.Context, id string) (*Response, error) - UnmarkNSFW(ctx context.Context, id string) (*Response, error) - - Spoiler(ctx context.Context, id string) (*Response, error) - Unspoiler(ctx context.Context, id string) (*Response, error) - - Hide(ctx context.Context, ids ...string) (*Response, error) - Unhide(ctx context.Context, ids ...string) (*Response, error) -} - -// PostServiceOp implements the PostService interface. -type PostServiceOp struct { - client *Client -} - -var _ PostService = &PostServiceOp{} +type PostService service type submittedLinkRoot struct { JSON struct { @@ -78,7 +56,7 @@ type SubmitURLOptions struct { } // SubmitSelf submits a self text post. -func (s *PostServiceOp) SubmitSelf(ctx context.Context, opts SubmitSelfOptions) (*Submitted, *Response, error) { +func (s *PostService) SubmitSelf(ctx context.Context, opts SubmitSelfOptions) (*Submitted, *Response, error) { type submit struct { SubmitSelfOptions Kind string `url:"kind,omitempty"` @@ -87,7 +65,7 @@ func (s *PostServiceOp) SubmitSelf(ctx context.Context, opts SubmitSelfOptions) } // SubmitURL submits a link post. -func (s *PostServiceOp) SubmitURL(ctx context.Context, opts SubmitURLOptions) (*Submitted, *Response, error) { +func (s *PostService) SubmitURL(ctx context.Context, opts SubmitURLOptions) (*Submitted, *Response, error) { type submit struct { SubmitURLOptions Kind string `url:"kind,omitempty"` @@ -95,7 +73,7 @@ func (s *PostServiceOp) SubmitURL(ctx context.Context, opts SubmitURLOptions) (* return s.submit(ctx, &submit{opts, "link"}) } -func (s *PostServiceOp) submit(ctx context.Context, v interface{}) (*Submitted, *Response, error) { +func (s *PostService) submit(ctx context.Context, v interface{}) (*Submitted, *Response, error) { path := "api/submit" form, err := query.Values(v) @@ -119,7 +97,7 @@ func (s *PostServiceOp) submit(ctx context.Context, v interface{}) (*Submitted, } // EnableReplies enables inbox replies for a thing created by the client. -func (s *PostServiceOp) EnableReplies(ctx context.Context, id string) (*Response, error) { +func (s *PostService) EnableReplies(ctx context.Context, id string) (*Response, error) { path := "api/sendreplies" form := url.Values{} @@ -135,7 +113,7 @@ func (s *PostServiceOp) EnableReplies(ctx context.Context, id string) (*Response } // DisableReplies dsables inbox replies for a thing created by the client. -func (s *PostServiceOp) DisableReplies(ctx context.Context, id string) (*Response, error) { +func (s *PostService) DisableReplies(ctx context.Context, id string) (*Response, error) { path := "api/sendreplies" form := url.Values{} @@ -151,7 +129,7 @@ func (s *PostServiceOp) DisableReplies(ctx context.Context, id string) (*Respons } // MarkNSFW marks a post as NSFW. -func (s *PostServiceOp) MarkNSFW(ctx context.Context, id string) (*Response, error) { +func (s *PostService) MarkNSFW(ctx context.Context, id string) (*Response, error) { path := "api/marknsfw" form := url.Values{} @@ -166,7 +144,7 @@ func (s *PostServiceOp) MarkNSFW(ctx context.Context, id string) (*Response, err } // UnmarkNSFW unmarks a post as NSFW. -func (s *PostServiceOp) UnmarkNSFW(ctx context.Context, id string) (*Response, error) { +func (s *PostService) UnmarkNSFW(ctx context.Context, id string) (*Response, error) { path := "api/unmarknsfw" form := url.Values{} @@ -181,7 +159,7 @@ func (s *PostServiceOp) UnmarkNSFW(ctx context.Context, id string) (*Response, e } // Spoiler marks a post as a spoiler. -func (s *PostServiceOp) Spoiler(ctx context.Context, id string) (*Response, error) { +func (s *PostService) Spoiler(ctx context.Context, id string) (*Response, error) { path := "api/spoiler" form := url.Values{} @@ -196,7 +174,7 @@ func (s *PostServiceOp) Spoiler(ctx context.Context, id string) (*Response, erro } // Unspoiler unmarks a post as a spoiler. -func (s *PostServiceOp) Unspoiler(ctx context.Context, id string) (*Response, error) { +func (s *PostService) Unspoiler(ctx context.Context, id string) (*Response, error) { path := "api/unspoiler" form := url.Values{} @@ -211,7 +189,7 @@ func (s *PostServiceOp) Unspoiler(ctx context.Context, id string) (*Response, er } // Hide hides links with the specified ids. -func (s *PostServiceOp) Hide(ctx context.Context, ids ...string) (*Response, error) { +func (s *PostService) Hide(ctx context.Context, ids ...string) (*Response, error) { if len(ids) == 0 { return nil, errors.New("must provide at least 1 id") } @@ -230,7 +208,7 @@ func (s *PostServiceOp) Hide(ctx context.Context, ids ...string) (*Response, err } // Unhide unhides links with the specified ids. -func (s *PostServiceOp) Unhide(ctx context.Context, ids ...string) (*Response, error) { +func (s *PostService) Unhide(ctx context.Context, ids ...string) (*Response, error) { if len(ids) == 0 { return nil, errors.New("must provide at least 1 id") } diff --git a/post_test.go b/post_test.go index 9cba6b4..acdb727 100644 --- a/post_test.go +++ b/post_test.go @@ -1,7 +1,6 @@ package geddit import ( - "fmt" "net/http" "net/url" "testing" @@ -9,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestPostServiceOp_Hide(t *testing.T) { +func TestPostService_Hide(t *testing.T) { setup() defer teardown() @@ -22,8 +21,6 @@ func TestPostServiceOp_Hide(t *testing.T) { err := r.ParseForm() assert.NoError(t, err) assert.Equal(t, form, r.PostForm) - - fmt.Fprint(w, `{}`) }) _, err := client.Post.Hide(ctx) @@ -34,7 +31,7 @@ func TestPostServiceOp_Hide(t *testing.T) { assert.Equal(t, http.StatusOK, res.StatusCode) } -func TestPostServiceOp_Unhide(t *testing.T) { +func TestPostService_Unhide(t *testing.T) { setup() defer teardown() @@ -47,8 +44,6 @@ func TestPostServiceOp_Unhide(t *testing.T) { err := r.ParseForm() assert.NoError(t, err) assert.Equal(t, form, r.PostForm) - - fmt.Fprint(w, `{}`) }) _, err := client.Post.Unhide(ctx) diff --git a/search.go b/search.go index 04555af..6fa9625 100644 --- a/search.go +++ b/search.go @@ -9,24 +9,16 @@ import ( ) // SearchService handles communication with the search -// related methods of the Reddit API -// IMPORTANT: for searches to include NSFW results, the -// user must check the following in their preferences: +// related methods of the Reddit API. +// +// For searches to include NSFW results, the user must +// enable the following setting in their preferences: // "include not safe for work (NSFW) search results in searches" // Note: The "limit" parameter in searches is prone to inconsistent -// behaviour. -type SearchService interface { - Posts(ctx context.Context, query string, subreddits []string, opts ...SearchOptionSetter) (*Posts, *Response, error) - Subreddits(ctx context.Context, query string, opts ...SearchOptionSetter) (*Subreddits, *Response, error) - Users(ctx context.Context, query string, opts ...SearchOptionSetter) (*Users, *Response, error) -} - -// SearchServiceOp implements the VoteService interface -type SearchServiceOp struct { - client *Client -} - -var _ SearchService = &SearchServiceOp{} +// behaviour, e.g. sometimes limit=1 returns nothing when it should. +// +// Reddit API docs: https://www.reddit.com/dev/api/#section_search +type SearchService service // SearchOptions define options used in search queries. type SearchOptions = url.Values @@ -98,8 +90,8 @@ func setRestrict(opts SearchOptions) { } // Posts searches for posts. -// If the list of subreddits is empty, the search is run against r/all. -func (s *SearchServiceOp) Posts(ctx context.Context, query string, subreddits []string, opts ...SearchOptionSetter) (*Posts, *Response, error) { +// If the list of subreddits provided is empty, the search is run against r/all. +func (s *SearchService) Posts(ctx context.Context, query string, subreddits []string, opts ...SearchOptionSetter) (*Posts, *Response, error) { opts = append(opts, setType("link"), setQuery(query)) path := "search" @@ -127,7 +119,7 @@ func (s *SearchServiceOp) Posts(ctx context.Context, query string, subreddits [] // Subreddits searches for subreddits. // The Sort and Timespan options don't affect the results for this search. -func (s *SearchServiceOp) Subreddits(ctx context.Context, query string, opts ...SearchOptionSetter) (*Subreddits, *Response, error) { +func (s *SearchService) Subreddits(ctx context.Context, query string, opts ...SearchOptionSetter) (*Subreddits, *Response, error) { opts = append(opts, setType("sr"), setQuery(query)) form := newSearchOptions(opts...) @@ -150,7 +142,7 @@ func (s *SearchServiceOp) Subreddits(ctx context.Context, query string, opts ... // Users searches for users. // The Sort and Timespan options don't affect the results for this search. -func (s *SearchServiceOp) Users(ctx context.Context, query string, opts ...SearchOptionSetter) (*Users, *Response, error) { +func (s *SearchService) Users(ctx context.Context, query string, opts ...SearchOptionSetter) (*Users, *Response, error) { opts = append(opts, setType("user"), setQuery(query)) form := newSearchOptions(opts...) diff --git a/subreddit.go b/subreddit.go index 1a1f0cd..d4e01cb 100644 --- a/subreddit.go +++ b/subreddit.go @@ -10,39 +10,10 @@ import ( ) // SubredditService handles communication with the subreddit -// related methods of the Reddit API -type SubredditService interface { - GetPosts() *PostFinder - - GetByName(ctx context.Context, subreddit string) (*Subreddit, *Response, error) - - GetPopular(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) - GetNew(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) - GetGold(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) - GetDefault(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) - - GetSubscribed(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) - GetApproved(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) - GetModerated(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) - - GetSticky1(ctx context.Context, subreddit string) (*PostAndComments, *Response, error) - GetSticky2(ctx context.Context, subreddit string) (*PostAndComments, *Response, error) - - Subscribe(ctx context.Context, subreddits ...string) (*Response, error) - SubscribeByID(ctx context.Context, ids ...string) (*Response, error) - Unsubscribe(ctx context.Context, subreddits ...string) (*Response, error) - UnsubscribeByID(ctx context.Context, ids ...string) (*Response, error) - - SearchSubredditNames(ctx context.Context, query string) ([]string, *Response, error) - SearchSubredditInfo(ctx context.Context, query string) ([]SubredditShort, *Response, error) -} - -// SubredditServiceOp implements the SubredditService interface -type SubredditServiceOp struct { - client *Client -} - -var _ SubredditService = &SubredditServiceOp{} +// related methods of the Reddit API. +// +// Reddit API docs: https://www.reddit.com/dev/api/#section_subreddits +type SubredditService service type subredditNamesRoot struct { Names []string `json:"names,omitempty"` @@ -63,14 +34,14 @@ type SubredditShort struct { // By default, it'll look for the hottest posts from r/all. // Note: when looking for hot posts in a subreddit, it will include the // sticked posts (if any) PLUS posts from the limit parameter (25 by default). -func (s *SubredditServiceOp) GetPosts() *PostFinder { +func (s *SubredditService) GetPosts() *PostFinder { f := new(PostFinder) f.client = s.client return f.Sort(SortHot).FromAll() } // GetByName gets a subreddit by name -func (s *SubredditServiceOp) GetByName(ctx context.Context, subreddit string) (*Subreddit, *Response, error) { +func (s *SubredditService) GetByName(ctx context.Context, subreddit string) (*Subreddit, *Response, error) { if subreddit == "" { return nil, nil, errors.New("empty subreddit name provided") } @@ -91,52 +62,52 @@ func (s *SubredditServiceOp) GetByName(ctx context.Context, subreddit string) (* } // GetPopular returns popular subreddits. -func (s *SubredditServiceOp) GetPopular(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { +func (s *SubredditService) GetPopular(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { return s.getSubreddits(ctx, "subreddits/popular", opts) } // GetNew returns new subreddits. -func (s *SubredditServiceOp) GetNew(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { +func (s *SubredditService) GetNew(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { return s.getSubreddits(ctx, "subreddits/new", opts) } // GetGold returns gold subreddits. -func (s *SubredditServiceOp) GetGold(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { +func (s *SubredditService) GetGold(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { return s.getSubreddits(ctx, "subreddits/gold", opts) } // GetDefault returns default subreddits. -func (s *SubredditServiceOp) GetDefault(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { +func (s *SubredditService) GetDefault(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { return s.getSubreddits(ctx, "subreddits/default", opts) } // GetSubscribed returns the list of subreddits the client is subscribed to. -func (s *SubredditServiceOp) GetSubscribed(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { +func (s *SubredditService) GetSubscribed(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { return s.getSubreddits(ctx, "subreddits/mine/subscriber", opts) } // GetApproved returns the list of subreddits the client is an approved user in. -func (s *SubredditServiceOp) GetApproved(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { +func (s *SubredditService) GetApproved(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { return s.getSubreddits(ctx, "subreddits/mine/contributor", opts) } // GetModerated returns the list of subreddits the client is a moderator of. -func (s *SubredditServiceOp) GetModerated(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { +func (s *SubredditService) GetModerated(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) { return s.getSubreddits(ctx, "subreddits/mine/moderator", opts) } // GetSticky1 returns the first stickied post on a subreddit (if it exists). -func (s *SubredditServiceOp) GetSticky1(ctx context.Context, name string) (*PostAndComments, *Response, error) { +func (s *SubredditService) GetSticky1(ctx context.Context, name string) (*PostAndComments, *Response, error) { return s.getSticky(ctx, name, 1) } // GetSticky2 returns the second stickied post on a subreddit (if it exists). -func (s *SubredditServiceOp) GetSticky2(ctx context.Context, name string) (*PostAndComments, *Response, error) { +func (s *SubredditService) GetSticky2(ctx context.Context, name string) (*PostAndComments, *Response, error) { return s.getSticky(ctx, name, 2) } // Subscribe subscribes to subreddits based on their names. -func (s *SubredditServiceOp) Subscribe(ctx context.Context, subreddits ...string) (*Response, error) { +func (s *SubredditService) Subscribe(ctx context.Context, subreddits ...string) (*Response, error) { form := url.Values{} form.Set("action", "sub") form.Set("sr_name", strings.Join(subreddits, ",")) @@ -144,7 +115,7 @@ func (s *SubredditServiceOp) Subscribe(ctx context.Context, subreddits ...string } // SubscribeByID subscribes to subreddits based on their id. -func (s *SubredditServiceOp) SubscribeByID(ctx context.Context, ids ...string) (*Response, error) { +func (s *SubredditService) SubscribeByID(ctx context.Context, ids ...string) (*Response, error) { form := url.Values{} form.Set("action", "sub") form.Set("sr", strings.Join(ids, ",")) @@ -152,7 +123,7 @@ func (s *SubredditServiceOp) SubscribeByID(ctx context.Context, ids ...string) ( } // Unsubscribe unsubscribes from subreddits based on their names. -func (s *SubredditServiceOp) Unsubscribe(ctx context.Context, subreddits ...string) (*Response, error) { +func (s *SubredditService) Unsubscribe(ctx context.Context, subreddits ...string) (*Response, error) { form := url.Values{} form.Set("action", "unsub") form.Set("sr_name", strings.Join(subreddits, ",")) @@ -160,7 +131,7 @@ func (s *SubredditServiceOp) Unsubscribe(ctx context.Context, subreddits ...stri } // UnsubscribeByID unsubscribes from subreddits based on their id. -func (s *SubredditServiceOp) UnsubscribeByID(ctx context.Context, ids ...string) (*Response, error) { +func (s *SubredditService) UnsubscribeByID(ctx context.Context, ids ...string) (*Response, error) { form := url.Values{} form.Set("action", "unsub") form.Set("sr", strings.Join(ids, ",")) @@ -168,7 +139,7 @@ func (s *SubredditServiceOp) UnsubscribeByID(ctx context.Context, ids ...string) } // SearchSubredditNames searches for subreddits with names beginning with the query provided. -func (s *SubredditServiceOp) SearchSubredditNames(ctx context.Context, query string) ([]string, *Response, error) { +func (s *SubredditService) SearchSubredditNames(ctx context.Context, query string) ([]string, *Response, error) { path := fmt.Sprintf("api/search_reddit_names?query=%s", query) req, err := s.client.NewRequest(http.MethodGet, path, nil) @@ -187,7 +158,7 @@ func (s *SubredditServiceOp) SearchSubredditNames(ctx context.Context, query str // SearchSubredditInfo searches for subreddits with names beginning with the query provided. // They hold a bit more info that just the name, but still not much. -func (s *SubredditServiceOp) SearchSubredditInfo(ctx context.Context, query string) ([]SubredditShort, *Response, error) { +func (s *SubredditService) SearchSubredditInfo(ctx context.Context, query string) ([]SubredditShort, *Response, error) { path := fmt.Sprintf("api/search_subreddits?query=%s", query) req, err := s.client.NewRequest(http.MethodPost, path, nil) @@ -204,7 +175,7 @@ func (s *SubredditServiceOp) SearchSubredditInfo(ctx context.Context, query stri return root.Subreddits, resp, nil } -func (s *SubredditServiceOp) handleSubscription(ctx context.Context, form url.Values) (*Response, error) { +func (s *SubredditService) handleSubscription(ctx context.Context, form url.Values) (*Response, error) { path := "api/subscribe" req, err := s.client.NewPostForm(path, form) if err != nil { @@ -219,7 +190,7 @@ func (s *SubredditServiceOp) handleSubscription(ctx context.Context, form url.Va return resp, nil } -func (s *SubredditServiceOp) getSubreddits(ctx context.Context, path string, opts *ListOptions) (*Subreddits, *Response, error) { +func (s *SubredditService) getSubreddits(ctx context.Context, path string, opts *ListOptions) (*Subreddits, *Response, error) { path, err := addOptions(path, opts) if err != nil { return nil, nil, err @@ -249,7 +220,7 @@ func (s *SubredditServiceOp) getSubreddits(ctx context.Context, path string, opt // getSticky returns one of the 2 stickied posts of the subreddit (if they exist). // Num should be equal to 1 or 2, depending on which one you want. -func (s *SubredditServiceOp) getSticky(ctx context.Context, subreddit string, num int) (*PostAndComments, *Response, error) { +func (s *SubredditService) getSticky(ctx context.Context, subreddit string, num int) (*PostAndComments, *Response, error) { type query struct { Num int `url:"num"` } diff --git a/subreddit_test.go b/subreddit_test.go index 646ba27..8803314 100644 --- a/subreddit_test.go +++ b/subreddit_test.go @@ -110,7 +110,7 @@ var expectedSticky = &PostAndComments{ }, } -func TestSubredditServiceOp_GetByName(t *testing.T) { +func TestSubredditService_GetByName(t *testing.T) { setup() defer teardown() @@ -126,7 +126,7 @@ func TestSubredditServiceOp_GetByName(t *testing.T) { assert.Equal(t, expectedSubreddit, subreddit) } -func TestSubredditServiceOp_GetPopular(t *testing.T) { +func TestSubredditService_GetPopular(t *testing.T) { setup() defer teardown() @@ -142,7 +142,7 @@ func TestSubredditServiceOp_GetPopular(t *testing.T) { assert.Equal(t, expectedSubreddits, subreddits) } -func TestSubredditServiceOp_GetNew(t *testing.T) { +func TestSubredditService_GetNew(t *testing.T) { setup() defer teardown() @@ -158,7 +158,7 @@ func TestSubredditServiceOp_GetNew(t *testing.T) { assert.Equal(t, expectedSubreddits, subreddits) } -func TestSubredditServiceOp_GetGold(t *testing.T) { +func TestSubredditService_GetGold(t *testing.T) { setup() defer teardown() @@ -174,7 +174,7 @@ func TestSubredditServiceOp_GetGold(t *testing.T) { assert.Equal(t, expectedSubreddits, subreddits) } -func TestSubredditServiceOp_GetDefault(t *testing.T) { +func TestSubredditService_GetDefault(t *testing.T) { setup() defer teardown() @@ -190,7 +190,7 @@ func TestSubredditServiceOp_GetDefault(t *testing.T) { assert.Equal(t, expectedSubreddits, subreddits) } -func TestSubredditServiceOp_GetSubscribed(t *testing.T) { +func TestSubredditService_GetSubscribed(t *testing.T) { setup() defer teardown() @@ -206,7 +206,7 @@ func TestSubredditServiceOp_GetSubscribed(t *testing.T) { assert.Equal(t, expectedSubreddits, subreddits) } -func TestSubredditServiceOp_GetApproved(t *testing.T) { +func TestSubredditService_GetApproved(t *testing.T) { setup() defer teardown() @@ -222,7 +222,7 @@ func TestSubredditServiceOp_GetApproved(t *testing.T) { assert.Equal(t, expectedSubreddits, subreddits) } -func TestSubredditServiceOp_GetModerated(t *testing.T) { +func TestSubredditService_GetModerated(t *testing.T) { setup() defer teardown() @@ -239,7 +239,7 @@ func TestSubredditServiceOp_GetModerated(t *testing.T) { } // todo: WIP -func TestSubredditServiceOp_GetSticky1(t *testing.T) { +func TestSubredditService_GetSticky1(t *testing.T) { setup() defer teardown() diff --git a/user.go b/user.go index 123f167..bbd6dae 100644 --- a/user.go +++ b/user.go @@ -9,47 +9,9 @@ import ( // UserService handles communication with the user // related methods of the Reddit API. -type UserService interface { - Get(ctx context.Context, username string) (*User, *Response, error) - GetMultipleByID(ctx context.Context, ids ...string) (map[string]*UserShort, *Response, error) - UsernameAvailable(ctx context.Context, username string) (bool, *Response, error) - - Overview(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Comments, *Response, error) - OverviewOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Posts, *Comments, *Response, error) - - Posts(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) - PostsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Posts, *Response, error) - - Comments(ctx context.Context, opts ...SearchOptionSetter) (*Comments, *Response, error) - CommentsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Comments, *Response, error) - - Saved(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Comments, *Response, error) - // todo: votes can be public (they're private by default) so make UpvotedOf and DownvotedOf as well - Upvoted(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) - Downvoted(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) - Hidden(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) - Gilded(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) - - GetFriendship(ctx context.Context, username string) (*Friendship, *Response, error) - Friend(ctx context.Context, username string) (*Friendship, *Response, error) - Unfriend(ctx context.Context, username string) (*Response, error) - - Block(ctx context.Context, username string) (*Blocked, *Response, error) - // todo: decide if you wanna keep these - // BlockByID(ctx context.Context, id string) (*Blocked, *Response, error) - Unblock(ctx context.Context, username string) (*Response, error) - // UnblockByID(ctx context.Context, id string) (*Response, error) - - Trophies(ctx context.Context) ([]Trophy, *Response, error) - TrophiesOf(ctx context.Context, username string) ([]Trophy, *Response, error) -} - -// UserServiceOp implements the UserService interface. -type UserServiceOp struct { - client *Client -} - -var _ UserService = &UserServiceOp{} +// +// Reddit API docs: https://www.reddit.com/dev/api/#section_users +type UserService service // User represents a Reddit user. type User struct { @@ -115,7 +77,7 @@ type Trophy struct { } // Get returns information about the user. -func (s *UserServiceOp) Get(ctx context.Context, username string) (*User, *Response, error) { +func (s *UserService) Get(ctx context.Context, username string) (*User, *Response, error) { path := fmt.Sprintf("user/%s/about", username) req, err := s.client.NewRequest(http.MethodGet, path, nil) if err != nil { @@ -133,7 +95,7 @@ func (s *UserServiceOp) Get(ctx context.Context, username string) (*User, *Respo // GetMultipleByID returns multiple users from their full IDs. // The response body is a map where the keys are the IDs (if they exist), and the value is the user -func (s *UserServiceOp) GetMultipleByID(ctx context.Context, ids ...string) (map[string]*UserShort, *Response, error) { +func (s *UserService) GetMultipleByID(ctx context.Context, ids ...string) (map[string]*UserShort, *Response, error) { type query struct { IDs []string `url:"ids,omitempty,comma"` } @@ -159,7 +121,7 @@ func (s *UserServiceOp) GetMultipleByID(ctx context.Context, ids ...string) (map } // UsernameAvailable checks whether a username is available for registration. -func (s *UserServiceOp) UsernameAvailable(ctx context.Context, username string) (bool, *Response, error) { +func (s *UserService) UsernameAvailable(ctx context.Context, username string) (bool, *Response, error) { type query struct { User string `url:"user,omitempty"` } @@ -185,12 +147,12 @@ func (s *UserServiceOp) UsernameAvailable(ctx context.Context, username string) } // Overview returns a list of the client's posts and comments. -func (s *UserServiceOp) Overview(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Comments, *Response, error) { +func (s *UserService) Overview(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Comments, *Response, error) { return s.OverviewOf(ctx, s.client.Username, opts...) } // OverviewOf returns a list of the user's posts and comments. -func (s *UserServiceOp) OverviewOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Posts, *Comments, *Response, error) { +func (s *UserService) OverviewOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Posts, *Comments, *Response, error) { form := newSearchOptions(opts...) path := fmt.Sprintf("user/%s/overview", username) @@ -211,12 +173,12 @@ func (s *UserServiceOp) OverviewOf(ctx context.Context, username string, opts .. } // Posts returns a list of the client's posts. -func (s *UserServiceOp) Posts(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { +func (s *UserService) Posts(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { return s.PostsOf(ctx, s.client.Username, opts...) } // PostsOf returns a list of the user's posts. -func (s *UserServiceOp) PostsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Posts, *Response, error) { +func (s *UserService) PostsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Posts, *Response, error) { form := newSearchOptions(opts...) path := fmt.Sprintf("user/%s/submitted", username) @@ -237,12 +199,12 @@ func (s *UserServiceOp) PostsOf(ctx context.Context, username string, opts ...Se } // Comments returns a list of the client's comments. -func (s *UserServiceOp) Comments(ctx context.Context, opts ...SearchOptionSetter) (*Comments, *Response, error) { +func (s *UserService) Comments(ctx context.Context, opts ...SearchOptionSetter) (*Comments, *Response, error) { return s.CommentsOf(ctx, s.client.Username, opts...) } // CommentsOf returns a list of the user's comments. -func (s *UserServiceOp) CommentsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Comments, *Response, error) { +func (s *UserService) CommentsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Comments, *Response, error) { form := newSearchOptions(opts...) path := fmt.Sprintf("user/%s/comments", username) @@ -263,7 +225,7 @@ func (s *UserServiceOp) CommentsOf(ctx context.Context, username string, opts .. } // Saved returns a list of the user's saved posts and comments. -func (s *UserServiceOp) Saved(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Comments, *Response, error) { +func (s *UserService) Saved(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Comments, *Response, error) { form := newSearchOptions(opts...) path := fmt.Sprintf("user/%s/saved", s.client.Username) @@ -284,7 +246,7 @@ func (s *UserServiceOp) Saved(ctx context.Context, opts ...SearchOptionSetter) ( } // Upvoted returns a list of the user's upvoted posts. -func (s *UserServiceOp) Upvoted(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { +func (s *UserService) Upvoted(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { form := newSearchOptions(opts...) path := fmt.Sprintf("user/%s/upvoted", s.client.Username) @@ -305,7 +267,7 @@ func (s *UserServiceOp) Upvoted(ctx context.Context, opts ...SearchOptionSetter) } // Downvoted returns a list of the user's downvoted posts. -func (s *UserServiceOp) Downvoted(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { +func (s *UserService) Downvoted(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { form := newSearchOptions(opts...) path := fmt.Sprintf("user/%s/downvoted", s.client.Username) @@ -326,7 +288,7 @@ func (s *UserServiceOp) Downvoted(ctx context.Context, opts ...SearchOptionSette } // Hidden returns a list of the user's hidden posts. -func (s *UserServiceOp) Hidden(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { +func (s *UserService) Hidden(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { form := newSearchOptions(opts...) path := fmt.Sprintf("user/%s/hidden", s.client.Username) @@ -347,7 +309,7 @@ func (s *UserServiceOp) Hidden(ctx context.Context, opts ...SearchOptionSetter) } // Gilded returns a list of the user's gilded posts. -func (s *UserServiceOp) Gilded(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { +func (s *UserService) Gilded(ctx context.Context, opts ...SearchOptionSetter) (*Posts, *Response, error) { form := newSearchOptions(opts...) path := fmt.Sprintf("user/%s/gilded", s.client.Username) @@ -369,7 +331,7 @@ func (s *UserServiceOp) Gilded(ctx context.Context, opts ...SearchOptionSetter) // GetFriendship returns friendship details with the specified user. // If the user is not your friend, it will return an error. -func (s *UserServiceOp) GetFriendship(ctx context.Context, username string) (*Friendship, *Response, error) { +func (s *UserService) GetFriendship(ctx context.Context, username string) (*Friendship, *Response, error) { path := fmt.Sprintf("api/v1/me/friends/%s", username) req, err := s.client.NewRequest(http.MethodGet, path, nil) @@ -387,7 +349,7 @@ func (s *UserServiceOp) GetFriendship(ctx context.Context, username string) (*Fr } // Friend friends a user. -func (s *UserServiceOp) Friend(ctx context.Context, username string) (*Friendship, *Response, error) { +func (s *UserService) Friend(ctx context.Context, username string) (*Friendship, *Response, error) { type request struct { Username string `json:"name"` } @@ -410,7 +372,7 @@ func (s *UserServiceOp) Friend(ctx context.Context, username string) (*Friendshi } // Unfriend unfriends a user. -func (s *UserServiceOp) Unfriend(ctx context.Context, username string) (*Response, error) { +func (s *UserService) Unfriend(ctx context.Context, username string) (*Response, error) { path := fmt.Sprintf("api/v1/me/friends/%s", username) req, err := s.client.NewRequest(http.MethodDelete, path, nil) if err != nil { @@ -420,7 +382,7 @@ func (s *UserServiceOp) Unfriend(ctx context.Context, username string) (*Respons } // Block blocks a user. -func (s *UserServiceOp) Block(ctx context.Context, username string) (*Blocked, *Response, error) { +func (s *UserService) Block(ctx context.Context, username string) (*Blocked, *Response, error) { path := "api/block_user" form := url.Values{} @@ -441,7 +403,7 @@ func (s *UserServiceOp) Block(ctx context.Context, username string) (*Blocked, * } // // BlockByID blocks a user via their full id. -// func (s *UserServiceOp) BlockByID(ctx context.Context, id string) (*Blocked, *Response, error) { +// func (s *UserService) BlockByID(ctx context.Context, id string) (*Blocked, *Response, error) { // path := "api/block_user" // form := url.Values{} @@ -462,7 +424,7 @@ func (s *UserServiceOp) Block(ctx context.Context, username string) (*Blocked, * // } // Unblock unblocks a user. -func (s *UserServiceOp) Unblock(ctx context.Context, username string) (*Response, error) { +func (s *UserService) Unblock(ctx context.Context, username string) (*Response, error) { selfID, err := s.client.GetRedditID(ctx) if err != nil { return nil, err @@ -484,7 +446,7 @@ func (s *UserServiceOp) Unblock(ctx context.Context, username string) (*Response } // // UnblockByID unblocks a user via their full id. -// func (s *UserServiceOp) UnblockByID(ctx context.Context, id string) (*Response, error) { +// func (s *UserService) UnblockByID(ctx context.Context, id string) (*Response, error) { // selfID, err := s.client.GetRedditID(ctx) // if err != nil { // return nil, err @@ -506,12 +468,12 @@ func (s *UserServiceOp) Unblock(ctx context.Context, username string) (*Response // } // Trophies returns a list of your trophies. -func (s *UserServiceOp) Trophies(ctx context.Context) ([]Trophy, *Response, error) { +func (s *UserService) Trophies(ctx context.Context) ([]Trophy, *Response, error) { return s.TrophiesOf(ctx, s.client.Username) } // TrophiesOf returns a list of the specified user's trophies. -func (s *UserServiceOp) TrophiesOf(ctx context.Context, username string) ([]Trophy, *Response, error) { +func (s *UserService) TrophiesOf(ctx context.Context, username string) ([]Trophy, *Response, error) { path := fmt.Sprintf("api/v1/user/%s/trophies", username) req, err := s.client.NewRequest(http.MethodGet, path, nil) diff --git a/user_test.go b/user_test.go index e2f758b..ccc34a4 100644 --- a/user_test.go +++ b/user_test.go @@ -205,8 +205,6 @@ func TestUserService_Overview(t *testing.T) { posts, comments, _, err := client.User.Overview(ctx) assert.NoError(t, err) - assert.NotNil(t, posts) - assert.NotNil(t, comments) assert.Len(t, posts.Posts, 1) assert.Equal(t, expectedPost, posts.Posts[0]) @@ -232,8 +230,6 @@ func TestUserService_OverviewOf(t *testing.T) { posts, comments, _, err := client.User.OverviewOf(ctx, "user2") assert.NoError(t, err) - assert.NotNil(t, posts) - assert.NotNil(t, comments) assert.Len(t, posts.Posts, 1) assert.Equal(t, expectedPost, posts.Posts[0]) @@ -284,7 +280,6 @@ func TestUserService_Posts(t *testing.T) { posts, _, err := client.User.Posts(ctx) assert.NoError(t, err) - assert.NotNil(t, posts) assert.Len(t, posts.Posts, 1) assert.Equal(t, expectedPost, posts.Posts[0]) @@ -305,7 +300,6 @@ func TestUserService_PostsOf(t *testing.T) { posts, _, err := client.User.PostsOf(ctx, "user2") assert.NoError(t, err) - assert.NotNil(t, posts) assert.Len(t, posts.Posts, 1) assert.Equal(t, expectedPost, posts.Posts[0]) @@ -350,7 +344,6 @@ func TestUserService_Comments(t *testing.T) { comments, _, err := client.User.Comments(ctx) assert.NoError(t, err) - assert.NotNil(t, comments) assert.Len(t, comments.Comments, 1) assert.Equal(t, expectedComment, comments.Comments[0]) @@ -371,7 +364,6 @@ func TestUserService_CommentsOf(t *testing.T) { comments, _, err := client.User.CommentsOf(ctx, "user2") assert.NoError(t, err) - assert.NotNil(t, comments) assert.Len(t, comments.Comments, 1) assert.Equal(t, expectedComment, comments.Comments[0]) @@ -417,8 +409,6 @@ func TestUserService_Saved(t *testing.T) { posts, comments, _, err := client.User.Saved(ctx) assert.NoError(t, err) - assert.NotNil(t, posts) - assert.NotNil(t, comments) assert.Len(t, posts.Posts, 1) assert.Equal(t, expectedPost, posts.Posts[0]) @@ -469,7 +459,6 @@ func TestUserService_Upvoted(t *testing.T) { posts, _, err := client.User.Upvoted(ctx) assert.NoError(t, err) - assert.NotNil(t, posts) assert.Len(t, posts.Posts, 1) assert.Equal(t, expectedPost, posts.Posts[0]) @@ -515,7 +504,6 @@ func TestUserService_Downvoted(t *testing.T) { posts, _, err := client.User.Downvoted(ctx) assert.NoError(t, err) - assert.NotNil(t, posts) assert.Len(t, posts.Posts, 1) assert.Equal(t, expectedPost, posts.Posts[0]) @@ -562,7 +550,6 @@ func TestUserService_Hidden(t *testing.T) { posts, _, err := client.User.Hidden(ctx) assert.NoError(t, err) - assert.NotNil(t, posts) assert.Len(t, posts.Posts, 1) assert.Equal(t, expectedPost, posts.Posts[0]) @@ -584,7 +571,6 @@ func TestUserService_Gilded(t *testing.T) { posts, _, err := client.User.Gilded(ctx) assert.NoError(t, err) - assert.NotNil(t, posts) assert.Len(t, posts.Posts, 1) assert.Equal(t, expectedPost, posts.Posts[0]) diff --git a/vote.go b/vote.go index 9beb9c9..0b92121 100644 --- a/vote.go +++ b/vote.go @@ -8,18 +8,9 @@ import ( // VoteService handles communication with the upvote/downvote // related methods of the Reddit API. -type VoteService interface { - Up(ctx context.Context, id string) (*Response, error) - Down(ctx context.Context, id string) (*Response, error) - Remove(ctx context.Context, id string) (*Response, error) -} - -// VoteServiceOp implements the Vote interface. -type VoteServiceOp struct { - client *Client -} - -var _ VoteService = &VoteServiceOp{} +// +// Reddit API docs: https://www.reddit.com/dev/api/#POST_api_vote +type VoteService service type vote int @@ -30,7 +21,7 @@ const ( upvote ) -func (s *VoteServiceOp) vote(ctx context.Context, id string, vote vote) (*Response, error) { +func (s *VoteService) vote(ctx context.Context, id string, vote vote) (*Response, error) { path := "api/vote" form := url.Values{} @@ -47,16 +38,16 @@ func (s *VoteServiceOp) vote(ctx context.Context, id string, vote vote) (*Respon } // Up upvotes a post or a comment. -func (s *VoteServiceOp) Up(ctx context.Context, id string) (*Response, error) { +func (s *VoteService) Up(ctx context.Context, id string) (*Response, error) { return s.vote(ctx, id, upvote) } // Down downvotes a post or a comment. -func (s *VoteServiceOp) Down(ctx context.Context, id string) (*Response, error) { +func (s *VoteService) Down(ctx context.Context, id string) (*Response, error) { return s.vote(ctx, id, downvote) } // Remove removes the user's vote on a post or a comment. -func (s *VoteServiceOp) Remove(ctx context.Context, id string) (*Response, error) { +func (s *VoteService) Remove(ctx context.Context, id string) (*Response, error) { return s.vote(ctx, id, novote) } diff --git a/vote_test.go b/vote_test.go index 2cf05c5..67d5b82 100644 --- a/vote_test.go +++ b/vote_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestVoteServiceOp_Up(t *testing.T) { +func TestVoteService_Up(t *testing.T) { setup() defer teardown() @@ -24,8 +24,6 @@ func TestVoteServiceOp_Up(t *testing.T) { err := r.ParseForm() assert.NoError(t, err) assert.Equal(t, form, r.PostForm) - - fmt.Fprint(w, `{}`) }) res, err := client.Vote.Up(ctx, "t3_test") @@ -33,7 +31,7 @@ func TestVoteServiceOp_Up(t *testing.T) { assert.Equal(t, http.StatusOK, res.StatusCode) } -func TestVoteServiceOp_Down(t *testing.T) { +func TestVoteService_Down(t *testing.T) { setup() defer teardown() @@ -48,8 +46,6 @@ func TestVoteServiceOp_Down(t *testing.T) { err := r.ParseForm() assert.NoError(t, err) assert.Equal(t, form, r.PostForm) - - fmt.Fprint(w, `{}`) }) res, err := client.Vote.Down(ctx, "t3_test") @@ -57,7 +53,7 @@ func TestVoteServiceOp_Down(t *testing.T) { assert.Equal(t, http.StatusOK, res.StatusCode) } -func TestVoteServiceOp_Remove(t *testing.T) { +func TestVoteService_Remove(t *testing.T) { setup() defer teardown() @@ -72,8 +68,6 @@ func TestVoteServiceOp_Remove(t *testing.T) { err := r.ParseForm() assert.NoError(t, err) assert.Equal(t, form, r.PostForm) - - fmt.Fprint(w, `{}`) }) res, err := client.Vote.Remove(ctx, "t3_test")