From fff4ee37cca3e5c5335a59023a67dd66d5b6a4af Mon Sep 17 00:00:00 2001 From: Vedant Koditkar Date: Sun, 30 May 2021 01:08:03 +0530 Subject: [PATCH 1/7] remove /V1 from base url & added as part of api url --- Src/Notion.Client/Constants.cs | 2 +- Src/Notion.Client/DatabasesClient.cs | 6 +++--- Src/Notion.Client/UsersClient.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Src/Notion.Client/Constants.cs b/Src/Notion.Client/Constants.cs index 3078de60..209e3b91 100644 --- a/Src/Notion.Client/Constants.cs +++ b/Src/Notion.Client/Constants.cs @@ -2,7 +2,7 @@ { internal class Constants { - internal static string BASE_URL = "https://api.notion.com/v1/"; + internal static string BASE_URL = "https://api.notion.com/"; internal static string DEFAULT_NOTION_VERSION = "2021-05-13"; } } diff --git a/Src/Notion.Client/DatabasesClient.cs b/Src/Notion.Client/DatabasesClient.cs index d3bc6f00..5a9ff5cc 100644 --- a/Src/Notion.Client/DatabasesClient.cs +++ b/Src/Notion.Client/DatabasesClient.cs @@ -24,7 +24,7 @@ public async Task RetrieveAsync(string databaseId) { try { - return await _client.GetAsync($"databases/{databaseId}"); + return await _client.GetAsync($"/v1/databases/{databaseId}"); } catch (Exception e) { @@ -44,7 +44,7 @@ public async Task> ListAsync(DatabasesListParameters dat { "page_size", databasesListQueryParmaters?.PageSize } }; - return await _client.GetAsync>("databases", queryParams); + return await _client.GetAsync>("/v1/databases", queryParams); } catch (Exception e) { @@ -58,7 +58,7 @@ public async Task> QueryAsync(string databaseId, DatabasesQu try { var body = (IDatabaseQueryBodyParameters)databasesQueryParameters; - return await _client.PostAsync>($"databases/{databaseId}/query", body); + return await _client.PostAsync>($"/v1/databases/{databaseId}/query", body); } catch (Exception e) { diff --git a/Src/Notion.Client/UsersClient.cs b/Src/Notion.Client/UsersClient.cs index 73bed377..701b1e21 100644 --- a/Src/Notion.Client/UsersClient.cs +++ b/Src/Notion.Client/UsersClient.cs @@ -22,7 +22,7 @@ public async Task RetrieveAsync(string userId) { try { - return await _client.GetAsync($"users/{userId}"); + return await _client.GetAsync($"/v1/users/{userId}"); } catch (Exception e) { @@ -34,7 +34,7 @@ public async Task> ListAsync() { try { - return await _client.GetAsync>("users"); + return await _client.GetAsync>("/v1/users"); } catch (Exception e) { From 0ceb853452c70c1c968bb9651c4506baec7fa4a3 Mon Sep 17 00:00:00 2001 From: Vedant Koditkar Date: Sun, 30 May 2021 01:35:01 +0530 Subject: [PATCH 2/7] Refactor rest client :recycle: - Throws NotionApiException if there is any error while calling API - Allow options to configure header - Use the SendAsync and HttpRequestMessage to form the request --- .../HttpResponseMessageExtensions.cs | 36 +++++++ Src/Notion.Client/NotionApiException.cs | 17 +++ Src/Notion.Client/RestClient.cs | 101 +++++++++++++++--- 3 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 Src/Notion.Client/Extensions/HttpResponseMessageExtensions.cs create mode 100644 Src/Notion.Client/NotionApiException.cs diff --git a/Src/Notion.Client/Extensions/HttpResponseMessageExtensions.cs b/Src/Notion.Client/Extensions/HttpResponseMessageExtensions.cs new file mode 100644 index 00000000..98b1499d --- /dev/null +++ b/Src/Notion.Client/Extensions/HttpResponseMessageExtensions.cs @@ -0,0 +1,36 @@ +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace Notion.Client.Extensions +{ + internal static class HttpResponseMessageExtensions + { + internal static async Task ParseStreamAsync(this HttpResponseMessage response, JsonSerializerSettings serializerSettings = null) + { + using (Stream stream = await response.Content.ReadAsStreamAsync()) + { + using (StreamReader streamReader = new StreamReader(stream)) + { + using (JsonReader jsonReader = new JsonTextReader(streamReader)) + { + + JsonSerializer serializer = null; + + if (serializerSettings == null) + { + serializer = JsonSerializer.CreateDefault(); + } + else + { + serializer = JsonSerializer.Create(serializerSettings); + } + + return serializer.Deserialize(jsonReader); + } + } + } + } + } +} \ No newline at end of file diff --git a/Src/Notion.Client/NotionApiException.cs b/Src/Notion.Client/NotionApiException.cs new file mode 100644 index 00000000..aa8ffa4a --- /dev/null +++ b/Src/Notion.Client/NotionApiException.cs @@ -0,0 +1,17 @@ +using System; +using System.Net; + +namespace Notion.Client +{ + class NotionApiException : Exception + { + public NotionApiException(HttpStatusCode statusCode, string message) : this(statusCode, message, null) + { + } + + public NotionApiException(HttpStatusCode statusCode, string message, Exception innerException) : base(message, innerException) + { + Data.Add("StatusCode", statusCode); + } + } +} \ No newline at end of file diff --git a/Src/Notion.Client/RestClient.cs b/Src/Notion.Client/RestClient.cs index ab536b03..08fb683c 100644 --- a/Src/Notion.Client/RestClient.cs +++ b/Src/Notion.Client/RestClient.cs @@ -3,16 +3,30 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; +using Notion.Client.Extensions; using Notion.Client.http; namespace Notion.Client { public interface IRestClient { - Task GetAsync(string uri, Dictionary queryParams = null); - Task PostAsync(string uri, object body); + Task GetAsync( + string uri, + IDictionary queryParams = null, + IDictionary headers = null, + JsonSerializerSettings serializerSettings = null, + CancellationToken cancellationToken = default); + + Task PostAsync( + string uri, + object body, + IDictionary queryParams = null, + IDictionary headers = null, + JsonSerializerSettings serializerSettings = null, + CancellationToken cancellationToken = default); } public class RestClient : IRestClient @@ -21,7 +35,7 @@ public class RestClient : IRestClient private readonly ClientOptions _options; private readonly List jsonConverters = new List(); - private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings + private readonly JsonSerializerSettings defaultSerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; @@ -41,32 +55,89 @@ private static ClientOptions MergeOptions(ClientOptions options) }; } - public async Task GetAsync(string uri, Dictionary queryParams = null) + public async Task GetAsync( + string uri, + IDictionary queryParams = null, + IDictionary headers = null, + JsonSerializerSettings serializerSettings = null, + CancellationToken cancellationToken = default) { EnsureHttpClient(); - uri = queryParams == null ? uri : QueryHelpers.AddQueryString(uri, queryParams); + string requestUri = queryParams == null ? uri : QueryHelpers.AddQueryString(uri, queryParams); - using (var stream = await _httpClient.GetStreamAsync(uri)) + var response = await SendAsync(requestUri, HttpMethod.Get, headers, cancellationToken: cancellationToken); + + if (response.IsSuccessStatusCode) { - return SerializerHelper.Deserialize(stream, jsonConverters); + return await response.ParseStreamAsync(serializerSettings); } + + var message = !string.IsNullOrWhiteSpace(response.ReasonPhrase) + ? response.ReasonPhrase + : await response.Content.ReadAsStringAsync(); + + throw new NotionApiException(response.StatusCode, message); } - public async Task PostAsync(string uri, object body) + private async Task SendAsync( + string requestUri, + HttpMethod httpMethod, + IDictionary headers = null, + Action attachContent = null, + CancellationToken cancellationToken = default) { - EnsureHttpClient(); + HttpRequestMessage httpRequest = new HttpRequestMessage(httpMethod, requestUri); + httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _options.AuthToken); + httpRequest.Headers.Add("Notion-Version", _options.NotionVersion); + + if (headers != null) + { + AddHeaders(httpRequest, headers); + } + + attachContent?.Invoke(httpRequest); + + return await _httpClient.SendAsync(httpRequest, cancellationToken); + } + + private static void AddHeaders(HttpRequestMessage request, IDictionary headers) + { + foreach (var header in headers) + { + request.Headers.Add(header.Key, header.Value); + } + } - var content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, "application/json"); + public async Task PostAsync( + string uri, + object body, + IDictionary queryParams = null, + IDictionary headers = null, + JsonSerializerSettings serializerSettings = null, + CancellationToken cancellationToken = default) + { + EnsureHttpClient(); - using (var response = await _httpClient.PostAsync(uri, content)) + void AttachContent(HttpRequestMessage httpRequest) { - response.EnsureSuccessStatusCode(); + httpRequest.Content = new StringContent(JsonConvert.SerializeObject(body, defaultSerializerSettings), Encoding.UTF8, "application/json"); + } + + string requestUri = queryParams == null ? uri : QueryHelpers.AddQueryString(uri, queryParams); - var stream = await response.Content.ReadAsStreamAsync(); + var response = await SendAsync(requestUri, HttpMethod.Post, headers, AttachContent, cancellationToken: cancellationToken); - return SerializerHelper.Deserialize(stream, jsonConverters); + if (response.IsSuccessStatusCode) + { + return await response.ParseStreamAsync(serializerSettings); } + + var message = !string.IsNullOrWhiteSpace(response.ReasonPhrase) + ? response.ReasonPhrase + : await response.Content.ReadAsStringAsync(); + + throw new NotionApiException(response.StatusCode, message); } private HttpClient EnsureHttpClient() @@ -75,8 +146,6 @@ private HttpClient EnsureHttpClient() { _httpClient = new HttpClient(); _httpClient.BaseAddress = new Uri(_options.BaseUrl); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _options.AuthToken); - _httpClient.DefaultRequestHeaders.Add("Notion-Version", _options.NotionVersion); } return _httpClient; From 3e5be05359d8c5e6f5f07bc3adc1e8f235eece3c Mon Sep 17 00:00:00 2001 From: Vedant Koditkar Date: Sun, 30 May 2021 01:42:34 +0530 Subject: [PATCH 3/7] Restructure files :truck: --- Src/Notion.Client/{ => Api/Databases}/DatabasesClient.cs | 0 Src/Notion.Client/{ => Api/Users}/UsersClient.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename Src/Notion.Client/{ => Api/Databases}/DatabasesClient.cs (100%) rename Src/Notion.Client/{ => Api/Users}/UsersClient.cs (100%) diff --git a/Src/Notion.Client/DatabasesClient.cs b/Src/Notion.Client/Api/Databases/DatabasesClient.cs similarity index 100% rename from Src/Notion.Client/DatabasesClient.cs rename to Src/Notion.Client/Api/Databases/DatabasesClient.cs diff --git a/Src/Notion.Client/UsersClient.cs b/Src/Notion.Client/Api/Users/UsersClient.cs similarity index 100% rename from Src/Notion.Client/UsersClient.cs rename to Src/Notion.Client/Api/Users/UsersClient.cs From 60886cdcb9ea6d01dc0fdbb5666829a08cdb84c2 Mon Sep 17 00:00:00 2001 From: Vedant Koditkar Date: Sun, 30 May 2021 01:43:13 +0530 Subject: [PATCH 4/7] Remove dead code :coffin: --- Src/Notion.Client/RestClient.cs | 1 - Src/Notion.Client/SerializerHelper.cs | 31 --------------------------- 2 files changed, 32 deletions(-) delete mode 100644 Src/Notion.Client/SerializerHelper.cs diff --git a/Src/Notion.Client/RestClient.cs b/Src/Notion.Client/RestClient.cs index 08fb683c..93d761a0 100644 --- a/Src/Notion.Client/RestClient.cs +++ b/Src/Notion.Client/RestClient.cs @@ -33,7 +33,6 @@ public class RestClient : IRestClient { private HttpClient _httpClient; private readonly ClientOptions _options; - private readonly List jsonConverters = new List(); private readonly JsonSerializerSettings defaultSerializerSettings = new JsonSerializerSettings { diff --git a/Src/Notion.Client/SerializerHelper.cs b/Src/Notion.Client/SerializerHelper.cs deleted file mode 100644 index 4134a358..00000000 --- a/Src/Notion.Client/SerializerHelper.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Notion.Client -{ - internal class SerializerHelper - { - internal static T Deserialize(Stream stream, List converters) - { - using (StreamReader sr = new StreamReader(stream)) - { - using (JsonReader reader = new JsonTextReader(sr)) - { - JsonSerializer serializer = JsonSerializer.CreateDefault(); - - if (converters.Any()) - { - foreach (var converter in converters) - { - serializer.Converters.Add(converter); - } - } - - return serializer.Deserialize(reader); - } - } - } - } -} From 3ce7a3cb722adbff9f42b93bbd1861ed211adf6e Mon Sep 17 00:00:00 2001 From: Vedant Koditkar Date: Sun, 30 May 2021 01:56:52 +0530 Subject: [PATCH 5/7] Move ApiEndpoints in a file for easy maintenance :art: --- Src/Notion.Client/Api/ApiEndpoints.cs | 18 ++++++++++++++++++ .../Api/Databases/DatabasesClient.cs | 7 ++++--- Src/Notion.Client/Api/Users/UsersClient.cs | 5 +++-- 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 Src/Notion.Client/Api/ApiEndpoints.cs diff --git a/Src/Notion.Client/Api/ApiEndpoints.cs b/Src/Notion.Client/Api/ApiEndpoints.cs new file mode 100644 index 00000000..de8c9cdb --- /dev/null +++ b/Src/Notion.Client/Api/ApiEndpoints.cs @@ -0,0 +1,18 @@ +namespace Notion.Client +{ + public static class ApiEndpoints + { + public static class DatabasesApiUrls + { + public static string Retrieve(string databaseId) => $"/v1/databases/{databaseId}"; + public static string List() => "/v1/databases"; + public static string Query(string databaseId) => $"/v1/databases/{databaseId}/query"; + } + + public static class UsersApiUrls + { + public static string Retrieve(string userId) => $"/v1/users/{userId}"; + public static string List() => "/v1/users"; + } + } +} \ No newline at end of file diff --git a/Src/Notion.Client/Api/Databases/DatabasesClient.cs b/Src/Notion.Client/Api/Databases/DatabasesClient.cs index 5a9ff5cc..73db40c1 100644 --- a/Src/Notion.Client/Api/Databases/DatabasesClient.cs +++ b/Src/Notion.Client/Api/Databases/DatabasesClient.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using static Notion.Client.ApiEndpoints; namespace Notion.Client { @@ -24,7 +25,7 @@ public async Task RetrieveAsync(string databaseId) { try { - return await _client.GetAsync($"/v1/databases/{databaseId}"); + return await _client.GetAsync(DatabasesApiUrls.Retrieve(databaseId)); } catch (Exception e) { @@ -44,7 +45,7 @@ public async Task> ListAsync(DatabasesListParameters dat { "page_size", databasesListQueryParmaters?.PageSize } }; - return await _client.GetAsync>("/v1/databases", queryParams); + return await _client.GetAsync>(DatabasesApiUrls.List(), queryParams); } catch (Exception e) { @@ -58,7 +59,7 @@ public async Task> QueryAsync(string databaseId, DatabasesQu try { var body = (IDatabaseQueryBodyParameters)databasesQueryParameters; - return await _client.PostAsync>($"/v1/databases/{databaseId}/query", body); + return await _client.PostAsync>(DatabasesApiUrls.Query(databaseId), body); } catch (Exception e) { diff --git a/Src/Notion.Client/Api/Users/UsersClient.cs b/Src/Notion.Client/Api/Users/UsersClient.cs index 701b1e21..0d62eaa8 100644 --- a/Src/Notion.Client/Api/Users/UsersClient.cs +++ b/Src/Notion.Client/Api/Users/UsersClient.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using static Notion.Client.ApiEndpoints; namespace Notion.Client { @@ -22,7 +23,7 @@ public async Task RetrieveAsync(string userId) { try { - return await _client.GetAsync($"/v1/users/{userId}"); + return await _client.GetAsync(UsersApiUrls.Retrieve(userId)); } catch (Exception e) { @@ -34,7 +35,7 @@ public async Task> ListAsync() { try { - return await _client.GetAsync>("/v1/users"); + return await _client.GetAsync>(UsersApiUrls.List()); } catch (Exception e) { From 7cc2908d1f7c8b9aa4e472960350acd4d5ad9024 Mon Sep 17 00:00:00 2001 From: Vedant Koditkar Date: Sun, 30 May 2021 02:00:16 +0530 Subject: [PATCH 6/7] remove try catch - let the exception bubble up. This help caller to get read the NotionApiException and handle as per their need. --- .../Api/Databases/DatabasesClient.cs | 43 +++++-------------- Src/Notion.Client/Api/Users/UsersClient.cs | 18 +------- 2 files changed, 13 insertions(+), 48 deletions(-) diff --git a/Src/Notion.Client/Api/Databases/DatabasesClient.cs b/Src/Notion.Client/Api/Databases/DatabasesClient.cs index 73db40c1..deb4798e 100644 --- a/Src/Notion.Client/Api/Databases/DatabasesClient.cs +++ b/Src/Notion.Client/Api/Databases/DatabasesClient.cs @@ -23,48 +23,27 @@ public DatabasesClient(IRestClient client) public async Task RetrieveAsync(string databaseId) { - try - { - return await _client.GetAsync(DatabasesApiUrls.Retrieve(databaseId)); - } - catch (Exception e) - { - // Todo: Throw Custom Exception - return null; - } + return await _client.GetAsync(DatabasesApiUrls.Retrieve(databaseId)); } public async Task> ListAsync(DatabasesListParameters databasesListParameters = null) { - try - { - var databasesListQueryParmaters = (IDatabasesListQueryParmaters)databasesListParameters; - var queryParams = new Dictionary() - { - { "start_cursor", databasesListQueryParmaters?.StartCursor }, - { "page_size", databasesListQueryParmaters?.PageSize } - }; + var databasesListQueryParmaters = (IDatabasesListQueryParmaters)databasesListParameters; - return await _client.GetAsync>(DatabasesApiUrls.List(), queryParams); - } - catch (Exception e) + var queryParams = new Dictionary() { - // Todo: Throw Custom Exception - return null; - } + { "start_cursor", databasesListQueryParmaters?.StartCursor }, + { "page_size", databasesListQueryParmaters?.PageSize } + }; + + return await _client.GetAsync>(DatabasesApiUrls.List(), queryParams); } public async Task> QueryAsync(string databaseId, DatabasesQueryParameters databasesQueryParameters) { - try - { - var body = (IDatabaseQueryBodyParameters)databasesQueryParameters; - return await _client.PostAsync>(DatabasesApiUrls.Query(databaseId), body); - } - catch (Exception e) - { - return null; - } + var body = (IDatabaseQueryBodyParameters)databasesQueryParameters; + + return await _client.PostAsync>(DatabasesApiUrls.Query(databaseId), body); } } } \ No newline at end of file diff --git a/Src/Notion.Client/Api/Users/UsersClient.cs b/Src/Notion.Client/Api/Users/UsersClient.cs index 0d62eaa8..ba30301c 100644 --- a/Src/Notion.Client/Api/Users/UsersClient.cs +++ b/Src/Notion.Client/Api/Users/UsersClient.cs @@ -21,26 +21,12 @@ public UsersClient(IRestClient client) public async Task RetrieveAsync(string userId) { - try - { - return await _client.GetAsync(UsersApiUrls.Retrieve(userId)); - } - catch (Exception e) - { - return null; - } + return await _client.GetAsync(UsersApiUrls.Retrieve(userId)); } public async Task> ListAsync() { - try - { - return await _client.GetAsync>(UsersApiUrls.List()); - } - catch (Exception e) - { - return null; - } + return await _client.GetAsync>(UsersApiUrls.List()); } } } From 04d5fe989de597bac01dd760cfb1031f4e956479 Mon Sep 17 00:00:00 2001 From: Vedant Koditkar Date: Sun, 30 May 2021 02:17:07 +0530 Subject: [PATCH 7/7] Fix CodeFactor issue :art: --- Src/Notion.Client/Extensions/HttpResponseMessageExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Src/Notion.Client/Extensions/HttpResponseMessageExtensions.cs b/Src/Notion.Client/Extensions/HttpResponseMessageExtensions.cs index 98b1499d..d7f2b894 100644 --- a/Src/Notion.Client/Extensions/HttpResponseMessageExtensions.cs +++ b/Src/Notion.Client/Extensions/HttpResponseMessageExtensions.cs @@ -15,7 +15,6 @@ internal static async Task ParseStreamAsync(this HttpResponseMessage respo { using (JsonReader jsonReader = new JsonTextReader(streamReader)) { - JsonSerializer serializer = null; if (serializerSettings == null)