Skip to content

Commit

Permalink
feat: added support for application/json content type in request body (
Browse files Browse the repository at this point in the history
…#239)

* feat: added support for application/json content type in request body

* docs: updated README.md with feature update

* chore: corrected version

* chore: corrected model names

* chore: added messaging bulk example (#243)

* chore: added messaging bulk example

* chore: added messaging bulk example

* chore: added messaging bulk example

* chore: corrected message
  • Loading branch information
tiwarishubham635 authored Apr 4, 2024
1 parent 298c07d commit fdee56a
Show file tree
Hide file tree
Showing 607 changed files with 5,905 additions and 797 deletions.
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
[![Tests](https://github.com/twilio/twilio-go/actions/workflows/test-and-deploy.yml/badge.svg)](https://github.com/twilio/twilio-go/actions/workflows/test-and-deploy.yml)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/twilio/twilio-go)](https://pkg.go.dev/github.com/twilio/twilio-go)
[![Release](https://img.shields.io/github/release/twilio/twilio-go.svg)](https://github.com/twilio/twilio-go/releases/latest)
[![Learn OSS Contribution in TwilioQuest](https://img.shields.io/static/v1?label=TwilioQuest&message=Learn%20to%20contribute%21&color=F22F46&labelColor=1f243c&style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAASFBMVEUAAAAZGRkcHBwjIyMoKCgAAABgYGBoaGiAgICMjIyzs7PJycnMzMzNzc3UoBfd3d3m5ubqrhfrMEDu7u739/f4vSb/3AD///9tbdyEAAAABXRSTlMAAAAAAMJrBrEAAAKoSURBVHgB7ZrRcuI6EESdyxXGYoNFvMD//+l2bSszRgyUYpFAsXOeiJGmj4NkuWx1Qeh+Ekl9DgEXOBwOx+Px5xyQhDykfgq4wG63MxxaR4ddIkg6Ul3g84vCIcjPBA5gmUMeXESrlukuoK33+33uID8TWeLAdOWsKpJYzwVMB7bOzYSGOciyUlXSn0/ABXTosJ1M1SbypZ4O4MbZuIDMU02PMbauhhHMHXbmebmALIiEbbbbbUrpF1gwE9kFfRNAJaP+FQEXCCTGyJ4ngDrjOFo3jEL5JdqjF/pueR4cCeCGgAtwmuRS6gDwaRiGvu+DMFwSBLTE3+jF8JyuV1okPZ+AC4hDFhCHyHQjdjPHUKFDlHSJkHQXMB3KpSwXNGJPcwwTdZiXlRN0gSp0zpWxNtM0beYE0nRH6QIbO7rawwXaBYz0j78gxjokDuv12gVeUuBD0MDi0OQCLvDaAho4juP1Q/jkAncXqIcCfd+7gAu4QLMACCLxpRsSuQh0igu0C9Svhi7weAGZg50L3IE3cai4IfkNZAC8dfdhsUD3CgKBVC9JE5ABAFzg4QL/taYPAAWrHdYcgfLaIgAXWJ7OV38n1LEF8tt2TH29E+QAoDoO5Ve/LtCQDmKM9kPbvCEBApK+IXzbcSJ0cIGF6e8gpcRhUDogWZ8JnaWjPXc/fNnBBUKRngiHgTUSivSzDRDgHZQOLvBQgf8rRt+VdBUUhwkU6VpJ+xcOwQUqZr+mR0kvBUgv6cB4+37hQAkXqE8PwGisGhJtN4xAHMzrsgvI7rccXqSvKh6jltGlrOHA3Xk1At3LC4QiPdX9/0ndHpGVvTjR4bZA1ypAKgVcwE5vx74ulwIugDt8e/X7JgfkucBMIAr26ndnB4UCLnDOqvteQsHlgX9N4A+c4cW3DXSPbwAAAABJRU5ErkJggg==)](https://twil.io/learn-open-source)

All the code [here](./rest) was generated by [twilio-oai-generator](https://github.com/twilio/twilio-oai-generator) by
leveraging [openapi-generator](https://github.com/OpenAPITools/openapi-generator)
and [twilio-oai](https://github.com/twilio/twilio-oai). If you find an issue with the generation or the OpenAPI specs,
please go ahead and open an issue or a PR against the relevant repositories.

## 🚀 Feature Update
Twilio Go Helper Library's version 1.20.0 adds support for the application/json content type in the request body. See example [here](#messaging-bulk).
Behind the scenes Go Helper is now auto-generated via OpenAPI with this release.
This enables us to rapidly add new features and enhance consistency across versions and languages.

## Documentation

The documentation for the Twilio API can be found [here][apidocs].
Expand Down Expand Up @@ -297,6 +301,48 @@ func main() {
}
```

### Send Bulk Message <a id="messaging-bulk"></a>

Try sending a message to multiple recipients with JSON request body support.

```go
package main

import (
"encoding/json"
"fmt"

"github.com/twilio/twilio-go"
previewMessaging "github.com/twilio/twilio-go/rest/preview_messaging/v1"
)

func main() {
accountSid := "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
authToken := "f2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

client := twilio.NewRestClientWithParams(twilio.ClientParams{
Username: accountSid,
Password: authToken,
})

// create multiple recipients
msg1 := previewMessaging.MessagingV1Message{To: "+XXXXXXXXXX"}
msg2 := previewMessaging.MessagingV1Message{To: "+XXXXXXXXXX"}

// create message request object
req := &previewMessaging.CreateMessagesRequest{Messages: []previewMessaging.MessagingV1Message{msg1, msg2}, Body: "Hello from Go!", From: "+XXXXXXXXXX"}
params := &previewMessaging.CreateMessagesParams{CreateMessagesRequest: req}

resp, err := client.PreviewMessagingV1.CreateMessages(params)
if err != nil {
fmt.Println("Error sending SMS message: " + err.Error())
} else {
response, _ := json.Marshal(*resp)
fmt.Println("Response: " + string(response))
}
}
```

### Iterate through records

This library also offers paging functionality. Collections such as calls and messages have `ListXxx` and `StreamXxx`
Expand Down
2 changes: 1 addition & 1 deletion client/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ type BaseClient interface {
AccountSid() string
SetTimeout(timeout time.Duration)
SendRequest(method string, rawURL string, data url.Values,
headers map[string]interface{}) (*http.Response, error)
headers map[string]interface{}, body ...byte) (*http.Response, error)
}
66 changes: 47 additions & 19 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package client

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
Expand Down Expand Up @@ -64,10 +65,20 @@ func (c *Client) SetTimeout(timeout time.Duration) {
c.HTTPClient.Timeout = timeout
}

func extractContentTypeHeader(headers map[string]interface{}) (cType string) {
headerType, ok := headers["Content-Type"]
if !ok {
return urlEncodedContentType
}
return headerType.(string)
}

const (
keepZeros = true
delimiter = '.'
escapee = '\\'
urlEncodedContentType = "application/x-www-form-urlencoded"
jsonContentType = "application/json"
keepZeros = true
delimiter = '.'
escapee = '\\'
)

func (c *Client) doWithErr(req *http.Request) (*http.Response, error) {
Expand Down Expand Up @@ -117,16 +128,24 @@ func (c *Client) validateCredentials() error {

// SendRequest verifies, constructs, and authorizes an HTTP request.
func (c *Client) SendRequest(method string, rawURL string, data url.Values,
headers map[string]interface{}) (*http.Response, error) {
headers map[string]interface{}, body ...byte) (*http.Response, error) {

contentType := extractContentTypeHeader(headers)

u, err := url.Parse(rawURL)
if err != nil {
return nil, err
}

valueReader := &strings.Reader{}
goVersion := runtime.Version()
var req *http.Request

if method == http.MethodGet {
//For HTTP GET Method there are no body parameters. All other parameters like query, path etc
// are added as information in the url itself. Also while Content-Type is json, we are sending
// json body. In that case, data variable conatins all other parameters than body, which is the
//same case as GET method. In that case as well all parameters will be added to url
if method == http.MethodGet || contentType == jsonContentType {
if data != nil {
v, _ := form.EncodeToStringWith(data, delimiter, escapee, keepZeros)
s := delimitingRegex.ReplaceAllString(v, "")
Expand All @@ -135,18 +154,32 @@ func (c *Client) SendRequest(method string, rawURL string, data url.Values,
}
}

if method == http.MethodPost {
valueReader = strings.NewReader(data.Encode())
}
//data is already processed and information will be added to u(the url) in the
//previous step. Now body will solely contain json payload
if contentType == jsonContentType {
req, err = http.NewRequest(method, u.String(), bytes.NewBuffer(body))
if err != nil {
return nil, err
}
} else {
//Here the HTTP POST methods which is not having json content type are processed
//All the values will be added in data and encoded (all body, query, path parameters)
if method == http.MethodPost {
valueReader = strings.NewReader(data.Encode())
}
credErr := c.validateCredentials()
if credErr != nil {
return nil, credErr
}
req, err = http.NewRequest(method, u.String(), valueReader)
if err != nil {
return nil, err
}

credErr := c.validateCredentials()
if credErr != nil {
return nil, credErr
}

req, err := http.NewRequest(method, u.String(), valueReader)
if err != nil {
return nil, err
if contentType == urlEncodedContentType {
req.Header.Add("Content-Type", urlEncodedContentType)
}

req.SetBasicAuth(c.basicAuth())
Expand All @@ -160,14 +193,9 @@ func (c *Client) SendRequest(method string, rawURL string, data url.Values,

req.Header.Add("User-Agent", userAgent)

if method == http.MethodPost {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
}

for k, v := range headers {
req.Header.Add(k, fmt.Sprint(v))
}

return c.doWithErr(req)
}

Expand Down
2 changes: 1 addition & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ func TestClient_SetAccountSid(t *testing.T) {
func TestClient_DefaultUserAgentHeaders(t *testing.T) {
headerServer := httptest.NewServer(http.HandlerFunc(
func(writer http.ResponseWriter, request *http.Request) {
assert.Regexp(t, regexp.MustCompile(`^twilio-go/[0-9.]+\s\(\w+\s\w+\)\sgo/\S+$`), request.Header.Get("User-Agent"))
assert.Regexp(t, regexp.MustCompile(`^twilio-go/[0-9.]+(-rc.[0-9])*\s\(\w+\s\w+\)\sgo/\S+$`), request.Header.Get("User-Agent"))
}))

resp, _ := testClient.SendRequest("GET", headerServer.URL, nil, nil)
Expand Down
9 changes: 4 additions & 5 deletions client/request_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ func NewRequestHandler(client BaseClient) *RequestHandler {
}

func (c *RequestHandler) sendRequest(method string, rawURL string, data url.Values,
headers map[string]interface{}) (*http.Response, error) {
headers map[string]interface{}, body ...byte) (*http.Response, error) {
parsedURL, err := c.BuildUrl(rawURL)
if err != nil {
return nil, err
}

return c.Client.SendRequest(method, parsedURL, data, headers)
return c.Client.SendRequest(method, parsedURL, data, headers, body...)
}

// BuildUrl builds the target host string taking into account region and edge configurations.
Expand Down Expand Up @@ -83,8 +82,8 @@ func (c *RequestHandler) BuildUrl(rawURL string) (string, error) {
return u.String(), nil
}

func (c *RequestHandler) Post(path string, bodyData url.Values, headers map[string]interface{}) (*http.Response, error) {
return c.sendRequest(http.MethodPost, path, bodyData, headers)
func (c *RequestHandler) Post(path string, bodyData url.Values, headers map[string]interface{}, body ...byte) (*http.Response, error) {
return c.sendRequest(http.MethodPost, path, bodyData, headers, body...)
}

func (c *RequestHandler) Get(path string, queryData url.Values, headers map[string]interface{}) (*http.Response, error) {
Expand Down
52 changes: 52 additions & 0 deletions client/request_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package client_test

import (
"errors"
"net/http"
"net/http/httptest"
"net/url"
"testing"

Expand Down Expand Up @@ -65,3 +67,53 @@ func assertAndGetURL(t *testing.T, requestHandler *client.RequestHandler, rawURL
assert.Nil(t, err)
return parsedURL
}

func TestRequestHandler_SendGetRequest(t *testing.T) {
errorResponse := `{
"status": 400,
"code":20001,
"message":"Bad request",
"more_info":"https://www.twilio.com/docs/errors/20001"
}`
errorServer := httptest.NewServer(http.HandlerFunc(
func(resp http.ResponseWriter, req *http.Request) {
resp.WriteHeader(400)
_, _ = resp.Write([]byte(errorResponse))
}))
defer errorServer.Close()

requestHandler := NewRequestHandler("user", "pass")
resp, err := requestHandler.Get(errorServer.URL, nil, nil) //nolint:bodyclose
twilioError := err.(*client.TwilioRestError)
assert.Nil(t, resp)
assert.Equal(t, 400, twilioError.Status)
assert.Equal(t, 20001, twilioError.Code)
assert.Equal(t, "https://www.twilio.com/docs/errors/20001", twilioError.MoreInfo)
assert.Equal(t, "Bad request", twilioError.Message)
assert.Nil(t, twilioError.Details)
}

func TestRequestHandler_SendPostRequest(t *testing.T) {
errorResponse := `{
"status": 400,
"code":20001,
"message":"Bad request",
"more_info":"https://www.twilio.com/docs/errors/20001"
}`
errorServer := httptest.NewServer(http.HandlerFunc(
func(resp http.ResponseWriter, req *http.Request) {
resp.WriteHeader(400)
_, _ = resp.Write([]byte(errorResponse))
}))
defer errorServer.Close()

requestHandler := NewRequestHandler("user", "pass")
resp, err := requestHandler.Post(errorServer.URL, nil, nil) //nolint:bodyclose
twilioError := err.(*client.TwilioRestError)
assert.Nil(t, resp)
assert.Equal(t, 400, twilioError.Status)
assert.Equal(t, 20001, twilioError.Code)
assert.Equal(t, "https://www.twilio.com/docs/errors/20001", twilioError.MoreInfo)
assert.Equal(t, "Bad request", twilioError.Message)
assert.Nil(t, twilioError.Details)
}
2 changes: 1 addition & 1 deletion rest/accounts/v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This is the public Twilio REST API.
## Overview
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project from the OpenAPI specs located at [twilio/twilio-oai](https://github.com/twilio/twilio-oai/tree/main/spec). By using the [OpenAPI-spec](https://www.openapis.org/) from a remote server, you can easily generate an API client.

- API version: 1.55.3
- API version: 1.0.0
- Package version: 1.0.0
- Build package: com.twilio.oai.TwilioGoGenerator
For more information, please visit [https://support.twilio.com](https://support.twilio.com)
Expand Down
2 changes: 1 addition & 1 deletion rest/accounts/v1/docs/ListCredentialAwsResponseMeta.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**FirstPageUrl** | **string** | |[optional]
**Key** | **string** | |[optional]
**NextPageUrl** | Pointer to **string** | |
**Page** | **int** | |[optional]
**PageSize** | **int** | |[optional]
**PreviousPageUrl** | Pointer to **string** | |
**Url** | **string** | |[optional]
**Key** | **string** | |[optional]

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ package openapi
// ListCredentialAwsResponseMeta struct for ListCredentialAwsResponseMeta
type ListCredentialAwsResponseMeta struct {
FirstPageUrl string `json:"first_page_url,omitempty"`
Key string `json:"key,omitempty"`
NextPageUrl *string `json:"next_page_url,omitempty"`
Page int `json:"page,omitempty"`
PageSize int `json:"page_size,omitempty"`
PreviousPageUrl *string `json:"previous_page_url,omitempty"`
Url string `json:"url,omitempty"`
Key string `json:"key,omitempty"`
}
Loading

0 comments on commit fdee56a

Please sign in to comment.