api.go
author Mikael Berthe <mikael@lilotux.net>
Sat, 15 Apr 2017 21:08:34 +0200
changeset 125 2bbb72b9ebf6
child 128 a5a00fad7a32
permissions -rw-r--r--
Rework the API wrappers to handle arrays of parameters This make some API calls work better (reports with several statuses, statuses with several attachments, relationships for multiple accounts...).

package gondole

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

	"github.com/sendgrid/rest"
)

// restAPI actually does the HTTP query
// It is a copy of rest.API with better handling of parameters with multiple values
func restAPI(request rest.Request) (*rest.Response, error) {
	c := &rest.Client{HTTPClient: http.DefaultClient}

	// Build the HTTP request object.
	if len(request.QueryParams) != 0 {
		// Add parameters to the URL
		request.BaseURL += "?"
		urlp := url.Values{}
		for key, value := range request.QueryParams {
			// It seems Mastodon doesn't like parameters with index
			// numbers, but it needs the brackets.
			// Let's check if the key matches '^.+\[.*\]$'
			klen := len(key)
			if klen == 0 {
				continue
			}
			i := strings.Index(key, "[")
			if key[klen-1] == ']' && i > 0 {
				// This is an array, let's remove the index number
				key = key[:i] + "[]"
			}
			urlp.Add(key, value)
		}
		urlpstr := urlp.Encode()
		request.BaseURL += urlpstr
	}

	req, err := http.NewRequest(string(request.Method), request.BaseURL, bytes.NewBuffer(request.Body))
	if err != nil {
		return nil, err
	}

	for key, value := range request.Headers {
		req.Header.Set(key, value)
	}
	_, exists := req.Header["Content-Type"]
	if len(request.Body) > 0 && !exists {
		req.Header.Set("Content-Type", "application/json")
	}

	// Build the HTTP client and make the request.
	res, err := c.MakeRequest(req)
	if err != nil {
		return nil, err
	}

	// Build Response object.
	response, err := rest.BuildResponse(res)
	if err != nil {
		return nil, err
	}

	return response, nil
}

// prepareRequest inserts all pre-defined stuff
func (g *Client) prepareRequest(target string, method rest.Method, params apiCallParams) (req rest.Request) {
	endPoint := g.APIBase + "/" + target

	// Request headers
	hdrs := make(map[string]string)
	hdrs["User-Agent"] = fmt.Sprintf("Gondole/%s", GondoleVersion)
	if g.UserToken != nil {
		hdrs["Authorization"] = fmt.Sprintf("Bearer %s", g.UserToken.AccessToken)
	}

	req = rest.Request{
		BaseURL:     endPoint,
		Headers:     hdrs,
		Method:      method,
		QueryParams: params,
	}
	return
}

// apiCall makes a call to the Mastodon API server
func (g *Client) apiCall(endPoint string, method rest.Method, params apiCallParams, data interface{}) error {
	// Prepare query
	req := g.prepareRequest(endPoint, method, params)

	// Make API call
	r, err := restAPI(req)
	if err != nil {
		return fmt.Errorf("API query (%s) failed: %s", endPoint, err.Error())
	}

	// Check for error reply
	var errorResult Error
	if err := json.Unmarshal([]byte(r.Body), &errorResult); err == nil {
		// The empty object is not an error
		if errorResult.Text != "" {
			return fmt.Errorf("%s", errorResult.Text)
		}
	}

	// Not an error reply; let's unmarshal the data
	err = json.Unmarshal([]byte(r.Body), &data)
	if err != nil {
		return fmt.Errorf("cannot decode API response (%s): %s", method, err.Error())
	}
	return nil
}