Skip to content

Commit

Permalink
feat: Refactors API client into hand rolled sdk in api/ directory (#111)
Browse files Browse the repository at this point in the history
Signed-off-by: John McBride <[email protected]>
  • Loading branch information
jpmcb authored Sep 4, 2024
1 parent ad9e3a0 commit e16e889
Show file tree
Hide file tree
Showing 22 changed files with 1,116 additions and 180 deletions.
45 changes: 45 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package api

import (
"net/http"
"time"

"github.com/open-sauced/pizza-cli/api/services/contributors"
"github.com/open-sauced/pizza-cli/api/services/histogram"
"github.com/open-sauced/pizza-cli/api/services/repository"
)

// Client is the API client for OpenSauced API
type Client struct {
// API services
RepositoryService *repository.Service
ContributorService *contributors.Service
HistogramService *histogram.Service

// The configured http client for making API requests
httpClient *http.Client

// The API endpoint to use when making requests
// Example: https://api.opensauced.pizza
endpoint string
}

// NewClient returns a new API Client based on provided inputs
func NewClient(endpoint string) *Client {
httpClient := &http.Client{
// TODO (jpmcb): in the future, we can allow users to configure the API
// timeout via some global flag
Timeout: time.Second * 10,
}

client := Client{
httpClient: httpClient,
endpoint: endpoint,
}

client.ContributorService = contributors.NewContributorsService(client.httpClient, client.endpoint)
client.RepositoryService = repository.NewRepositoryService(client.httpClient, client.endpoint)
client.HistogramService = histogram.NewHistogramService(client.httpClient, client.endpoint)

return &client
}
23 changes: 23 additions & 0 deletions api/mock/mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package mock

import "net/http"

// RoundTripper is a custom, mock http.RoundTripper used for testing and mocking
// purposes ONLY.
type RoundTripper struct {
RoundTripFunc func(req *http.Request) (*http.Response, error)
}

// NewMockRoundTripper returns a new RoundTripper which will execut the given
// roundTripFunc provided by the caller
func NewMockRoundTripper(roundTripFunc func(req *http.Request) (*http.Response, error)) *RoundTripper {
return &RoundTripper{
RoundTripFunc: roundTripFunc,
}
}

// RoundTrip fufills the http.Client interface and executes the provided RoundTripFunc
// given by the caller in the NewMockRoundTripper
func (m *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return m.RoundTripFunc(req)
}
189 changes: 189 additions & 0 deletions api/services/contributors/contributors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package contributors

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)

// Service is the Contributors service used for accessing the "v2/contributors"
// endpoint and API services
type Service struct {
httpClient *http.Client
endpoint string
}

// NewContributorsService returns a new contributors Service
func NewContributorsService(httpClient *http.Client, endpoint string) *Service {
return &Service{
httpClient: httpClient,
endpoint: endpoint,
}
}

