Skip to content

Commit c14c123

Browse files
authored
Feature/add call support (#1)
* Add Call and CallResource * Add proper api error parsing
1 parent d56d49d commit c14c123

File tree

3 files changed

+174
-31
lines changed

3 files changed

+174
-31
lines changed

twilio.go

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package twilio
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"io"
78
"net/http"
89
"net/url"
910
"strings"
1011

12+
"github.com/google/go-querystring/query"
1113
"github.com/pkg/errors"
1214
"go.opencensus.io/trace"
1315
)
@@ -62,7 +64,7 @@ func (c *Client) DisconnectCall(ctx context.Context, callSid string) error {
6264
defer res.Body.Close()
6365

6466
if res.StatusCode != http.StatusOK {
65-
return fmt.Errorf("twilio.Client.DisconnectCall(): expected status code 200, got %d", res.StatusCode)
67+
return errors.WithMessage(decodeError(res.Body), "twilio.Client.DisconnectCall()")
6668
}
6769

6870
return nil
@@ -91,45 +93,76 @@ func (c *Client) SetMute(ctx context.Context, conferenceSid, callSid string, mut
9193
defer res.Body.Close()
9294

9395
if res.StatusCode != http.StatusOK {
94-
return fmt.Errorf("twilio.Client.SetMute(): expected status code 200, got %d", res.StatusCode)
96+
return errors.WithMessage(decodeError(res.Body), "twilio.Client.SetMute()")
9597
}
9698

9799
return nil
98100
}
99101

100-
// CallResource recieves call resource details
101-
func (c *Client) CallResource(ctx context.Context, callSid string) {
102+
// CallResource receives call resource details
103+
func (c *Client) CallResource(ctx context.Context, callSid string) (*CallResource, error) {
102104
ctx, span := trace.StartSpan(ctx, "twilio.Client.CallResource()")
103105
defer span.End()
104106

105-
}
107+
url := fmt.Sprintf("%s/Accounts/%s/Calls/%s.json", baseURL, c.accountSid, callSid)
108+
109+
req, err := c.newRequest(ctx, http.MethodGet, url, nil)
110+
if err != nil {
111+
return nil, errors.WithMessage(err, "twilio.Client.CallResource()")
112+
}
113+
114+
res, err := c.httpClient.Do(req)
115+
if err != nil {
116+
return nil, errors.WithMessage(err, "twilio.Client.CallResource(): http.Do(")
117+
}
118+
defer res.Body.Close()
106119

107-
// CallResource holds the details of a call resouce
108-
type CallResource struct {
109-
Sid string `json:"sid,omitempty"`
110-
DateCreated string `json:"date_created,omitempty"`
111-
DateUpdated string `json:"date_updated,omitempty"`
112-
ParentCallSid string `json:"parent_call_sid,omitempty"`
113-
AccountSid string `json:"account_sid,omitempty"`
114-
To string `json:"to,omitempty"`
115-
From string `json:"from,omitempty"`
116-
PhoneNumberSid string `json:"phone_number_sid,omitempty"`
117-
Status string `json:"status,omitempty"`
118-
StartTime string `json:"start_time,omitempty"`
119-
EndTime string `json:"end_time,omitempty"`
120-
Duration string `json:"duration,omitempty"`
121-
Price string `json:"price,omitempty"`
122-
Direction string `json:"direction,omitempty"`
123-
AnsweredBy string `json:"answered_by,omitempty"`
124-
APIVersion string `json:"api_version,omitempty"`
125-
ForwardedFrom string `json:"forwarded_from,omitempty"`
126-
CallerName string `json:"caller_name,omitempty"`
127-
URI string `json:"uri,omitempty"`
128-
SubresourceUris SubresourceUris `json:"subresource_uris,omitempty"`
120+
if res.StatusCode != http.StatusOK {
121+
return nil, errors.WithMessage(decodeError(res.Body), "twilio.Client.CallResource()")
122+
}
123+
124+
callResource := &CallResource{}
125+
126+
if err := json.NewDecoder(res.Body).Decode(callResource); err != nil {
127+
return nil, errors.WithMessage(err, "twilio.Client.CallResource(): json.Decoder.Decode()")
128+
}
129+
130+
return callResource, nil
129131
}
130132

131-
// SubresourceUris holds details for subresource uri's
132-
type SubresourceUris struct {
133-
Notifications string `json:"notifications,omitempty"`
134-
Recordings string `json:"recordings,omitempty"`
133+
// Call creates an outbound call returning the resulting CallResource
134+
func (c *Client) Call(ctx context.Context, call *Call) (*CallResource, error) {
135+
ctx, span := trace.StartSpan(ctx, "twilio.Client.Call()")
136+
defer span.End()
137+
138+
params, err := query.Values(call)
139+
if err != nil {
140+
return nil, errors.WithMessage(err, "twilio.Client.Call(): query.Values()")
141+
}
142+
143+
url := fmt.Sprintf("%s/Accounts/%s/Calls.json", baseURL, c.accountSid)
144+
body := strings.NewReader(params.Encode())
145+
146+
req, err := c.newRequest(ctx, http.MethodPost, url, body)
147+
if err != nil {
148+
return nil, errors.WithMessage(err, "twilio.Client.Call()")
149+
}
150+
151+
res, err := c.httpClient.Do(req)
152+
if err != nil {
153+
return nil, errors.WithMessage(err, "twilio.Client.Call(): http.Do(")
154+
}
155+
defer res.Body.Close()
156+
157+
if res.StatusCode != http.StatusCreated {
158+
return nil, errors.WithMessage(decodeError(res.Body), "twilio.Client.Call()")
159+
}
160+
161+
callResource := &CallResource{}
162+
163+
if err := json.NewDecoder(res.Body).Decode(callResource); err != nil {
164+
return nil, errors.WithMessage(err, "twilio.Client.Call(): json.Decoder.Decode()")
165+
}
166+
167+
return callResource, nil
135168
}

types.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package twilio
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// CallResource holds the details of a call resouce
8+
type CallResource struct {
9+
Sid string `json:"sid,omitempty"`
10+
AccountSid string `json:"account_sid,omitempty"`
11+
Annotation string `json:"annotation,omitempty"`
12+
AnsweredBy string `json:"answered_by,omitempty"`
13+
APIVersion string `json:"api_version,omitempty"`
14+
CallerName string `json:"caller_name,omitempty"`
15+
DateCreated string `json:"date_created,omitempty"`
16+
DateUpdated string `json:"date_updated,omitempty"`
17+
Direction string `json:"direction,omitempty"`
18+
Duration string `json:"duration,omitempty"`
19+
EndTime string `json:"end_time,omitempty"`
20+
ForwardedFrom string `json:"forwarded_from,omitempty"`
21+
From string `json:"from,omitempty"`
22+
FromFormatted string `json:"from_formatted,omitempty"`
23+
GroupSid string `json:"group_sid,omitempty"`
24+
ParentCallSid string `json:"parent_call_sid,omitempty"`
25+
PhoneNumberSid string `json:"phone_number_sid,omitempty"`
26+
Price string `json:"price,omitempty"`
27+
PriceUnit string `json:"price_unit,omitempty"`
28+
QueueTime string `json:"queue_time,omitempty"`
29+
StartTime string `json:"start_time,omitempty"`
30+
Status string `json:"status,omitempty"`
31+
SubresourceUris SubresourceUris `json:"subresource_uris,omitempty"`
32+
To string `json:"to,omitempty"`
33+
ToFormatted string `json:"to_formatted,omitempty"`
34+
TrunkSid string `json:"trunk_sid,omitempty"`
35+
URI string `json:"uri,omitempty"`
36+
}
37+
38+
// SubresourceUris holds details for subresource uri's
39+
type SubresourceUris struct {
40+
Notifications string `json:"notifications,omitempty"`
41+
Recordings string `json:"recordings,omitempty"`
42+
Feedback string `json:"feedback,omitempty"`
43+
FeedbackSummaries string `json:"feedback_summaries,omitempty"`
44+
Payments string `json:"payments,omitempty"`
45+
}
46+
47+
// Call describes a outgoing call settings
48+
type Call struct {
49+
AccountSid string `url:"AccountSid,omitempty"`
50+
ApplicationSid string `url:"ApplicationSid,omitempty"`
51+
AsyncAmd string `url:"AsyncAmd,omitempty"`
52+
AsyncAmdStatusCallback string `url:"AsyncAmdStatusCallback,omitempty"`
53+
AsyncAmdStatusCallbackMethod string `url:"AsyncAmdStatusCallbackMethod,omitempty"`
54+
Byoc string `url:"Byoc,omitempty"`
55+
CallerId string `url:"CallerId,omitempty"`
56+
CallReason string `url:"CallReason,omitempty"`
57+
FallbackMethod string `url:"FallbackMethod,omitempty"`
58+
FallbackUrl string `url:"FallbackUrl,omitempty"`
59+
From string `url:"From,omitempty"`
60+
MachineDetection string `url:"MachineDetection,omitempty"`
61+
MachineDetectionSilenceTimeout int `url:"MachineDetectionSilenceTimeout,omitempty"`
62+
MachineDetectionSpeechEndThreshold int `url:"MachineDetectionSpeechEndThreshold,omitempty"`
63+
MachineDetectionSpeechThreshold int `url:"MachineDetectionSpeechThreshold,omitempty"`
64+
MachineDetectionTimeout int `url:"MachineDetectionTimeout,omitempty"`
65+
Method string `url:"Method,omitempty"`
66+
Record bool `url:"Record,omitempty"`
67+
RecordingChannels string `url:"RecordingChannels,omitempty"`
68+
RecordingStatusCallback string `url:"RecordingStatusCallback,omitempty"`
69+
RecordingStatusCallbackEvent string `url:"RecordingStatusCallbackEvent,omitempty"`
70+
RecordingStatusCallbackMethod string `url:"RecordingStatusCallbackMethod,omitempty"`
71+
SendDigits string `url:"SendDigits,omitempty"`
72+
SipAuthPassword string `url:"SipAuthPassword,omitempty"`
73+
SipAuthUsername string `url:"SipAuthUsername,omitempty"`
74+
StatusCallback string `url:"StatusCallback,omitempty"`
75+
StatusCallbackEvent string `url:"StatusCallbackEvent,omitempty"`
76+
StatusCallbackMethod string `url:"StatusCallbackMethod,omitempty"`
77+
Timeout int `url:"Timeout,omitempty"`
78+
To string `url:"To,omitempty"`
79+
Trim string `url:"Trim,omitempty"`
80+
Twiml string `url:"Twiml,omitempty"`
81+
URL string `url:"Url,omitempty"`
82+
}
83+
84+
type APIError struct {
85+
Code int `json:"code"`
86+
Message string `json:"message"`
87+
MoreInfo string `json:"more_info"`
88+
Status int `json:"status"`
89+
}
90+
91+
func (a *APIError) Error() string {
92+
return fmt.Sprintf("APIError: %s: more_info: %s", a.Message, a.MoreInfo)
93+
}

utils.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package twilio
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
7+
"github.com/pkg/errors"
8+
)
9+
10+
func decodeError(r io.Reader) error {
11+
apiError := &APIError{}
12+
if err := json.NewDecoder(r).Decode(apiError); err != nil {
13+
return errors.WithMessage(err, "decodeError()")
14+
}
15+
16+
return apiError
17+
}

0 commit comments

Comments
 (0)