snoobert/geddit.go
Vartan Benohanian 3c7aee142d Get subreddit by name
Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
2020-04-23 22:57:47 -04:00

201 lines
4.9 KiB
Go

package geddit
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
)
const (
libraryVersion = "0.0.1"
defaultBaseURL = "https://reddit.com"
defaultBaseURLOauth = "https://oauth.reddit.com"
userAgent = "geddit/" + libraryVersion
mediaType = "application/json"
headerContentType = "Content-Type"
headerAccept = "Accept"
headerUserAgent = "User-Agent"
)
// RequestCompletionCallback defines the type of the request callback function
type RequestCompletionCallback func(*http.Request, *http.Response)
// Client manages communication with the Reddit API
type Client struct {
// HTTP client used to communicate with the Reddit API
client *http.Client
// Base URL for HTTP requests
BaseURL *url.URL
UserAgent string
Subreddit SubredditService
onRequestCompleted RequestCompletionCallback
}
// OnRequestCompleted sets the client's request completion callback
func (c *Client) OnRequestCompleted(rc RequestCompletionCallback) {
c.onRequestCompleted = rc
}
func newClient(httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = &http.Client{}
}
baseURL, _ := url.Parse(defaultBaseURL)
c := &Client{client: httpClient, BaseURL: baseURL, UserAgent: userAgent}
c.Subreddit = &SubredditServiceOp{client: c}
return c
}
// New returns a client that can make requests to the Reddit API
func New(httpClient *http.Client, opts ...Opt) (c *Client, err error) {
c = newClient(httpClient)
for _, opt := range opts {
if err = opt(c); err != nil {
return
}
}
return
}
// NewRequest creates an API request
// The path is the relative URL which will be resolves to the BaseURL of the Client
// It should always be specified without a preceding slash
func (c *Client) NewRequest(method, path string, body interface{}) (*http.Request, error) {
u, err := c.BaseURL.Parse(path)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
if body != nil {
err = json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}
reqBody := bytes.NewReader(buf.Bytes())
req, err := http.NewRequest(method, u.String(), reqBody)
if err != nil {
return nil, err
}
req.Header.Add(headerContentType, mediaType)
req.Header.Add(headerAccept, mediaType)
req.Header.Add(headerUserAgent, c.UserAgent)
return req, nil
}
// Response is a PlayNetwork response. This wraps the standard http.Response returned from PlayNetwork.
type Response struct {
*http.Response
}
// newResponse creates a new Response for the provided http.Response
func newResponse(r *http.Response) *Response {
response := Response{Response: r}
return &response
}
// Do sends an API request and returns the API response. The API response is JSON decoded and stored in the value
// pointed to by v, or returned as an error if an API error has occurred. If v implements the io.Writer interface,
// the raw response will be written to v, without attempting to decode it.
func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) {
resp, err := DoRequestWithClient(ctx, c.client, req)
if err != nil {
return nil, err
}
if c.onRequestCompleted != nil {
c.onRequestCompleted(req, resp)
}
response := newResponse(resp)
defer func() {
if rerr := response.Body.Close(); err == nil {
err = rerr
}
}()
err = CheckResponse(resp)
if err != nil {
return response, err
}
if v != nil {
if w, ok := v.(io.Writer); ok {
_, err = io.Copy(w, response.Body)
if err != nil {
return nil, err
}
} else {
err = json.NewDecoder(response.Body).Decode(v)
if err != nil {
return nil, err
}
}
}
return response, err
}
// DoRequest submits an HTTP request.
func DoRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
return DoRequestWithClient(ctx, http.DefaultClient, req)
}
// DoRequestWithClient submits an HTTP request using the specified client.
func DoRequestWithClient(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
req = req.WithContext(ctx)
return client.Do(req)
}
// An ErrorResponse reports the error caused by an API request
type ErrorResponse struct {
// HTTP response that caused this error
Response *http.Response
// Error message
Message string `json:"message"`
}
func (r *ErrorResponse) Error() string {
return fmt.Sprintf(
"%v %v: %d %v",
r.Response.Request.Method, r.Response.Request.URL, r.Response.StatusCode, r.Message,
)
}
// CheckResponse checks the API response for errors, and returns them if present.
// A response is considered an error if it has a status code outside the 200 range.
func CheckResponse(r *http.Response) error {
if c := r.StatusCode; c >= 200 && c <= 299 {
return nil
}
errorResponse := &ErrorResponse{Response: r}
data, err := ioutil.ReadAll(r.Body)
if err == nil && len(data) > 0 {
err := json.Unmarshal(data, errorResponse)
if err != nil {
errorResponse.Message = string(data)
}
}
return errorResponse
}