Skip to content

Commit

Permalink
Add better documentation, more errors, default timeout (#426)
Browse files Browse the repository at this point in the history
This PR adds an upgrade doc, updates the readme, adds more error classes and a default timeout.

Co-authored-by: Lrubin <Lrubin@nylas.com>
  • Loading branch information
mrashed-dev and Lrubin authored Aug 23, 2023
1 parent c8513c4 commit 7fc7788
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 37 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

### 6.0.0-beta.2 / TBD
* Added additional error classes
* Set default timeout to 30 seconds

### 6.0.0-beta.1 / 2023-08-16
* **BREAKING CHANGE**: Ruby SDK v6 supports the Nylas API v3 exclusively, dropping support for any endpoints that are not available in v3.
* **BREAKING CHANGE**: Officially support minimum Ruby v3
Expand Down
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@

# Nylas Ruby SDK

[![Gem (including prereleases)](https://img.shields.io/gem/v/nylas?include_prereleases)](https://rubygems.org/gems/nylas)
[![codecov](https://codecov.io/gh/nylas/nylas-ruby/branch/main/graph/badge.svg?token=IKH0YMH4KA)](https://codecov.io/gh/nylas/nylas-ruby)

This is the GitHub repository for the Nylas Ruby SDK. This repo is primarily for anyone who wants to make contributions to the SDK or install it from source. For documentation on how to use this SDK to access the Nylas Email, Calendar, or Contacts APIs, see the official [Ruby SDK Quickstart Guide](https://developer.nylas.com/docs/sdks/ruby/).

The Nylas Communications Platform provides REST APIs for [Email](https://developer.nylas.com/docs/email/), [Calendar](https://developer.nylas.com/docs/calendar/), and [Contacts](https://developer.nylas.com/docs/contacts/), and the Nylas SDK is the quickest way to build your integration using Kotlin or Java.
The Nylas Communications Platform provides REST APIs for [Email](https://developer.nylas.com/docs/email/), [Calendar](https://developer.nylas.com/docs/calendar/), and [Contacts](https://developer.nylas.com/docs/contacts/), and the Nylas SDK is the quickest way to build your integration using Ruby.

Here are some resources to help you get started:

- [Sign up for your free Nylas account](https://dashboard.nylas.com/register)
- [Sign up for the Nylas v3 Beta program to access the v3 Dashboard](https://info.nylas.com/apiv3betasignup.html?utm_source=github&utm_medium=devrel-surfaces&utm_campaign=&utm_content=ruby-sdk-upgrade)
- [Nylas API v3 Quickstart Guide](https://developer.nylas.com/docs/v3-beta/v3-quickstart/)
- [Nylas SDK Reference](https://nylas-ruby-sdk-reference.pages.dev/)
- [Nylas API Reference](https://developer.nylas.com/docs/api/)
- [Nylas API Reference](https://developer.nylas.com/docs/api/v3-beta/)
- [Nylas Samples repo for code samples and example applications](https://github.com/orgs/nylas-samples/repositories?q=&type=all&language=ruby)

If you have a question about the Nylas Communications Platform, [contact Nylas Support](https://support.nylas.com/) for help.

## ⚙️ Install

### Prerequisites

- Ruby 3.0 or above.
- Ruby Frameworks: `rest-client` and `yajl-ruby`.

Expand All @@ -39,7 +42,7 @@ And then execute:
bundle
```

To run scripts that use the Nylas Ruby SDK, install the nylas gem.
To run scripts that use the Nylas Ruby SDK, install the `nylas` gem.

```bash
gem install nylas
Expand All @@ -52,7 +55,7 @@ git clone https://github.com/nylas/nylas-ruby.git && cd nylas-ruby
bundle install
```

### Setup Ruby SDK for Development
### Set up Ruby SDK for Development

Install [RubyGems](https://rubygems.org/pages/download) if you don't already have it:

Expand All @@ -75,7 +78,7 @@ rspec spec

## ⚡️ Usage

To use this SDK, you must first [get a free Nylas account](https://dashboard.nylas.com/register).
To use this SDK, you must first [sign up for the v3 Beta and get a free Nylas account](https://info.nylas.com/apiv3betasignup.html?utm_source=github&utm_medium=devrel-surfaces&utm_campaign=&utm_content=ruby-sdk-upgrade).

Then, follow the Quickstart guide to [set up your first app and get your API keys](https://developer.nylas.com/docs/v3-beta/v3-quickstart/).

Expand Down Expand Up @@ -103,10 +106,16 @@ calendars, _request_ids = nylas.calendars.list(identifier: "GRANT_ID")

Nylas maintains a [reference guide for the Ruby SDK](https://nylas-ruby-sdk-reference.pages.dev/) to help you get familiar with the available methods and classes.

## ✨ Upgrading from 5.x

See [UPGRADE.md](UPGRADE.md) for instructions on upgrading from 5.x to 6.x.

**Note**: The Ruby SDK v6.x is not compatible with the Nylas API earlier than v3-beta. If you are using Nylas v2.7 or earlier, continue using the v5.x Nylas Ruby SDK.

## 💙 Contributing

Please refer to [Contributing](Contributing.md) for information about how to make contributions to this project. We welcome questions, bug reports, and pull requests.

## 📝 License

This project is licensed under the terms of the MIT license. Please refer to [LICENSE](LICENSE.txt) for the full terms.
This project is licensed under the terms of the MIT license. Please refer to [LICENSE](LICENSE.txt) for the full terms.
152 changes: 152 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Upgrading to Nylas Ruby SDK v6.0

The Nylas Ruby SDK has been refactored and large parts of it have been rewritten for the upcoming release of the [Nylas API v3](https://developer.nylas.com/docs/v3-beta/). The goal was to have a product that is intuitive and easier to use. This guide helps you upgrade your code to use the new SDK. The new SDK also includes [documentation for the SDK's methods and usage](https://nylas-ruby-sdk-reference.pages.dev/) so you can easily find the implementation details you need.

Head's up! Nylas API v3 [contains a lot of changes](https://developer.nylas.com/docs/v3-beta/features-and-changes/), and you should familiarize yourself with them before you start upgrading.

⚠️ **Note:** The Nylas Ruby SDK v6.0 is not compatible with Nylas APIs earlier than 3.0 beta. If you are still using an earlier version of the API (such as Nylas v2.7), keep using the Nylas Ruby SDK v5.x until you can upgrade.

## Initial Set up

To upgrade to the new SDK, update your dependencies to use the new version. You do this by using RubyGems to install the new version of the SDK.

**Note:** The minimum Ruby version is now at the oldest supported LTS, Ruby v3.0.

```bash
gem install nylas --pre
```

The first step to using the new SDK is to initialize a new instance of the Nylas SDK. Do this by passing in your API key to the constructor. Notice the syntax change in the example below.

```ruby
require 'nylas'

nylas = Nylas::API.new(
api_key: "NYLAS_API_KEY",
)
```

From here, you can use the `Nylas` instance to make API requests using your API key to access the different resources.

## Making Requests to the Nylas API

You use the `Nylas` instance that you configured earlier to make requests to the Nylas API. The SDK is organized into different resources for each of the Email, Calendar, and Contacts APIs, and each resource has all the available methods to make requests to the API.

For example, to get a list of calendars, you can do so like:

```ruby
require 'nylas'

nylas = Nylas::API.new(
api_key: "NYLAS_API_KEY",
)

events, _request_ids = nylas.events.list(identifier: "grant_id")
```

You might notice that there are some new concepts in the example SDK code above when making requests. These concepts are explained in more detail below.

### Resource Parameters

Each resource takes different parameters. All resources take an "identifier", which is the ID of the account you want to make the request on behalf of. This is usually the Grant ID or the email address of the account. Some resources also take "query parameters" which are mainly used to filter data or pass in additional information.

### Response Objects

The Nylas API returns a JSON response for each request. The SDK parses the JSON response and returns a response hash that contains the data returned from the API, and a string that represents the request ID for the request it responds to. You can use this ID for debugging and troubleshooting.

List request responses include the same items, except the response hash contains an _array_ of the data returned from the API. If there are multiple pages of data, the response hash also contains a `next_cursor` key that includes a token that represents the next page of data. You can extract this token and use it as a query parameter for the next request to get the next page of data. Pagination features are coming soon.

### Error Objects

Like the response objects, Nylas v3 now has standard error objects for all requests (excluding OAuth endpoints, see below). There are two superclass error classes, `AbstractNylasApiError`, used for errors returned by the API, and `AbstractNylasSdkError`, used for errors returned by the SDK.

The `AbstractNylasApiError` includes two subclasses: `NylasOAuthError`, used for API errors that are returned from the OAuth endpoints, and `NylasApiError`, used for any other Nylas API errors.

The SDK extracts the error details from the response and stores them in the error object, along with the request ID and the HTTP status code.

`AbstractNylasSdkError` is used for errors returned by the SDK. Right now there's only one type of error we return, and that's a `NylasSdkTimeoutError` which is thrown when a request times out.

Putting it all together, the following example code shows how to make a request to create a new Event and handle any errors that may occur:

```ruby
require 'nylas'

nylas = Nylas::API.new(
api_key: "NYLAS_APP_KEY",
)

begin
# Build the create event request
create_event_request = nylas.events.create(
identifier: "GRANT_ID",
query_params: {
calendar_id: "CALENDAR_ID", # A calendar ID is required
},
request_body: {
when: {
start_time: 1686765600,
end_time: 1686769200,
}, # A "When" type is required
title: "My Event", # Title is optional
description: "My event description", # Description is optional
location: "My event location", # Location is optional
}
)
rescue Nylas::NylasApiError => e
# Handle the error
puts e.message
puts e.request_id
puts e.status_code
rescue Nylas::NylasSdkTimeoutError => e
# Handle the error
puts e.message
puts e.url
end
```

## Authentication

The SDK's authentication methods reflect [the methods available in the new Nylas API v3](https://developer.nylas.com/docs/developer-guide/v3-authentication/). While you can only create and manage your application's connectors (formerly called integrations) in the dashboard, you can manage almost everything else directly from the SDK. This includes managing grants, redirect URIs, OAuth tokens, and authenticating your users.

There are two main methods to focus on when authenticating users to your application. The first is the `Auth#urlForOAuth2` method, which returns the URL that you redirect your users to in order to authenticate them using Nylas' OAuth 2.0 implementation.

The second is the `Auth#exchangeCodeForToken` method. Use this method to exchange the code Nylas returned from the authentication redirect for an access token from the OAuth provider. Nylas's response to this request includes both the access token, and information about the grant that was created. You don't _need_ to use the `grant_id` to make requests. Instead, you can use the authenticated email address directly as the identifier for the account. If you prefer to use the `grant_id`, you can extract it from the `CodeExchangeResponse` object and use that instead.

The following code shows how to authenticate a user into a Nylas application:

```ruby
require 'nylas'

nylas = Nylas::API.new(
api_key: "NYLAS_APP_KEY",
)

# Build the URL for authentication
auth_url = nylas.auth.url_for_oauth2(
client_id: "CLIENT_ID",
redirect_uri: "REDIRECT_URI",
login_hint: "example@email.com"
)

# Write code here to redirect the user to the url and parse the code
...

# Exchange the code for an access token

code_exchange_response = nylas.auth.exchange_code_for_token({
redirect_uri: "REDIRECT_URI",
client_id: "CLIENT_ID",
client_secret: "CLIENT_SECRET",
code: "CODE"
})

# Now you can either use the email address that was authenticated or the grant ID in the response as the identifier

response_with_email = nylas.calendars.list(
identifier: "example@email.com"
)

response_with_grant = nylas.calendars.list(
identifier: code_exchange_response.grant_id
)
```
10 changes: 5 additions & 5 deletions lib/nylas/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ class Client

# Initializes a client session.
#
# @param api_key [Hash, nil] API key to use for the client session.
# @param api_uri [Hash] Client session's host.
# @param timeout [Hash, nil] Timeout value to use for the client session.
def initialize(api_key: nil,
# @param api_key [String, nil] API key to use for the client session.
# @param api_uri [String] Client session's host.
# @param timeout [String, nil] Timeout value to use for the client session.
def initialize(api_key:,
api_uri: Config::DEFAULT_REGION_URL,
timeout: nil)
@api_key = api_key
@api_uri = api_uri
@timeout = timeout
@timeout = timeout || 30
end

# The application resources for your Nylas application.
Expand Down
44 changes: 33 additions & 11 deletions lib/nylas/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@
module Nylas
Error = Class.new(::StandardError)

class JsonParseError < Error; end
# Base error class for API-related errors.
class AbstractNylasApiError < Error; end

# Base class to inflate the standard errors returned from the Nylas API.
class NylasApiError < Error
# Base error class for SDK-related errors.
class AbstractNylasSdkError < Error; end

# Error class representing a failed parse of a JSON response from the Nylas API.
class JsonParseError < AbstractNylasSdkError; end

# Error class representing a failed response from the Nylas API.
class NylasApiError < AbstractNylasApiError
attr_accessor :type, :request_id, :provider_error, :status_code

# Initializes an error and assigns the given attributes to it.
#
# @param type [Hash] Error type.
# @param message [String] Error message.
# @param status_code [Hash] Error status code.
# @param status_code [Integer] Error status code.
# @param provider_error [String, nil] Provider error.
# @param request_id [Hash, nil] The ID of the request.
def initialize(type, message, status_code, provider_error = nil, request_id = nil)
Expand All @@ -27,7 +34,7 @@ def initialize(type, message, status_code, provider_error = nil, request_id = ni
# Parses the error response.
#
# @param response [Hash] Response from the Nylas API.
# @param status_code [String] Error status code.
# @param status_code [Integer] Error status code.
def self.parse_error_response(response, status_code)
new(
response["type"],
Expand All @@ -38,17 +45,17 @@ def self.parse_error_response(response, status_code)
end
end

# Base class to inflate the standard errors returned from the Nylas OAuth integration.
class NylasOAuthError < Error
# Error class representing a failed response from the Nylas OAuth integration.
class NylasOAuthError < AbstractNylasApiError
attr_accessor :error, :error_description, :error_uri, :error_code, :status_code

# Initializes an error and assigns the given attributes to it.
#
# @param error [Hash] Error type.
# @param error [String] Error type.
# @param error_description [String] Description of the error.
# @param error_uri [Hash] Error URI.
# @param error_code [Hash] Error code.
# @param status_code [Hash] Error status code.
# @param error_uri [String] Error URI.
# @param error_code [String] Error code.
# @param status_code [String] Error status code.
def initialize(error, error_description, error_uri, error_code, status_code)
super(error_description)
self.error = error
Expand All @@ -59,5 +66,20 @@ def initialize(error, error_description, error_uri, error_code, status_code)
end
end

# Error class representing a timeout from the Nylas SDK.
class NylasSdkTimeoutError < AbstractNylasSdkError
attr_accessor :url, :timeout

# Initializes an error and assigns the given attributes to it.
# @param url [String] URL that timed out.
# @param timeout [Integer] Timeout in seconds.
# @return [NylasSdkTimeoutError] The error object.
def initialize(url, timeout)
super("Nylas SDK timed out before receiving a response from the server.")
self.url = url
self.timeout = timeout
end
end

HTTP_SUCCESS_CODES = [200, 201, 202, 302].freeze
end
32 changes: 18 additions & 14 deletions lib/nylas/handler/http_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,36 @@ module HttpClient
# @param method [Symbol] HTTP method for the API call. Either :get, :post, :delete, or :patch.
# @param path [String, nil] Relative path from the API Base. This is the preferred way to execute
# arbitrary or-not-yet-SDK-ified API commands.
# @param timeout [Hash, nil] Timeout value to send with the request.
# @param headers [Hash, {}] Additional HTTP headers to include in the payload.
# @param query [Hash, {}] Hash of names and values to include in the query section of the URI
# fragment.
# @param payload [String, Hash, nil] Body to send with the request.
# @param api_key [Hash, nil] API key to send with the request.
# @param timeout [Hash, nil] Timeout value to send with the request.
# @return [Object] Parsed JSON response from the API.
def execute(method:, path: nil, headers: {}, query: {}, payload: nil, api_key: nil, timeout: nil)
def execute(method:, path:, timeout:, headers: {}, query: {}, payload: nil, api_key: nil)
request = build_request(method: method, path: path, headers: headers,
query: query, payload: payload, api_key: api_key, timeout: timeout)
rest_client_execute(**request) do |response, _request, result|
content_type = nil
begin
rest_client_execute(**request) do |response, _request, result|
content_type = nil

if response.headers && response.headers[:content_type]
content_type = response.headers[:content_type].downcase
end
if response.headers && response.headers[:content_type]
content_type = response.headers[:content_type].downcase
end

begin
response = parse_response(response) if content_type == "application/json"
rescue Nylas::JsonParseError
handle_failed_response(result, response, path)
raise
end

begin
response = parse_response(response) if content_type == "application/json"
rescue Nylas::JsonParseError
handle_failed_response(result, response, path)
raise
return response
end

handle_failed_response(result, response, path)
return response
rescue Timeout::Error => _e
raise Nylas::NylasSdkTimeoutError.new(request.path, timeout)
end
end

Expand Down

0 comments on commit 7fc7788

Please sign in to comment.