Rework the API wrappers to handle arrays of parameters
authorMikael Berthe <mikael@lilotux.net>
Sat, 15 Apr 2017 21:08:34 +0200
changeset 125 2bbb72b9ebf6
parent 124 5ee3f23205af
child 126 330c4b83f7d3
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...).
account.go
api.go
gondole.go
login.go
report.go
--- a/account.go	Sat Apr 15 21:08:34 2017 +0200
+++ b/account.go	Sat Apr 15 21:08:34 2017 +0200
@@ -244,25 +244,17 @@
 }
 
 // GetAccountRelationships returns a list of relationship entities for the given accounts
-// NOTE: Currently it doesn't seem to work with several items.
 func (g *Client) GetAccountRelationships(accountIDs []int) ([]Relationship, error) {
 	if len(accountIDs) < 1 {
 		return nil, ErrInvalidID
 	}
 
-	if len(accountIDs) > 1 { // XXX
-		return nil, fmt.Errorf("accounts/relationships currently does not work with more than 1 ID")
+	params := make(apiCallParams)
+	for i, id := range accountIDs {
+		qID := fmt.Sprintf("id[%d]", i+1)
+		params[qID] = strconv.Itoa(id)
 	}
 
-	params := make(apiCallParams)
-	params["id"] = strconv.Itoa(accountIDs[0])
-	/*
-		for i, id := range accountIDList {
-			qID := fmt.Sprintf("id[%d]", i+1)
-			params[qID] = strconv.Itoa(id)
-		}
-	*/
-
 	var rl []Relationship
 	if err := g.apiCall("accounts/relationships", rest.Get, params, &rl); err != nil {
 		return nil, err
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/api.go	Sat Apr 15 21:08:34 2017 +0200
@@ -0,0 +1,117 @@
+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
+}
--- a/gondole.go	Sat Apr 15 21:08:34 2017 +0200
+++ b/gondole.go	Sat Apr 15 21:08:34 2017 +0200
@@ -1,11 +1,7 @@
 package gondole
 
 import (
-	"encoding/json"
 	"errors"
-	"fmt"
-
-	"github.com/sendgrid/rest"
 )
 
 // apiCallParams is a map with the parameters for an API call
@@ -30,51 +26,3 @@
 	ErrInvalidParameter  = errors.New("incorrect parameter")
 	ErrInvalidID         = errors.New("incorrect entity ID")
 )
-
-// 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 := rest.API(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
-}
--- a/login.go	Sat Apr 15 21:08:34 2017 +0200
+++ b/login.go	Sat Apr 15 21:08:34 2017 +0200
@@ -46,7 +46,7 @@
 		Method:      rest.Post,
 	}
 
-	r, err := rest.API(req)
+	r, err := restAPI(req)
 	if err != nil {
 		return err
 	}
--- a/report.go	Sat Apr 15 21:08:34 2017 +0200
+++ b/report.go	Sat Apr 15 21:08:34 2017 +0200
@@ -1,6 +1,7 @@
 package gondole
 
 import (
+	"fmt"
 	"strconv"
 
 	"github.com/sendgrid/rest"
@@ -16,7 +17,6 @@
 }
 
 // ReportUser reports the user account
-// NOTE: Currently only the first statusID is sent.
 func (g *Client) ReportUser(accountID int, statusIDs []int, comment string) (*Report, error) {
 	if accountID < 1 || comment == "" || len(statusIDs) < 1 {
 		return nil, ErrInvalidParameter
@@ -24,9 +24,11 @@
 
 	params := make(apiCallParams)
 	params["account_id"] = strconv.Itoa(accountID)
-	// XXX Sending only the first one since I'm not sure arrays work
-	params["status_ids"] = strconv.Itoa(statusIDs[0])
 	params["comment"] = comment
+	for i, id := range statusIDs {
+		qID := fmt.Sprintf("status_ids[%d]", i+1)
+		params[qID] = strconv.Itoa(id)
+	}
 
 	var report Report
 	if err := g.apiCall("reports", rest.Post, params, &report); err != nil {