// NewPullRequestContributors calls the "v2/contributors/insights/new" API endpoint
func (s *Service) NewPullRequestContributors(repos []string, rangeVal int) (*ContribResponse, *http.Response, error) {
baseURL := fmt.Sprintf("%s/v2/contributors/insights/new", s.endpoint)

// Create URL with query parameters
u, err := url.Parse(baseURL)
if err != nil {
return nil, nil, fmt.Errorf("error parsing URL: %v", err)
}

q := u.Query()
q.Set("range", fmt.Sprintf("%d", rangeVal))
q.Set("repos", strings.Join(repos, ","))
u.RawQuery = q.Encode()

resp, err := s.httpClient.Get(u.String())
if err != nil {
return nil, resp, fmt.Errorf("error making request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, resp, fmt.Errorf("API request failed with status code: %d", resp.StatusCode)
}

var newContributorsResponse ContribResponse
if err := json.NewDecoder(resp.Body).Decode(&newContributorsResponse); err != nil {
return nil, resp, fmt.Errorf("error decoding response: %v", err)
}

return &newContributorsResponse, resp, nil
}

// RecentPullRequestContributors calls the "v2/contributors/insights/recent" API endpoint
func (s *Service) RecentPullRequestContributors(repos []string, rangeVal int) (*ContribResponse, *http.Response, error) {
baseURL := fmt.Sprintf("%s/v2/contributors/insights/recent", s.endpoint)

// Create URL with query parameters
u, err := url.Parse(baseURL)
if err != nil {
return nil, nil, fmt.Errorf("error parsing URL: %v", err)
}

q := u.Query()
q.Set("range", fmt.Sprintf("%d", rangeVal))
q.Set("repos", strings.Join(repos, ","))
u.RawQuery = q.Encode()

resp, err := s.httpClient.Get(u.String())
if err != nil {
return nil, resp, fmt.Errorf("error making request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, resp, fmt.Errorf("API request failed with status code: %d", resp.StatusCode)
}

var recentContributorsResponse ContribResponse
if err := json.NewDecoder(resp.Body).Decode(&recentContributorsResponse); err != nil {
return nil, resp, fmt.Errorf("error decoding response: %v", err)
}

return &recentContributorsResponse, resp, nil
}

// AlumniPullRequestContributors calls the "v2/contributors/insights/alumni" API endpoint
func (s *Service) AlumniPullRequestContributors(repos []string, rangeVal int) (*ContribResponse, *http.Response, error) {
baseURL := fmt.Sprintf("%s/v2/contributors/insights/alumni", s.endpoint)

// Create URL with query parameters
u, err := url.Parse(baseURL)
if err != nil {
return nil, nil, fmt.Errorf("error parsing URL: %v", err)
}

q := u.Query()
q.Set("range", fmt.Sprintf("%d", rangeVal))
q.Set("repos", strings.Join(repos, ","))
u.RawQuery = q.Encode()

resp, err := s.httpClient.Get(u.String())
if err != nil {
return nil, resp, fmt.Errorf("error making request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, resp, fmt.Errorf("API request failed with status code: %d", resp.StatusCode)
}

var alumniContributorsResponse ContribResponse
if err := json.NewDecoder(resp.Body).Decode(&alumniContributorsResponse); err != nil {
return nil, resp, fmt.Errorf("error decoding response: %v", err)
}

return &alumniContributorsResponse, resp, nil
}

// RepeatPullRequestContributors calls the "v2/contributors/insights/repeat" API endpoint
func (s *Service) RepeatPullRequestContributors(repos []string, rangeVal int) (*ContribResponse, *http.Response, error) {
baseURL := fmt.Sprintf("%s/v2/contributors/insights/repeat", s.endpoint)

// Create URL with query parameters
u, err := url.Parse(baseURL)
if err != nil {
return nil, nil, fmt.Errorf("error parsing URL: %v", err)
}

q := u.Query()
q.Set("range", fmt.Sprintf("%d", rangeVal))
q.Set("repos", strings.Join(repos, ","))
u.RawQuery = q.Encode()

resp, err := s.httpClient.Get(u.String())
if err != nil {
return nil, resp, fmt.Errorf("error making request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, resp, fmt.Errorf("API request failed with status code: %d", resp.StatusCode)
}

var repeatContributorsResponse ContribResponse
if err := json.NewDecoder(resp.Body).Decode(&repeatContributorsResponse); err != nil {
return nil, resp, fmt.Errorf("error decoding response: %v", err)
}

return &repeatContributorsResponse, resp, nil
}

// SearchPullRequestContributors calls the "v2/contributors/search"
func (s *Service) SearchPullRequestContributors(repos []string, rangeVal int) (*ContribResponse, *http.Response, error) {
baseURL := fmt.Sprintf("%s/v2/contributors/search", s.endpoint)

// Create URL with query parameters
u, err := url.Parse(baseURL)
if err != nil {
return nil, nil, fmt.Errorf("error parsing URL: %v", err)
}

q := u.Query()
q.Set("range", fmt.Sprintf("%d", rangeVal))
q.Set("repos", strings.Join(repos, ","))
u.RawQuery = q.Encode()

resp, err := s.httpClient.Get(u.String())
if err != nil {
return nil, resp, fmt.Errorf("error making request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, resp, fmt.Errorf("API request failed with status code: %d", resp.StatusCode)
}

var searchContributorsResponse ContribResponse
if err := json.NewDecoder(resp.Body).Decode(&searchContributorsResponse); err != nil {
return nil, resp, fmt.Errorf("error decoding response: %v", err)
}

return &searchContributorsResponse, resp, nil
}
Loading

0 comments on commit e16e889

Please sign in to comment.