-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrally.go
181 lines (142 loc) · 4 KB
/
rally.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package rally
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)
const apiURL = "https://rally1.rallydev.com/slm/webservice/v2.0/"
const apiSessionKey = "ZSESSIONID"
type rally struct {
url string
token string
}
// New returns a rally instance storing the necessary data. The token string
// must be obtained from rally via: https://rally1.rallydev.com/login/
//
// Basic auth with username and password is not supported at the moment
func New(token string) *rally {
return &rally{url: apiURL, token: token}
}
// Get a populated instance of the supplied object given the id
func (r *rally) Get(object interface{}, id string) error {
kind := getStructType(object)
parts := []string{strings.ToLower(kind), "/", id}
url := r.generateURL(strings.Join(parts, ""))
body, err := r.get(url)
if err != nil {
return err
}
return unmarshal(object, body, kind)
}
// Fetch a populated instance of the supplied object given the reference
// supplied
func (r *rally) Fetch(object interface{}, ref reference) error {
kind := getStructType(object)
if len(ref.ReferenceURL) == 0 {
message := fmt.Sprintf("null reference for: %s", kind)
return errors.New(message)
}
body, err := r.get(ref.ReferenceURL)
if err != nil {
return err
}
return unmarshal(object, body, kind)
}
// Fetch a populated query instance of the supplied query object given the
// query reference supplied
func (r *rally) QueryFetch(object interface{}, ref queryReference) error {
if len(ref.ReferenceURL) == 0 {
kind := getStructType(object)
message := fmt.Sprintf("null query reference for: %s", kind)
return errors.New(message)
}
body, err := r.get(ref.ReferenceURL)
if err != nil {
return err
}
return unmarshal(object, body, "QueryResult")
}
// Concatinates the api url with the supplied uri
func (r *rally) generateURL(uri string) string {
var buffer bytes.Buffer
buffer.WriteString(r.url)
buffer.WriteString(uri)
return buffer.String()
}
// Convenience method for HTTP GET requests
func (r *rally) get(url string) ([]byte, error) {
return r.call("GET", url)
}
// Call the supplied url using the HTTP method
func (r *rally) call(method string, url string) (body []byte, err error) {
request, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
request.Header.Add(apiSessionKey, r.token)
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
message := fmt.Sprintf("http status code: %d", response.StatusCode)
return nil, errors.New(message)
}
body, err = ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
return body, nil
}
// Get the name of the struct
func getStructType(object interface{}) string {
name := fmt.Sprintf("%T", object)
splitted := strings.Split(name, ".")
return splitted[len(splitted)-1]
}
// Unmarshal the body onto the supplied object
func unmarshal(object interface{}, body []byte, key string) (err error) {
var data map[string]*json.RawMessage
if err = json.Unmarshal(body, &data); err != nil {
return err
}
errorResponseKey := "OperationResult"
if _, isset := data[errorResponseKey]; isset {
if err = checkResponse(data, errorResponseKey); err != nil {
return err
}
}
if err = json.Unmarshal(*data[key], &object); err != nil {
return err
}
if err = checkResponse(data, key); err != nil {
return err
}
return nil
}
// Check the response for any errors or warnings. Warnings will be logged
// and errors will be returned
func checkResponse(data map[string]*json.RawMessage, key string) error {
type result struct {
Errors []string
Warnings []string
}
var r result
json.Unmarshal(*data[key], &r)
if len(r.Warnings) > 0 {
message := strings.Join(r.Warnings, "\"; \"")
log.Println("rally api warnings: \"" + message + "\"")
}
if len(r.Errors) > 0 {
message := strings.Join(r.Errors, "\"; \"")
return errors.New("rally api errors: \"" + message + "\"")
}
return nil
}