├── .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 | ![SendGrid Logo](twilio_sendgrid_logo.png) 2 | 3 | [![BuildStatus](https://github.com/sendgrid/rest/actions/workflows/test.yml/badge.svg)](https://github.com/sendgrid/rest/actions/workflows/test.yml) 4 | [![GoDoc](https://godoc.org/github.com/sendgrid/rest?status.png)](http://godoc.org/github.com/sendgrid/rest) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/sendgrid/rest)](https://goreportcard.com/report/github.com/sendgrid/rest) 6 | [![Twitter Follow](https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow)](https://twitter.com/sendgrid) 7 | [![GitHub contributors](https://img.shields.io/github/contributors/sendgrid/rest.svg)](https://github.com/sendgrid/rest/graphs/contributors) 8 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](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 | --------------------------------------------------------------------------------