├── .env_sample
├── .github
└── workflows
│ ├── pr-lint.yml
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── FIRST_TIMERS.md
├── LICENSE
├── Makefile
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── TROUBLESHOOTING.md
├── USAGE.md
├── docker-compose.yml
├── docker
├── Dockerfile
└── example.go
├── examples
└── example.go
├── rest.go
├── rest_test.go
├── static
└── img
│ ├── github-fork.png
│ └── github-sign-up.png
├── twilio_sendgrid_logo.png
└── use-cases
└── README.md
/.env_sample:
--------------------------------------------------------------------------------
1 | export SENDGRID_API_KEY=''
--------------------------------------------------------------------------------
/.github/workflows/pr-lint.yml:
--------------------------------------------------------------------------------
1 | name: Lint PR
2 | on:
3 | pull_request_target:
4 | types: [ opened, edited, synchronize, reopened ]
5 |
6 | jobs:
7 | validate:
8 | name: Validate title
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: amannn/action-semantic-pull-request@v4
12 | with:
13 | types: chore docs fix feat test
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test and Deploy
2 | on:
3 | push:
4 | branches: [ '*' ]
5 | tags: [ '*' ]
6 | pull_request:
7 | branches: [ main ]
8 | schedule:
9 | # Run automatically at 8AM PST Monday-Friday
10 | - cron: '0 15 * * 1-5'
11 | workflow_dispatch:
12 |
13 | jobs:
14 | test:
15 | name: Build & Test
16 | runs-on: ubuntu-latest
17 | timeout-minutes: 20
18 | strategy:
19 | matrix:
20 | go: [ '1.14', '1.15', '1.16', '1.17' ]
21 | steps:
22 | - name: Checkout rest
23 | uses: actions/checkout@v2
24 |
25 | - name: Setup Go environment
26 | uses: actions/setup-go@v2
27 | with:
28 | go-version: ${{ matrix.go }}
29 |
30 | - name: Set Go env vars
31 | run: |
32 | echo "GOPATH=$HOME" >> $GITHUB_ENV
33 | echo "GOBIN=$HOME/bin" >> $GITHUB_ENV
34 | echo "GO111MODULE=off" >> $GITHUB_ENV
35 |
36 | - name: Run Tests
37 | run: make test
38 |
39 | deploy:
40 | name: Deploy
41 | if: success() && github.ref_type == 'tag'
42 | needs: [ test ]
43 | runs-on: ubuntu-latest
44 | steps:
45 | - name: Checkout rest
46 | uses: actions/checkout@v2
47 |
48 | - name: Create GitHub Release
49 | uses: sendgrid/dx-automator/actions/release@main
50 | env:
51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52 |
53 | - name: Submit metric to Datadog
54 | uses: sendgrid/dx-automator/actions/datadog-release-metric@main
55 | env:
56 | DD_API_KEY: ${{ secrets.DATADOG_API_KEY }}
57 |
58 | notify-on-failure:
59 | name: Slack notify on failure
60 | if: failure() && github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag')
61 | needs: [ test, deploy ]
62 | runs-on: ubuntu-latest
63 | steps:
64 | - uses: rtCamp/action-slack-notify@v2
65 | env:
66 | SLACK_COLOR: failure
67 | SLACK_ICON_EMOJI: ':github:'
68 | SLACK_MESSAGE: ${{ format('Test *{0}*, Deploy *{1}*, {2}/{3}/actions/runs/{4}', needs.test.result, needs.deploy.result, github.server_url, github.repository, github.run_id) }}
69 | SLACK_TITLE: Action Failure - ${{ github.repository }}
70 | SLACK_USERNAME: GitHub Actions
71 | SLACK_MSG_AUTHOR: twilio-dx
72 | SLACK_FOOTER: Posted automatically using GitHub Actions
73 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
74 | MSG_MINIMAL: true
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 | .env
26 | .settings.json
27 |
28 | temp.go
29 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 |
4 | This project adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | [2022-03-09] Version 2.6.9
7 | --------------------------
8 | **Library - Chore**
9 | - [PR #110](https://github.com/sendgrid/rest/pull/110): push Datadog Release Metric upon deploy success. Thanks to [@eshanholtz](https://github.com/eshanholtz)!
10 |
11 |
12 | [2022-02-09] Version 2.6.8
13 | --------------------------
14 | **Library - Chore**
15 | - [PR #109](https://github.com/sendgrid/rest/pull/109): upgrade supported language versions. Thanks to [@childish-sambino](https://github.com/childish-sambino)!
16 | - [PR #108](https://github.com/sendgrid/rest/pull/108): add gh release to workflow. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)!
17 |
18 |
19 | [2022-01-12] Version 2.6.7
20 | --------------------------
21 | **Library - Chore**
22 | - [PR #107](https://github.com/sendgrid/rest/pull/107): update license year. Thanks to [@JenniferMah](https://github.com/JenniferMah)!
23 |
24 |
25 | [2021-12-15] Version 2.6.6
26 | --------------------------
27 | **Library - Chore**
28 | - [PR #106](https://github.com/sendgrid/rest/pull/106): migrate to gh actions. Thanks to [@beebzz](https://github.com/beebzz)!
29 |
30 |
31 | [2021-09-22] Version 2.6.5
32 | --------------------------
33 | **Library - Chore**
34 | - [PR #105](https://github.com/sendgrid/rest/pull/105): add tests against v1.16. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)!
35 |
36 |
37 | [2021-05-05] Version 2.6.4
38 | --------------------------
39 | **Library - Chore**
40 | - [PR #103](https://github.com/sendgrid/rest/pull/103): follow up context.Context support. Thanks to [@johejo](https://github.com/johejo)!
41 |
42 |
43 | [2021-03-15] Version 2.6.3
44 | --------------------------
45 | **Library - Fix**
46 | - [PR #92](https://github.com/sendgrid/rest/pull/92): add SendWithContext function. Thanks to [@someone1](https://github.com/someone1)!
47 |
48 |
49 | [2020-10-14] Version 2.6.2
50 | --------------------------
51 | **Library - Fix**
52 | - [PR #101](https://github.com/sendgrid/rest/pull/101): Pass empty client instead of http.DefaultClient. Thanks to [@mateorider](https://github.com/mateorider)!
53 |
54 |
55 | [2020-08-19] Version 2.6.1
56 | --------------------------
57 | **Library - Chore**
58 | - [PR #100](https://github.com/sendgrid/rest/pull/100): update GitHub branch references to use HEAD. Thanks to [@thinkingserious](https://github.com/thinkingserious)!
59 |
60 |
61 | [2020-02-19] Version 2.6.0
62 | --------------------------
63 | **Library - Feature**
64 | - [PR #73](https://github.com/sendgrid/rest/pull/73): Dockerize sendgrid/rest. Thanks to [@graystevens](https://github.com/graystevens)!
65 |
66 |
67 | [2020-02-05] Version 2.5.1
68 | --------------------------
69 | **Library - Docs**
70 | - [PR #77](https://github.com/sendgrid/rest/pull/77): Run Grammarly on *.md files. Thanks to [@obahareth](https://github.com/obahareth)!
71 | - [PR #86](https://github.com/sendgrid/rest/pull/86): Fixed link to bug report template. Thanks to [@alxshelepenok](https://github.com/alxshelepenok)!
72 |
73 |
74 | [2020-01-30] Version 2.5.0
75 | --------------------------
76 | **Library - Docs**
77 | - [PR #97](https://github.com/sendgrid/rest/pull/97): baseline all the templated markdown docs. Thanks to [@childish-sambino](https://github.com/childish-sambino)!
78 | - [PR #88](https://github.com/sendgrid/rest/pull/88): add our Developer Experience Engineer career opportunity to the README. Thanks to [@mptap](https://github.com/mptap)!
79 | - [PR #65](https://github.com/sendgrid/rest/pull/65): added "Code Review" section to CONTRIBUTING.md. Thanks to [@aleien](https://github.com/aleien)!
80 | - [PR #80](https://github.com/sendgrid/rest/pull/80): add first timers guide for newcomers. Thanks to [@daniloff200](https://github.com/daniloff200)!
81 | - [PR #82](https://github.com/sendgrid/rest/pull/82): update contribution guide with new workflow. Thanks to [@radlinskii](https://github.com/radlinskii)!
82 | - [PR #62](https://github.com/sendgrid/rest/pull/62): update CONTRIBUTING.md with environment variables section. Thanks to [@thepriefy](https://github.com/thepriefy)!
83 |
84 | **Library - Chore**
85 | - [PR #96](https://github.com/sendgrid/rest/pull/96): prep repo for automation. Thanks to [@thinkingserious](https://github.com/thinkingserious)!
86 | - [PR #94](https://github.com/sendgrid/rest/pull/94): add current Go version to Travis. Thanks to [@pangaunn](https://github.com/pangaunn)!
87 | - [PR #93](https://github.com/sendgrid/rest/pull/93): add current Go versions to Travis. Thanks to [@gliptak](https://github.com/gliptak)!
88 | - [PR #83](https://github.com/sendgrid/rest/pull/83): follow godoc deprecation standards. Thanks to [@vaskoz](https://github.com/vaskoz)!
89 | - [PR #74](https://github.com/sendgrid/rest/pull/74): create README.md in use-cases. Thanks to [@ajloria](https://github.com/ajloria)!
90 |
91 | **Library - Feature**
92 | - [PR #72](https://github.com/sendgrid/rest/pull/72): do not swallow the error code. Thanks to [@Succo](https://github.com/Succo)!
93 |
94 |
95 | [2018-04-09] Version 2.4.1
96 | --------------------------
97 | ### Fixed
98 | - Pull #71, Solves #70
99 | - Fix Travis CI Build
100 | - Special thanks to [Vasko Zdravevski](https://github.com/vaskoz) for the PR!
101 |
102 | ## [2.4.0] - 2017-4-10
103 | ### Added
104 | - Pull #18, Solves #17
105 | - Add RestError Struct for an error handling
106 | - Special thanks to [Takahiro Ikeuchi](https://github.com/iktakahiro) for the PR!
107 |
108 | ## [2.3.1] - 2016-10-14
109 | ### Changed
110 | - Pull #15, solves Issue #7
111 | - Moved QueryParams processing into BuildRequestObject
112 | - Special thanks to [Gábor Lipták](https://github.com/gliptak) for the PR!
113 |
114 | ## [2.3.0] - 2016-10-04
115 | ### Added
116 | - Pull [#10] [Allow for custom Content-Types](https://github.com/sendgrid/rest/issues/10)
117 |
118 | ## [2.2.0] - 2016-07-28
119 | ### Added
120 | - Pull [#9](https://github.com/sendgrid/rest/pull/9): Allow for setting a custom HTTP client
121 | - [Here](rest_test.go#L127) is an example of usage
122 | - This enables usage of the [sendgrid-go library](https://github.com/sendgrid/sendgrid-go) on [Google App Engine (GAE)](https://cloud.google.com/appengine/)
123 | - Special thanks to [Chris Broadfoot](https://github.com/broady) and [Sridhar Venkatakrishnan](https://github.com/sridharv) for providing code and feedback!
124 |
125 | ## [2.1.0] - 2016-06-10
126 | ### Added
127 | - Automatically add Content-Type: application/json when there is a request body
128 |
129 | ## [2.0.0] - 2016-06-03
130 | ### Changed
131 | - Made the Request and Response variables non-redundant. e.g. request.RequestBody becomes request.Body
132 |
133 | ## [1.0.2] - 2016-04-07
134 | ### Added
135 | - these changes are thanks to [deckarep](https://github.com/deckarep). Thanks!
136 | - more updates to error naming convention
137 | - more error handing on HTTP request
138 |
139 | ## [1.0.1] - 2016-04-07
140 | ### Added
141 | - these changes are thanks to [deckarep](https://github.com/deckarep). Thanks!
142 | - update error naming convention
143 | - explicitly define supported HTTP verbs
144 | - better error handling on HTTP request
145 |
146 | ## [1.0.0] - 2016-04-05
147 | ### Added
148 | - We are live!
149 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | - Using welcoming and inclusive language
18 | - Being respectful of differing viewpoints and experiences
19 | - Gracefully accepting constructive criticism
20 | - Focusing on what is best for the community
21 | - Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | - The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | - Trolling, insulting/derogatory comments, and personal or political attacks
28 | - Public or private harassment
29 | - Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | - Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at open-source@twilio.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Hello! Thank you for choosing to help contribute to one of the SendGrid open source projects. There are many ways you can contribute and help is always welcome. We simply ask that you follow the following contribution policies.
2 |
3 | - [Improvements to the Codebase](#improvements-to-the-codebase)
4 | - [Understanding the Code Base](#understanding-the-codebase)
5 | - [Testing](#testing)
6 | - [Style Guidelines & Naming Conventions](#style-guidelines-and-naming-conventions)
7 | - [Creating a Pull Request](#creating-a-pull-request)
8 | - [Code Reviews](#code-reviews)
9 |
10 |
11 | ## Improvements to the Codebase
12 |
13 | We welcome direct contributions to the rest code base. Thank you!
14 |
15 | ### Development Environment ###
16 |
17 | #### Install and Run Locally ####
18 |
19 | ##### Supported Versions #####
20 |
21 | - Go version 1.14, 1.15 or 1.16
22 |
23 | ##### Initial setup: #####
24 |
25 | ```bash
26 | git clone https://github.com/sendgrid/rest.git
27 | cd rest
28 | ```
29 |
30 | ### Environment Variables
31 |
32 | First, get your free SendGrid account [here](https://sendgrid.com/free?source=rest).
33 |
34 | Next, update your environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) if you will test with Swift Mailer.
35 |
36 | ```
37 | echo "export SENDGRID-API-KEY='YOUR-API-KEY'" > sendgrid.env
38 | echo "sendgrid.env" >> .gitignore
39 | source ./sendgrid.env
40 | go run examples/example.go
41 | ```
42 |
43 | ##### Execute: #####
44 |
45 | See the [examples folder](examples) to get started quickly.
46 |
47 |
48 | ## Understanding the Code Base
49 |
50 | **/examples**
51 |
52 | Working examples that demonstrate usage.
53 |
54 | **rest.go**
55 |
56 | There is a struct to hold both the request and response to the API server.
57 |
58 | The main function that does the heavy lifting (and external entry point) is `API`.
59 |
60 |
61 | ## Testing
62 |
63 | All PRs require passing tests before the PR will be reviewed.
64 |
65 | All test files are in [`rest-test.go`](rest_test.go).
66 |
67 | For the purposes of contributing to this repo, please update the [`rest-test.go`](rest_test.go) file with unit tests as you modify the code.
68 |
69 | Run the test:
70 |
71 | ```bash
72 | go test -v
73 | ```
74 |
75 |
76 | ## Style Guidelines & Naming Conventions
77 |
78 | Generally, we follow the style guidelines as suggested by the official language. However, we ask that you conform to the styles that already exist in the library. If you wish to deviate, please explain your reasoning.
79 |
80 | - [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
81 |
82 | Please run your code through:
83 |
84 | - [fmt](https://blog.golang.org/go-fmt-your-code)
85 |
86 | ## Creating a Pull Request
87 |
88 | 1. [Fork](https://help.github.com/fork-a-repo/) the project, clone your fork,
89 | and configure the remotes:
90 |
91 | ```bash
92 | # Clone your fork of the repo into the current directory
93 | git clone https://github.com/sendgrid/rest
94 | # Navigate to the newly cloned directory
95 | cd rest
96 | # Assign the original repo to a remote called "upstream"
97 | git remote add upstream https://github.com/sendgrid/rest
98 | ```
99 |
100 | 2. If you cloned a while ago, get the latest changes from upstream:
101 |
102 | ```bash
103 | git checkout development
104 | git pull upstream development
105 | ```
106 |
107 | 3. Create a new topic branch off the `development` branch to
108 | contain your feature, change, or fix:
109 |
110 | ```bash
111 | git checkout -b
112 | ```
113 |
114 | 4. Commit your changes in logical chunks. Please adhere to these [git commit
115 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
116 | or your code is unlikely to be merged into the main project. Use Git's
117 | [interactive rebase](https://help.github.com/articles/interactive-rebase)
118 | feature to tidy up your commits before making them public.
119 |
120 | 4a. Create tests.
121 |
122 | 4b. Create or update the example code that demonstrates the functionality of this change to the code.
123 |
124 | 5. Locally merge (or rebase) the upstream development branch into your topic branch:
125 |
126 | ```bash
127 | git pull [--rebase] upstream development
128 | ```
129 |
130 | 6. Push your topic branch up to your fork:
131 |
132 | ```bash
133 | git push origin
134 | ```
135 |
136 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
137 | with a clear title and description against the `development` branch. All tests must be passing before we will review the PR.
138 |
139 | ## Code Reviews
140 | If you can, please look at open PRs and review them. Give feedback and help us merge these PRs much faster! If you don't know how, Github has some great [information on how to review a Pull Request](https://help.github.com/articles/about-pull-request-reviews/).
141 |
--------------------------------------------------------------------------------
/FIRST_TIMERS.md:
--------------------------------------------------------------------------------
1 | # How To Contribute to Twilio SendGrid Repositories via GitHub
2 | Contributing to the Twilio SendGrid repositories is easy! All you need to do is find an open issue (see the bottom of this page for a list of repositories containing open issues), fix it and submit a pull request. Once you have submitted your pull request, the team can easily review it before it is merged into the repository.
3 |
4 | To make a pull request, follow these steps:
5 |
6 | 1. Log into GitHub. If you do not already have a GitHub account, you will have to create one in order to submit a change. Click the Sign up link in the upper right-hand corner to create an account. Enter your username, password, and email address. If you are an employee of Twilio SendGrid, please use your full name with your GitHub account and enter Twilio SendGrid as your company so we can easily identify you.
7 |
8 |
9 |
10 | 2. __[Fork](https://help.github.com/fork-a-repo/)__ the [rest](https://github.com/sendgrid/rest) repository:
11 |
12 |
13 |
14 | 3. __Clone__ your fork via the following commands:
15 |
16 | ```bash
17 | # Clone your fork of the repo into the current directory
18 | git clone https://github.com/your_username/rest
19 | # Navigate to the newly cloned directory
20 | cd rest
21 | # Assign the original repo to a remote called "upstream"
22 | git remote add upstream https://github.com/sendgrid/rest
23 | ```
24 |
25 | > Don't forget to replace *your_username* in the URL by your real GitHub username.
26 |
27 | 4. __Create a new topic branch__ (off the main project development branch) to contain your feature, change, or fix:
28 |
29 | ```bash
30 | git checkout -b
31 | ```
32 |
33 | 5. __Commit your changes__ in logical chunks.
34 |
35 | Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) feature to tidy up your commits before making them public. Probably you will also have to create tests (if needed) or create or update the example code that demonstrates the functionality of this change to the code.
36 |
37 | 6. __Locally merge (or rebase)__ the upstream development branch into your topic branch:
38 |
39 | ```bash
40 | git pull [--rebase] upstream main
41 | ```
42 |
43 | 7. __Push__ your topic branch up to your fork:
44 |
45 | ```bash
46 | git push origin
47 | ```
48 |
49 | 8. __[Open a Pull Request](https://help.github.com/articles/creating-a-pull-request/#changing-the-branch-range-and-destination-repository/)__ with a clear title and description against the `main` branch. All tests must be passing before we will review the PR.
50 |
51 | ## Important notice
52 |
53 | Before creating a pull request, make sure that you respect the repository's constraints regarding contributions. You can find them in the [CONTRIBUTING.md](CONTRIBUTING.md) file.
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (C) 2023, Twilio SendGrid, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test install
2 |
3 | install:
4 | go get -t -v ./...
5 |
6 | test: install
7 | go test -race -cover -v ./...
8 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
16 |
17 | # Fixes #
18 |
19 | A short description of what this PR does.
20 |
21 | ### Checklist
22 | - [x] I acknowledge that all my contributions will be made under the project's license
23 | - [ ] I have made a material change to the repo (functionality, testing, spelling, grammar)
24 | - [ ] I have read the [Contribution Guidelines](https://github.com/sendgrid/rest/blob/main/CONTRIBUTING.md) and my PR follows them
25 | - [ ] I have titled the PR appropriately
26 | - [ ] I have updated my branch with the main branch
27 | - [ ] I have added tests that prove my fix is effective or that my feature works
28 | - [ ] I have added the necessary documentation about the functionality in the appropriate .md file
29 | - [ ] I have added inline documentation to the code I modified
30 |
31 | If you have questions, please file a [support ticket](https://support.sendgrid.com).
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://github.com/sendgrid/rest/actions/workflows/test.yml)
4 | [](http://godoc.org/github.com/sendgrid/rest)
5 | [](https://goreportcard.com/report/github.com/sendgrid/rest)
6 | [](https://twitter.com/sendgrid)
7 | [](https://github.com/sendgrid/rest/graphs/contributors)
8 | [](LICENSE)
9 |
10 | **Quickly and easily access any RESTful or RESTful-like API.**
11 |
12 | If you are looking for the SendGrid API client library, please see [this repo](https://github.com/sendgrid/sendgrid-go).
13 |
14 | # Announcements
15 | All updates to this library is documented in our [CHANGELOG](CHANGELOG.md).
16 |
17 | # Table of Contents
18 | - [Installation](#installation)
19 | - [Quick Start](#quick-start)
20 | - [Usage](#usage)
21 | - [How to Contribute](#contribute)
22 | - [About](#about)
23 | - [Support](#support)
24 | - [License](#license)
25 |
26 |
27 | # Installation
28 |
29 | ## Supported Versions
30 |
31 | This library supports the following Go implementations:
32 |
33 | * Go 1.14
34 | * Go 1.15
35 | * Go 1.16
36 | * Go 1.17
37 |
38 | ## Install Package
39 |
40 | ```bash
41 | go get github.com/sendgrid/rest
42 | ```
43 |
44 | ## Setup Environment Variables
45 |
46 | ### Initial Setup
47 |
48 | ```bash
49 | cp .env_sample .env
50 | ```
51 |
52 | ### Environment Variable
53 |
54 | Update the development environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys), for example:
55 |
56 | ```bash
57 | echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env
58 | echo "sendgrid.env" >> .gitignore
59 | source ./sendgrid.env
60 | ```
61 |
62 | ## With Docker
63 |
64 | A Docker image has been created to allow you to get started with `rest` right away.
65 |
66 | ```bash
67 | docker-compose up -d --build
68 |
69 | # Ensure the container is running with 'docker ps'
70 | docker ps
71 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
72 | 40c8d984a620 rest_go "tail -f /dev/null" About a minute ago Up About a minute rest_go_1
73 | ```
74 |
75 | With the container running, you can execute your local `go` scripts using the following:
76 |
77 | ```bash
78 | # docker exec
79 | docker exec rest_go_1 go run docker/example.go
80 | 200
81 | {
82 | "args": {},
83 | "headers": {
84 | "Accept-Encoding": "gzip",
85 | "Connection": "close",
86 | "Host": "httpbin.org",
87 | "User-Agent": "Go-http-client/1.1"
88 | },
89 | "origin": "86.180.177.202",
90 | "url": "https://httpbin.org/get"
91 | }
92 |
93 | map[Access-Control-Allow-Origin:[*] Access-Control-Allow-Credentials:[true] Via:[1.1 vegur] Connection:[keep-alive] Server:[gunicorn/19.9.0] Date:[Tue, 02 Oct 2018 18:20:43 GMT] Content-Type:[application/json] Content-Length:[233]]
94 |
95 | # You can install libraries too, using the same command
96 | # NOTE: Any libraries installed will be removed when the container is stopped.
97 | docker exec rest_go_1 go get github.com/uniplaces/carbon
98 | ```
99 |
100 | Your go files will be executed relative to the root of this directory. So in the example above, to execute the `example.go` file within the `docker` directory, we run `docker exec rest_go_1 go run docker/example.go`. If this file was in the root of this repository (next to README.exe, rest.go etc.), you would run `docker exec rest_go_1 go run my_go_script.go`
101 |
102 |
103 | # Quick Start
104 |
105 | `GET /your/api/{param}/call`
106 |
107 | ```go
108 | package main
109 |
110 | import "github.com/sendgrid/rest"
111 | import "fmt"
112 |
113 | func main() {
114 | const host = "https://api.example.com"
115 | param := "myparam"
116 | endpoint := "/your/api/" + param + "/call"
117 | baseURL := host + endpoint
118 | method := rest.Get
119 | request := rest.Request{
120 | Method: method,
121 | BaseURL: baseURL,
122 | }
123 | response, err := rest.Send(request)
124 | if err != nil {
125 | fmt.Println(err)
126 | } else {
127 | fmt.Println(response.StatusCode)
128 | fmt.Println(response.Body)
129 | fmt.Println(response.Headers)
130 | }
131 | }
132 | ```
133 |
134 | `POST /your/api/{param}/call` with headers, query parameters and a request body.
135 |
136 | ```go
137 | package main
138 |
139 | import "github.com/sendgrid/rest"
140 | import "fmt"
141 |
142 | func main() {
143 | const host = "https://api.example.com"
144 | param := "myparam"
145 | endpoint := "/your/api/" + param + "/call"
146 | baseURL := host + endpoint
147 | Headers := make(map[string]string)
148 | key := os.Getenv("API_KEY")
149 | Headers["Authorization"] = "Bearer " + key
150 | Headers["X-Test"] = "Test"
151 | var Body = []byte(`{"some": 0, "awesome": 1, "data": 3}`)
152 | queryParams := make(map[string]string)
153 | queryParams["hello"] = "0"
154 | queryParams["world"] = "1"
155 | method := rest.Post
156 | request = rest.Request{
157 | Method: method,
158 | BaseURL: baseURL,
159 | Headers: Headers,
160 | QueryParams: queryParams,
161 | Body: Body,
162 | }
163 | response, err := rest.Send(request)
164 | if err != nil {
165 | fmt.Println(err)
166 | } else {
167 | fmt.Println(response.StatusCode)
168 | fmt.Println(response.Body)
169 | fmt.Println(response.Headers)
170 | }
171 | }
172 | ```
173 |
174 |
175 | # Usage
176 |
177 | - [Usage Examples](USAGE.md)
178 |
179 |
180 | # How to Contribute
181 |
182 | We encourage contribution to our projects, please see our [CONTRIBUTING](CONTRIBUTING.md) guide for details.
183 |
184 | Quick links:
185 |
186 | - [Improvements to the Codebase](CONTRIBUTING.md#improvements-to-the-codebase)
187 | - [Code Reviews](CONTRIBUTING.md#code-reviews)
188 |
189 |
190 | # About
191 |
192 | rest is maintained and funded by Twilio SendGrid, Inc. The names and logos for rest are trademarks of Twilio SendGrid, Inc.
193 |
194 |
195 | # Support
196 |
197 | If you need help using SendGrid, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com).
198 |
199 |
200 | # License
201 | [The MIT License (MIT)](LICENSE)
202 |
--------------------------------------------------------------------------------
/TROUBLESHOOTING.md:
--------------------------------------------------------------------------------
1 | ## Table of Contents
2 |
3 | * [Viewing the Request Body](#request-body)
4 |
5 |
6 |
7 | ## Viewing the Request Body
8 |
9 | When debugging or testing, it may be useful to examine the raw request body to compare against the [documented format](https://sendgrid.com/docs/API_Reference/api_v3.html).
10 |
11 | Example Code
12 | ```go
13 | package main
14 |
15 | import "github.com/sendgrid/rest"
16 | import "fmt"
17 |
18 | func main() {
19 | const host = "https://api.example.com"
20 | param := "myparam"
21 | endpoint := "/your/api/" + param + "/call"
22 | baseURL := host + endpoint
23 | Headers := make(map[string]string)
24 | key := os.Getenv("API_KEY")
25 | Headers["Authorization"] = "Bearer " + key
26 | Headers["X-Test"] = "Test"
27 | var Body = []byte(`{"some": 0, "awesome": 1, "data": 3}`)
28 | queryParams := make(map[string]string)
29 | queryParams["hello"] = "0"
30 | queryParams["world"] = "1"
31 | method := rest.Post
32 | request = rest.Request{
33 | Method: method,
34 | BaseURL: baseURL,
35 | Headers: Headers,
36 | QueryParams: queryParams,
37 | Body: Body,
38 | }
39 | response, err := rest.API(request)
40 | if err != nil {
41 | fmt.Println(err)
42 | } else {
43 | fmt.Println(response.StatusCode)
44 | fmt.Println(response.Body)
45 | fmt.Println(response.Headers)
46 | }
47 | }
48 | ```
49 |
50 | You can do this right before you call
51 | `response, err := rest.API(request)` like so:
52 |
53 | ```go
54 | fmt.Printf("Request Body: %v \n", string(request.Body))
55 |
56 | req, e := BuildRequestObject(request)
57 | requestDump, err := httputil.DumpRequest(req, true)
58 | if err != nil {
59 | t.Errorf("Error : %v", err)
60 | }
61 | fmt.Printf("Request : %v \n", string(requestDump))
62 | ```
--------------------------------------------------------------------------------
/USAGE.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | Usage examples for SendGrid REST library
4 |
5 | ## Initialization
6 |
7 | ```go
8 | package main
9 |
10 | import (
11 | "encoding/json"
12 | "fmt"
13 | "os"
14 |
15 | "github.com/sendgrid/rest"
16 | )
17 |
18 | // Build the URL
19 | const host = "https://api.sendgrid.com"
20 | endpoint := "/v3/api_keys"
21 | baseURL := host + endpoint
22 |
23 | // Build the request headers
24 | key := os.Getenv("SENDGRID_API_KEY")
25 | Headers := make(map[string]string)
26 | Headers["Authorization"] = "Bearer " + key
27 | ```
28 |
29 | ## Table of Contents
30 |
31 | - [GET](#get)
32 | - [DELETE](#delete)
33 | - [POST](#post)
34 | - [PUT](#put)
35 | - [PATCH](#patch)
36 |
37 |
38 | ## GET
39 |
40 | #### GET Single
41 |
42 | ```go
43 | method = rest.Get
44 |
45 | // Make the API call
46 | request = rest.Request{
47 | Method: method,
48 | BaseURL: baseURL + "/" + apiKey,
49 | Headers: Headers,
50 | }
51 | response, err = rest.API(request)
52 | if err != nil {
53 | fmt.Println(err)
54 | } else {
55 | fmt.Println(response.StatusCode)
56 | fmt.Println(response.Body)
57 | fmt.Println(response.Headers)
58 | }
59 | ```
60 |
61 | #### GET Collection
62 |
63 | ```go
64 | method := rest.Get
65 |
66 | // Build the query parameters
67 | queryParams := make(map[string]string)
68 | queryParams["limit"] = "100"
69 | queryParams["offset"] = "0"
70 |
71 | // Make the API call
72 | request := rest.Request{
73 | Method: method,
74 | BaseURL: baseURL,
75 | Headers: Headers,
76 | QueryParams: queryParams,
77 | }
78 | response, err := rest.API(request)
79 | if err != nil {
80 | fmt.Println(err)
81 | } else {
82 | fmt.Println(response.StatusCode)
83 | fmt.Println(response.Body)
84 | fmt.Println(response.Headers)
85 | }
86 | ```
87 |
88 | ## DELETE
89 |
90 | ```go
91 | method = rest.Delete
92 |
93 | // Make the API call
94 | request = rest.Request{
95 | Method: method,
96 | BaseURL: baseURL + "/" + apiKey,
97 | Headers: Headers,
98 | QueryParams: queryParams,
99 | }
100 | response, err = rest.API(request)
101 | if err != nil {
102 | fmt.Println(err)
103 | } else {
104 | fmt.Println(response.StatusCode)
105 | fmt.Println(response.Headers)
106 | }
107 | ```
108 |
109 |
110 | ## POST
111 |
112 | ```go
113 | method = rest.Post
114 |
115 | // Build the request body
116 | var Body = []byte(`{
117 | "name": "My API Key",
118 | "scopes": [
119 | "mail.send",
120 | "alerts.create",
121 | "alerts.read"
122 | ]
123 | }`)
124 |
125 | // Make the API call
126 | request = rest.Request{
127 | Method: method,
128 | BaseURL: baseURL,
129 | Headers: Headers,
130 | QueryParams: queryParams,
131 | Body: Body,
132 | }
133 | response, err = rest.API(request)
134 | if err != nil {
135 | fmt.Println(err)
136 | } else {
137 | fmt.Println(response.StatusCode)
138 | fmt.Println(response.Body)
139 | fmt.Println(response.Headers)
140 | }
141 |
142 | // Get a particular return value.
143 | // Note that you can unmarshall into a struct if
144 | // you know the JSON structure in advance.
145 | b := []byte(response.Body)
146 | var f interface{}
147 | err = json.Unmarshal(b, &f)
148 | if err != nil {
149 | fmt.Println(err)
150 | }
151 | m := f.(map[string]interface{})
152 | apiKey := m["api_key_id"].(string)
153 | ```
154 |
155 | ## PUT
156 |
157 | ```go
158 | method = rest.Put
159 |
160 | // Build the request body
161 | Body = []byte(`{
162 | "name": "A New Hope",
163 | "scopes": [
164 | "user.profile.read",
165 | "user.profile.update"
166 | ]
167 | }`)
168 |
169 | // Make the API call
170 | request = rest.Request{
171 | Method: method,
172 | BaseURL: baseURL + "/" + apiKey,
173 | Headers: Headers,
174 | Body: Body,
175 | }
176 | response, err = rest.API(request)
177 | if err != nil {
178 | fmt.Println(err)
179 | } else {
180 | fmt.Println(response.StatusCode)
181 | fmt.Println(response.Body)
182 | fmt.Println(response.Headers)
183 | }
184 | ```
185 |
186 | ## PATCH
187 |
188 | ```go
189 | method = rest.Patch
190 |
191 | // Build the request body
192 | Body = []byte(`{
193 | "name": "A New Hope"
194 | }`)
195 |
196 | // Make the API call
197 | request = rest.Request{
198 | Method: method,
199 | BaseURL: baseURL + "/" + apiKey,
200 | Headers: Headers,
201 | Body: Body,
202 | }
203 | response, err = rest.API(request)
204 | if err != nil {
205 | fmt.Println(err)
206 | } else {
207 | fmt.Println(response.StatusCode)
208 | fmt.Println(response.Body)
209 | fmt.Println(response.Headers)
210 | }
211 | ```
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | go:
4 | build: ./docker/
5 | volumes:
6 | - ./:/app
7 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.11
2 |
3 | VOLUME ["/app"]
4 | WORKDIR /app
5 |
6 | # Get sendgrid-go
7 | RUN mkdir -p /go/src/github.com/sendgrid && \
8 | cd /go/src/github.com/sendgrid && \
9 | git clone https://www.github.com/sendgrid/rest && \
10 | cd rest && \
11 | go get
12 |
13 | ENTRYPOINT ["tail", "-f", "/dev/null"]
14 |
--------------------------------------------------------------------------------
/docker/example.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "github.com/sendgrid/rest"
4 | import "fmt"
5 |
6 | func main() {
7 | const host = "https://httpbin.org"
8 | param := "get"
9 | endpoint := "/" + param
10 | baseURL := host + endpoint
11 | method := rest.Get
12 | request := rest.Request{
13 | Method: method,
14 | BaseURL: baseURL,
15 | }
16 | response, err := rest.Send(request)
17 | if err != nil {
18 | fmt.Println(err)
19 | } else {
20 | fmt.Println(response.StatusCode)
21 | fmt.Println(response.Body)
22 | fmt.Println(response.Headers)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/example.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/sendgrid/rest"
9 | )
10 |
11 | func main() {
12 |
13 | // Build the URL
14 | const host = "https://api.sendgrid.com"
15 | endpoint := "/v3/api_keys"
16 | baseURL := host + endpoint
17 |
18 | // Build the request headers
19 | key := os.Getenv("SENDGRID_API_KEY")
20 | Headers := make(map[string]string)
21 | Headers["Authorization"] = "Bearer " + key
22 |
23 | // GET Collection
24 | method := rest.Get
25 |
26 | // Build the query parameters
27 | queryParams := make(map[string]string)
28 | queryParams["limit"] = "100"
29 | queryParams["offset"] = "0"
30 |
31 | // Make the API call
32 | request := rest.Request{
33 | Method: method,
34 | BaseURL: baseURL,
35 | Headers: Headers,
36 | QueryParams: queryParams,
37 | }
38 | response, err := rest.Send(request)
39 | if err != nil {
40 | fmt.Println(err)
41 | } else {
42 | fmt.Println(response.StatusCode)
43 | fmt.Println(response.Body)
44 | fmt.Println(response.Headers)
45 | }
46 |
47 | // POST
48 | method = rest.Post
49 |
50 | var Body = []byte(` {
51 | "name": "My API Key",
52 | "scopes": [
53 | "mail.send",
54 | "alerts.create",
55 | "alerts.read"
56 | ]
57 | }`)
58 | request = rest.Request{
59 | Method: method,
60 | BaseURL: baseURL,
61 | Headers: Headers,
62 | QueryParams: queryParams,
63 | Body: Body,
64 | }
65 | response, err = rest.Send(request)
66 | if err != nil {
67 | fmt.Println(err)
68 | } else {
69 | fmt.Println(response.StatusCode)
70 | fmt.Println(response.Body)
71 | fmt.Println(response.Headers)
72 | }
73 |
74 | // Get a particular return value.
75 | // Note that you can unmarshall into a struct if
76 | // you know the JSON structure in advance.
77 | b := []byte(response.Body)
78 | var f interface{}
79 | err = json.Unmarshal(b, &f)
80 | if err != nil {
81 | fmt.Println(err)
82 | }
83 | m := f.(map[string]interface{})
84 | apiKey := m["api_key_id"].(string)
85 |
86 | // GET Single
87 | method = rest.Get
88 |
89 | // Make the API call
90 | request = rest.Request{
91 | Method: method,
92 | BaseURL: baseURL + "/" + apiKey,
93 | Headers: Headers,
94 | }
95 | response, err = rest.Send(request)
96 | if err != nil {
97 | fmt.Println(err)
98 | } else {
99 | fmt.Println(response.StatusCode)
100 | fmt.Println(response.Body)
101 | fmt.Println(response.Headers)
102 | }
103 |
104 | // PATCH
105 | method = rest.Patch
106 |
107 | Body = []byte(`{
108 | "name": "A New Hope"
109 | }`)
110 | request = rest.Request{
111 | Method: method,
112 | BaseURL: baseURL + "/" + apiKey,
113 | Headers: Headers,
114 | Body: Body,
115 | }
116 | response, err = rest.Send(request)
117 | if err != nil {
118 | fmt.Println(err)
119 | } else {
120 | fmt.Println(response.StatusCode)
121 | fmt.Println(response.Body)
122 | fmt.Println(response.Headers)
123 | }
124 |
125 | // PUT
126 | method = rest.Put
127 |
128 | Body = []byte(`{
129 | "name": "A New Hope",
130 | "scopes": [
131 | "user.profile.read",
132 | "user.profile.update"
133 | ]
134 | }`)
135 | request = rest.Request{
136 | Method: method,
137 | BaseURL: baseURL + "/" + apiKey,
138 | Headers: Headers,
139 | Body: Body,
140 | }
141 | response, err = rest.Send(request)
142 | if err != nil {
143 | fmt.Println(err)
144 | } else {
145 | fmt.Println(response.StatusCode)
146 | fmt.Println(response.Body)
147 | fmt.Println(response.Headers)
148 | }
149 |
150 | // DELETE
151 | method = rest.Delete
152 |
153 | request = rest.Request{
154 | Method: method,
155 | BaseURL: baseURL + "/" + apiKey,
156 | Headers: Headers,
157 | QueryParams: queryParams,
158 | }
159 | response, err = rest.Send(request)
160 | if err != nil {
161 | fmt.Println(err)
162 | } else {
163 | fmt.Println(response.StatusCode)
164 | fmt.Println(response.Headers)
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/rest.go:
--------------------------------------------------------------------------------
1 | // Package rest allows for quick and easy access any REST or REST-like API.
2 | package rest
3 |
4 | import (
5 | "bytes"
6 | "context"
7 | "io/ioutil"
8 | "net/http"
9 | "net/url"
10 | )
11 |
12 | // Version represents the current version of the rest library
13 | const Version = "2.6.9"
14 |
15 | // Method contains the supported HTTP verbs.
16 | type Method string
17 |
18 | // Supported HTTP verbs.
19 | const (
20 | Get Method = "GET"
21 | Post Method = "POST"
22 | Put Method = "PUT"
23 | Patch Method = "PATCH"
24 | Delete Method = "DELETE"
25 | )
26 |
27 | // Request holds the request to an API Call.
28 | type Request struct {
29 | Method Method
30 | BaseURL string // e.g. https://api.sendgrid.com
31 | Headers map[string]string
32 | QueryParams map[string]string
33 | Body []byte
34 | }
35 |
36 | // RestError is a struct for an error handling.
37 | type RestError struct {
38 | Response *Response
39 | }
40 |
41 | // Error is the implementation of the error interface.
42 | func (e *RestError) Error() string {
43 | return e.Response.Body
44 | }
45 |
46 | // DefaultClient is used if no custom HTTP client is defined
47 | var DefaultClient = &Client{HTTPClient: &http.Client{}}
48 |
49 | // Client allows modification of client headers, redirect policy
50 | // and other settings
51 | // See https://golang.org/pkg/net/http
52 | type Client struct {
53 | HTTPClient *http.Client
54 | }
55 |
56 | // Response holds the response from an API call.
57 | type Response struct {
58 | StatusCode int // e.g. 200
59 | Body string // e.g. {"result: success"}
60 | Headers map[string][]string // e.g. map[X-Ratelimit-Limit:[600]]
61 | }
62 |
63 | // AddQueryParameters adds query parameters to the URL.
64 | func AddQueryParameters(baseURL string, queryParams map[string]string) string {
65 | baseURL += "?"
66 | params := url.Values{}
67 | for key, value := range queryParams {
68 | params.Add(key, value)
69 | }
70 | return baseURL + params.Encode()
71 | }
72 |
73 | // BuildRequestObject creates the HTTP request object.
74 | func BuildRequestObject(request Request) (*http.Request, error) {
75 | // Add any query parameters to the URL.
76 | if len(request.QueryParams) != 0 {
77 | request.BaseURL = AddQueryParameters(request.BaseURL, request.QueryParams)
78 | }
79 | req, err := http.NewRequest(string(request.Method), request.BaseURL, bytes.NewBuffer(request.Body))
80 | if err != nil {
81 | return req, err
82 | }
83 | for key, value := range request.Headers {
84 | req.Header.Set(key, value)
85 | }
86 | _, exists := req.Header["Content-Type"]
87 | if len(request.Body) > 0 && !exists {
88 | req.Header.Set("Content-Type", "application/json")
89 | }
90 | return req, err
91 | }
92 |
93 | // MakeRequest makes the API call.
94 | func MakeRequest(req *http.Request) (*http.Response, error) {
95 | return DefaultClient.HTTPClient.Do(req)
96 | }
97 |
98 | // BuildResponse builds the response struct.
99 | func BuildResponse(res *http.Response) (*Response, error) {
100 | body, err := ioutil.ReadAll(res.Body)
101 | response := Response{
102 | StatusCode: res.StatusCode,
103 | Body: string(body),
104 | Headers: res.Header,
105 | }
106 | res.Body.Close() // nolint
107 | return &response, err
108 | }
109 |
110 | // Deprecated: API supports old implementation
111 | func API(request Request) (*Response, error) {
112 | return Send(request)
113 | }
114 |
115 | // Send uses the DefaultClient to send your request
116 | func Send(request Request) (*Response, error) {
117 | return SendWithContext(context.Background(), request)
118 | }
119 |
120 | // SendWithContext uses the DefaultClient to send your request with the provided context.
121 | func SendWithContext(ctx context.Context, request Request) (*Response, error) {
122 | return DefaultClient.SendWithContext(ctx, request)
123 | }
124 |
125 | // The following functions enable the ability to define a
126 | // custom HTTP Client
127 |
128 | // MakeRequest makes the API call.
129 | func (c *Client) MakeRequest(req *http.Request) (*http.Response, error) {
130 | return c.HTTPClient.Do(req)
131 | }
132 |
133 | // Deprecated: API supports old implementation
134 | func (c *Client) API(request Request) (*Response, error) {
135 | return c.Send(request)
136 | }
137 |
138 | // Send will build your request, make the request, and build your response.
139 | func (c *Client) Send(request Request) (*Response, error) {
140 | return c.SendWithContext(context.Background(), request)
141 | }
142 |
143 | // SendWithContext will build your request passing in the provided context, make the request, and build your response.
144 | func (c *Client) SendWithContext(ctx context.Context, request Request) (*Response, error) {
145 | // Build the HTTP request object.
146 | req, err := BuildRequestObject(request)
147 | if err != nil {
148 | return nil, err
149 | }
150 | // Pass in the user provided context
151 | req = req.WithContext(ctx)
152 |
153 | // Build the HTTP client and make the request.
154 | res, err := c.MakeRequest(req)
155 | if err != nil {
156 | return nil, err
157 | }
158 |
159 | // Build Response object.
160 | return BuildResponse(res)
161 | }
162 |
--------------------------------------------------------------------------------
/rest_test.go:
--------------------------------------------------------------------------------
1 | package rest
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "net/http/httptest"
9 | "net/http/httputil"
10 | "os"
11 | "regexp"
12 | "strings"
13 | "testing"
14 | "time"
15 |
16 | "golang.org/x/net/context"
17 | )
18 |
19 | func TestBuildURL(t *testing.T) {
20 | t.Parallel()
21 | host := "http://api.test.com"
22 | queryParams := make(map[string]string)
23 | queryParams["test"] = "1"
24 | queryParams["test2"] = "2"
25 | testURL := AddQueryParameters(host, queryParams)
26 | if testURL != "http://api.test.com?test=1&test2=2" {
27 | t.Error("Bad BuildURL result")
28 | }
29 | }
30 |
31 | func TestBuildRequest(t *testing.T) {
32 | t.Parallel()
33 | method := Get
34 | baseURL := "http://api.test.com"
35 | key := "API_KEY"
36 | Headers := make(map[string]string)
37 | Headers["Content-Type"] = "application/json"
38 | Headers["Authorization"] = "Bearer " + key
39 | queryParams := make(map[string]string)
40 | queryParams["test"] = "1"
41 | queryParams["test2"] = "2"
42 | request := Request{
43 | Method: method,
44 | BaseURL: baseURL,
45 | Headers: Headers,
46 | QueryParams: queryParams,
47 | }
48 | req, e := BuildRequestObject(request)
49 | if e != nil {
50 | t.Errorf("Rest failed to BuildRequest. Returned error: %v", e)
51 | }
52 | if req == nil {
53 | t.Errorf("Failed to BuildRequest.")
54 | }
55 |
56 | //Start PrintRequest
57 | requestDump, err := httputil.DumpRequest(req, true)
58 | if err != nil {
59 | t.Errorf("Error : %v", err)
60 | }
61 | fmt.Println("Request : ", string(requestDump))
62 | //End Print Request
63 | }
64 |
65 | func TestBuildBadRequest(t *testing.T) {
66 | t.Parallel()
67 | request := Request{
68 | Method: Method("@"),
69 | }
70 | req, e := BuildRequestObject(request)
71 | if e == nil {
72 | t.Errorf("Expected an error for a bad HTTP Method")
73 | }
74 | if req != nil {
75 | t.Errorf("If there's an error there shouldn't be a Request.")
76 | }
77 | }
78 |
79 | func TestBuildBadAPI(t *testing.T) {
80 | t.Parallel()
81 | request := Request{
82 | Method: Method("@"),
83 | }
84 | res, e := API(request)
85 | if e == nil {
86 | t.Errorf("Expected an error for a bad HTTP Method")
87 | }
88 | if res != nil {
89 | t.Errorf("If there's an error there shouldn't be a Response.")
90 | }
91 | }
92 |
93 | func TestBuildResponse(t *testing.T) {
94 | t.Parallel()
95 | fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
96 | fmt.Fprintln(w, "{\"message\": \"success\"}")
97 | }))
98 | defer fakeServer.Close()
99 | baseURL := fakeServer.URL
100 | method := Get
101 | request := Request{
102 | Method: method,
103 | BaseURL: baseURL,
104 | }
105 | req, e := BuildRequestObject(request)
106 | if e != nil {
107 | t.Error("Failed to BuildRequestObject", e)
108 | }
109 | res, e := MakeRequest(req)
110 | if e != nil {
111 | t.Error("Failed to MakeRequest", e)
112 | }
113 | response, e := BuildResponse(res)
114 | if response.StatusCode != 200 {
115 | t.Error("Invalid status code in BuildResponse")
116 | }
117 | if len(response.Body) == 0 {
118 | t.Error("Invalid response body in BuildResponse")
119 | }
120 | if len(response.Headers) == 0 {
121 | t.Error("Invalid response headers in BuildResponse")
122 | }
123 | if e != nil {
124 | t.Errorf("Rest failed to make a valid API request. Returned error: %v", e)
125 | }
126 |
127 | //Start Print Request
128 | requestDump, err := httputil.DumpRequest(req, true)
129 | if err != nil {
130 | t.Errorf("Error : %v", err)
131 | }
132 | fmt.Println("Request :", string(requestDump))
133 | //End Print Request
134 |
135 | }
136 |
137 | type panicResponse struct{}
138 |
139 | func (*panicResponse) Read([]byte) (n int, err error) {
140 | return 0, errors.New("test error")
141 | }
142 |
143 | func (*panicResponse) Close() error {
144 | return nil
145 | }
146 |
147 | func TestBuildBadResponse(t *testing.T) {
148 | t.Parallel()
149 | res := &http.Response{
150 | Body: new(panicResponse),
151 | }
152 | _, e := BuildResponse(res)
153 | if e == nil {
154 | t.Errorf("This was a bad response and error should be returned")
155 | }
156 | }
157 |
158 | func TestRest(t *testing.T) {
159 | t.Parallel()
160 | testingAPI(t, Send)
161 | testingAPI(t, API)
162 | }
163 |
164 | func testingAPI(t *testing.T, fn func(request Request) (*Response, error)) {
165 | fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
166 | fmt.Fprintln(w, "{\"message\": \"success\"}")
167 | }))
168 | defer fakeServer.Close()
169 |
170 | host := fakeServer.URL
171 | endpoint := "/test_endpoint"
172 | baseURL := host + endpoint
173 | key := "API_KEY"
174 | Headers := make(map[string]string)
175 | Headers["Content-Type"] = "application/json"
176 | Headers["Authorization"] = "Bearer " + key
177 | method := Get
178 | queryParams := make(map[string]string)
179 | queryParams["test"] = "1"
180 | queryParams["test2"] = "2"
181 | request := Request{
182 | Method: method,
183 | BaseURL: baseURL,
184 | Headers: Headers,
185 | QueryParams: queryParams,
186 | }
187 |
188 | //Start Print Request
189 | req, e := BuildRequestObject(request)
190 | if e != nil {
191 | t.Errorf("Error during BuildRequestObject: %v", e)
192 | }
193 | requestDump, err := httputil.DumpRequest(req, true)
194 | if err != nil {
195 | t.Errorf("Error : %v", err)
196 | }
197 | fmt.Println("Request :", string(requestDump))
198 | //End Print Request
199 |
200 | response, e := fn(request)
201 |
202 | if response.StatusCode != 200 {
203 | t.Error("Invalid status code")
204 | }
205 | if len(response.Body) == 0 {
206 | t.Error("Invalid response body")
207 | }
208 | if len(response.Headers) == 0 {
209 | t.Error("Invalid response headers")
210 | }
211 | if e != nil {
212 | t.Errorf("Rest failed to make a valid API request. Returned error: %v", e)
213 | }
214 | }
215 |
216 | func TestDefaultContentTypeWithBody(t *testing.T) {
217 | t.Parallel()
218 | host := "http://localhost"
219 | method := Get
220 | request := Request{
221 | Method: method,
222 | BaseURL: host,
223 | Body: []byte("Hello World"),
224 | }
225 |
226 | response, _ := BuildRequestObject(request)
227 | if response.Header.Get("Content-Type") != "application/json" {
228 | t.Error("Content-Type not set to the correct default value when a body is set.")
229 | }
230 |
231 | //Start Print Request
232 | fmt.Println("Request Body: ", string(request.Body))
233 |
234 | requestDump, err := httputil.DumpRequest(response, true)
235 | if err != nil {
236 | t.Errorf("Error : %v", err)
237 | }
238 | fmt.Println("Request :", string(requestDump))
239 | //End Print Request
240 | }
241 |
242 | func TestCustomContentType(t *testing.T) {
243 | t.Parallel()
244 | host := "http://localhost"
245 | Headers := make(map[string]string)
246 | Headers["Content-Type"] = "custom"
247 | method := Get
248 | request := Request{
249 | Method: method,
250 | BaseURL: host,
251 | Headers: Headers,
252 | Body: []byte("Hello World"),
253 | }
254 | response, _ := BuildRequestObject(request)
255 | if response.Header.Get("Content-Type") != "custom" {
256 | t.Error("Content-Type not modified correctly")
257 | }
258 |
259 | //Start Print Request
260 | requestDump, err := httputil.DumpRequest(response, true)
261 | if err != nil {
262 | t.Errorf("Error : %v", err)
263 | }
264 | fmt.Println("Request :", string(requestDump))
265 | //End Print Request
266 | }
267 |
268 | func TestCustomHTTPClient(t *testing.T) {
269 | t.Parallel()
270 | fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
271 | time.Sleep(time.Millisecond * 20)
272 | fmt.Fprintln(w, "{\"message\": \"success\"}")
273 | }))
274 | defer fakeServer.Close()
275 | host := fakeServer.URL
276 | endpoint := "/test_endpoint"
277 | baseURL := host + endpoint
278 | method := Get
279 | request := Request{
280 | Method: method,
281 | BaseURL: baseURL,
282 | }
283 |
284 | customClient := &Client{&http.Client{Timeout: time.Millisecond * 10}}
285 | _, err := customClient.Send(request)
286 | if err == nil {
287 | t.Error("A timeout did not trigger as expected")
288 | }
289 | if !strings.Contains(err.Error(), "Client.Timeout exceeded while awaiting headers") {
290 | t.Error("We did not receive the Timeout error")
291 | }
292 | }
293 |
294 | func TestRestError(t *testing.T) {
295 | t.Parallel()
296 | headers := make(map[string][]string)
297 | headers["Content-Type"] = []string{"application/json"}
298 |
299 | response := &Response{
300 | StatusCode: 400,
301 | Body: `{"result": "failure"}`,
302 | Headers: headers,
303 | }
304 |
305 | var err error = &RestError{Response: response}
306 |
307 | if err.Error() != `{"result": "failure"}` {
308 | t.Error("Invalid error message.")
309 | }
310 | }
311 |
312 | func TestRepoFiles(t *testing.T) {
313 | files := []string{".env_sample", ".gitignore", ".github/workflows/test.yml", "CHANGELOG.md",
314 | "CODE_OF_CONDUCT.md", "CONTRIBUTING.md",
315 | "LICENSE", "PULL_REQUEST_TEMPLATE.md", "README.md",
316 | "TROUBLESHOOTING.md", "USAGE.md"}
317 |
318 | for _, file := range files {
319 | if _, err := os.Stat(file); os.IsNotExist(err) {
320 | t.Errorf("Repo file does not exist: %v", file)
321 | }
322 | }
323 | }
324 |
325 | func TestLicenseYear(t *testing.T) {
326 | t.Parallel()
327 | dat, err := ioutil.ReadFile("LICENSE")
328 |
329 | currentYear := time.Now().Year()
330 | r := fmt.Sprintf("%d", currentYear)
331 | match, _ := regexp.MatchString(r, string(dat))
332 |
333 | if err != nil {
334 | t.Error("License File Not Found")
335 | }
336 | if !match {
337 | t.Error("Incorrect Year in License Copyright")
338 | }
339 | }
340 |
341 | func TestSendWithContext(t *testing.T) {
342 | t.Parallel()
343 | fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
344 | time.Sleep(time.Millisecond * 20)
345 | fmt.Fprintln(w, "{\"message\": \"success\"}")
346 | }))
347 | defer fakeServer.Close()
348 | host := fakeServer.URL
349 | endpoint := "/test_endpoint"
350 | baseURL := host + endpoint
351 | method := Get
352 | request := Request{
353 | Method: method,
354 | BaseURL: baseURL,
355 | }
356 |
357 | ctx, _ := context.WithTimeout(context.Background(), time.Millisecond*10)
358 | _, err := SendWithContext(ctx, request)
359 | if err == nil {
360 | t.Error("A timeout did not trigger as expected")
361 | }
362 | if !strings.Contains(err.Error(), "context deadline exceeded") {
363 | t.Error("We did not receive the Timeout error")
364 | }
365 | }
366 |
--------------------------------------------------------------------------------
/static/img/github-fork.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendgrid/rest/0bdc741d2dbf5ec7ca4f9fc9dbc04be13d919e83/static/img/github-fork.png
--------------------------------------------------------------------------------
/static/img/github-sign-up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendgrid/rest/0bdc741d2dbf5ec7ca4f9fc9dbc04be13d919e83/static/img/github-sign-up.png
--------------------------------------------------------------------------------
/twilio_sendgrid_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sendgrid/rest/0bdc741d2dbf5ec7ca4f9fc9dbc04be13d919e83/twilio_sendgrid_logo.png
--------------------------------------------------------------------------------
/use-cases/README.md:
--------------------------------------------------------------------------------
1 | This document provides examples for specific SendGrid v3 API use cases. Please [open an issue](https://github.com/sendgrid/rest/issues) or make a pull request for any email use cases you would like us to document here. Thank you!
2 |
3 | # Email Use Cases
4 |
5 | # Non-mail Use Cases
6 |
--------------------------------------------------------------------------------