diff --git a/CHANGELOG.md b/CHANGELOG.md index a3c0206..b08b1de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Change Log -## 12/08/2023 - v1.1.0 +## 15/08/2023 - v1.2.0 + +- **BaseClient**: + - Add methods and types for Xml requests + - Add methods for download files + - Do not convert query parameters to lower-case + - Handle `enums` with flags as lists on query parameters +- **WormsClient:** Add `GetAphiaLink(string|int)` method to get a link to WoRMS +- **IUCN:** Make `GetSpecieRedirectLink(string|int)` method static +- Add Barcode of Life Data Sytem (`BoldSystemsClient`) + +## 13/08/2023 - v1.1.0 - Add Species+/CITES (`SpeciesPlusClient`) - Move `Worms` to `MarineSpecies` namespace @@ -17,5 +28,5 @@ - Fix the regex for `WebsiteUrl` to better remove the api sub-domain ## 11/08/2023 - v1.0.0 - + - First release \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 9c1331b..1cfc5f8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,11 +1,12 @@ net6.0;net7.0 - Tiago Conceição; sn4k3 + Tiago Conceição (sn4k3) PTRTECH Copyright 2023-$([System.DateTime]::Now.ToString(`yyyy`)) © PTRTECH 1.1.0 Queries and fetch data from species, taxon, regions and conservation database(s) to retrieve information using the provider API. + $(MSBuildThisFileDirectory)images/icon.ico MIT true @@ -13,8 +14,8 @@ https://github.com/sn4k3/SpeciesDatabaseApi https://github.com/sn4k3/SpeciesDatabaseApi/releases README.md - icon.png - species, taxomy, taxomony, biota, database, api, rest, world, register, marine, wrms, worms, iucn, mr, conservation, nature, regions + icon-128.png + species taxomy taxomony biota database api rest world register marine wrms worms iucn mr conservation nature regions bold systems Git enable @@ -26,7 +27,7 @@ - + diff --git a/README.md b/README.md index ca9146b..20a5cfa 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,19 @@ -# Species Database Api +# ![Icon](https://raw.githubusercontent.com/sn4k3/SpeciesDatabaseApi/master/images/icon-64.png) Species Database Api Queries and fetch data from species, taxon, regions and conservation database(s) to retrieve information using the provider API. ## 🌐 Clients -| Name / Provider | Class | Terms of use | -| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | -| [International Union for Conservation of Nature (IUCN)](https://iucn.org) | [IucnClient](https://github.com/sn4k3/SpeciesDatabaseApi/blob/master/SpeciesDatabaseApi/Iucn/IucnClient.cs) | [Terms of use](http://apiv3.iucnredlist.org/about) | -| [Marine Regions](https://marineregions.org) | [MarineRegionsClient](https://github.com/sn4k3/SpeciesDatabaseApi/blob/master/SpeciesDatabaseApi/MarineRegions/MarineRegionsClient.cs) | [Terms of use](https://marineregions.org/disclaimer.php) | -| [Species+/CITES](https://speciesplus.net) | [SpeciesPlusClient](https://github.com/sn4k3/SpeciesDatabaseApi/blob/master/SpeciesDatabaseApi/SpeciesPlus/SpeciesPlusClient.cs) | [Terms of use](https://speciesplus.net/terms-of-use) | -| [World Register of Marine Species (WoRMS)](https://marinespecies.org) | [WormsClient](https://github.com/sn4k3/SpeciesDatabaseApi/blob/master/SpeciesDatabaseApi/MarineSpecies/WormsClient.cs) | [Terms of use](https://marinespecies.org/about.php#terms) | +| Name / Provider | Class | Terms of use | +| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | +| [Barcode of Life Data Sytem (BoldSystems)](https://www.boldsystems.org) | [BoldSystemsClient](https://github.com/sn4k3/SpeciesDatabaseApi/blob/master/SpeciesDatabaseApi/BoldSystems/BoldSystemsClient.cs) | [Terms of use](https://www.boldsystems.org/index.php/Resources/whatIsBOLD) | +| [International Union for Conservation of Nature (IUCN)](https://www.iucn.org) | [IucnClient](https://github.com/sn4k3/SpeciesDatabaseApi/blob/master/SpeciesDatabaseApi/Iucn/IucnClient.cs) | [Terms of use](http://apiv3.iucnredlist.org/about) | +| [Marine Regions](https://www.marineregions.org) | [MarineRegionsClient](https://github.com/sn4k3/SpeciesDatabaseApi/blob/master/SpeciesDatabaseApi/MarineRegions/MarineRegionsClient.cs) | [Terms of use](https://www.marineregions.org/disclaimer.php) | +| [Species+/CITES](https://www.speciesplus.net) | [SpeciesPlusClient](https://github.com/sn4k3/SpeciesDatabaseApi/blob/master/SpeciesDatabaseApi/SpeciesPlus/SpeciesPlusClient.cs) | [Terms of use](https://www.speciesplus.net/terms-of-use) | +| [World Register of Marine Species (WoRMS)](https://www.marinespecies.org) | [WormsClient](https://github.com/sn4k3/SpeciesDatabaseApi/blob/master/SpeciesDatabaseApi/MarineSpecies/WormsClient.cs) | [Terms of use](https://www.marinespecies.org/about.php#terms) | ## 🤝 Terms of use @@ -117,10 +118,11 @@ Run the "SpeciesDatabaseCmd.exe" and follow the in-terminal instructions to call # -?, -h, --help Show help and usage information # # Commands: -# WORMS Query - World Register of Marine Species (https://marinespecies.org) -# IUCN Query - International Union for Conservation of Nature (http://iucnredlist.org) -# MARINEREGIONS Query - Marine Regions (https://marineregions.org) -# SPECIES+ Query - Species+/CITES (https://speciesplus.net) +# BOLDSYSTEMS Query - Barcode of Life Data Sytem (https://www.boldsystems.org) +# IUCN Query - International Union for Conservation of Nature (http://www.iucnredlist.org) +# MARINEREGIONS Query - Marine Regions (https://www.marineregions.org) +# SPECIES+ Query - Species+/CITES (https://www.speciesplus.net) +# WORMS Query - World Register of Marine Species (https://www.marinespecies.org) SpeciesDatabaseCmd.exe IUCN SpecieCommonNames "Carcharodon carcharias" diff --git a/SpeciesDatabaseApi/ApiToken.cs b/SpeciesDatabaseApi/ApiToken.cs index ee34b00..745323a 100644 --- a/SpeciesDatabaseApi/ApiToken.cs +++ b/SpeciesDatabaseApi/ApiToken.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Net.Http.Headers; namespace SpeciesDatabaseApi; @@ -101,6 +103,27 @@ protected internal void Set(string key, string value, ApiTokenPlacement placemen Placement = placement; } + /// + /// Try to inject this token into a if configured and possible + /// + /// The where it will try to inject the token + /// + public bool TryInject(HttpRequestMessage request) + { + if (!CanUse) return false; + switch (Placement) + { + case ApiTokenPlacement.Header: + request.Headers.Add(Key, Value); + return true; + case ApiTokenPlacement.HeaderAuthorization: + request.Headers.Authorization = new AuthenticationHeaderValue(Key, Value); + return true; + default: + return false; + } + } + #endregion #region Equality and Format diff --git a/SpeciesDatabaseApi/BaseClient.cs b/SpeciesDatabaseApi/BaseClient.cs index a2408c6..8a0688d 100644 --- a/SpeciesDatabaseApi/BaseClient.cs +++ b/SpeciesDatabaseApi/BaseClient.cs @@ -2,11 +2,13 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Net.Mime; using System.Reflection; using System.Security.Cryptography; using System.Text; @@ -17,6 +19,7 @@ using System.Threading.Tasks; using System.Timers; using System.Web; +using System.Xml.Serialization; namespace SpeciesDatabaseApi; @@ -109,7 +112,7 @@ public virtual string WebsiteUrl get { var domain = ApiAddress.GetLeftPart(UriPartial.Authority); - return Regex.Replace(domain, @"\/\/(.*api([a-zA-Z0-9_-]+)?|www)[.]", "//"); + return Regex.Replace(domain, @"\/\/(.*api([a-zA-Z0-9_-]+)?|www)[.]", "//www."); } } @@ -200,6 +203,7 @@ public void Dispose() #region Methods + #region Query parameters /// /// Escapes the data query to be safe in the url /// @@ -232,7 +236,7 @@ public string GetRequestUrl(string path) var url = GetRawRequestUrl(path); if (ApiToken is { CanUse: true, Placement: ApiTokenPlacement.Get }) { - return $"{url}?{UrlEncode(ApiToken.Key)}={UrlEncode(ApiToken.Value)}"; + return $"{url}?{EscapeDataString(ApiToken.Key)}={EscapeDataString(ApiToken.Value)}"; } return url; @@ -300,28 +304,44 @@ public string GetUrlParametersString(IEnumerable> foreach (var obj in list) { if (obj is null) continue; - value = obj.ToString()?.Trim().ToLowerInvariant(); + value = obj.ToString()?.Trim(); if (string.IsNullOrWhiteSpace(value)) continue; items.Add(value); } value = string.Join(',', items); break; + case Enum enumValue: + if (enumValue.GetType().IsDefined(typeof(FlagsAttribute), false)) + { + // Treat as list + var enumList = Enum.GetValues(enumValue.GetType()).Cast().Where(enumValue.HasFlag); + value = string.Join(',', enumList); + } + else + { + // Treat as string + value = enumValue.ToString(); + } + break; + case bool boolValue: + value = boolValue.ToString().ToLowerInvariant(); + break; case string str: - value = str.Trim().ToLowerInvariant(); + value = str.Trim(); if (string.IsNullOrWhiteSpace(value)) continue; break; default: - value = item.Value.ToString()?.Trim().ToLowerInvariant(); + value = item.Value.ToString()?.Trim(); if (string.IsNullOrWhiteSpace(value)) continue; break; } - builder.Append($"{(builder.Length == 0 ? "?" : "&")}{UrlEncode(item.Key.ToLowerInvariant())}={UrlEncode(value)}"); + builder.Append($"{(builder.Length == 0 ? "?" : "&")}{EscapeDataString(item.Key)}={EscapeDataString(value)}"); } if (ApiToken is { CanUse: true, Placement: ApiTokenPlacement.Get }) { - builder.Append($"{(builder.Length == 0 ? "?" : "&")}{UrlEncode(ApiToken.Key)}={UrlEncode(ApiToken.Value)}"); + builder.Append($"{(builder.Length == 0 ? "?" : "&")}{EscapeDataString(ApiToken.Key)}={EscapeDataString(ApiToken.Value)}"); } return builder.ToString(); @@ -347,6 +367,11 @@ public string GetUrlParametersString(IEnumerable> /// /// The formatted string public string GetUrlParametersString(object? obj) + { + return GetUrlParametersString(GetDictionaryFromClassProperties(obj)); + } + + public Dictionary GetDictionaryFromClassProperties(object? obj) { var dict = new Dictionary(); @@ -359,42 +384,50 @@ public string GetUrlParametersString(object? obj) var method = propertyInfo.GetMethod; if (method is null) continue; var value = propertyInfo.GetValue(obj); + if (value is null) continue; var attr = propertyInfo.GetCustomAttribute(); dict.Add(attr?.Name ?? method.Name, value); } } - return GetUrlParametersString(dict); + return dict; } + #endregion - protected HttpRequestMessage PrepareJsonHttpRequestMessage(string requestUrl, HttpMethod httpMethod) + protected HttpRequestMessage CreateHttpRequestMessage(string requestUrl, HttpMethod httpMethod, RequestContentType contentType = RequestContentType.Json) { var request = new HttpRequestMessage(httpMethod, requestUrl); - request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - if (ProductInfoHeader is not null) + + switch (contentType) { - request.Headers.UserAgent.Add(ProductInfoHeader); + case RequestContentType.Raw: + break; + case RequestContentType.Json: + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + break; + case RequestContentType.Xml: + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); + break; + default: + throw new ArgumentOutOfRangeException(nameof(contentType), contentType, null); } - - if (ApiToken.CanUse) + + if (ProductInfoHeader is not null) { - switch (ApiToken.Placement) - { - case ApiTokenPlacement.Header: - request.Headers.Add(ApiToken.Key, ApiToken.Value); - break; - case ApiTokenPlacement.HeaderAuthorization: - request.Headers.Authorization = new AuthenticationHeaderValue(ApiToken.Key, ApiToken.Value); - break; - } + request.Headers.UserAgent.Add(ProductInfoHeader); } + ApiToken.TryInject(request); + AuthToken.TryInject(request); + return request; } + + #region Json Requests public Task PostJsonAsync(string requestUrl, object postData, IEnumerable> urlParameters, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Post); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Post); var requestJson = JsonSerializer.Serialize(postData); request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); @@ -404,7 +437,7 @@ protected HttpRequestMessage PrepareJsonHttpRequestMessage(string requestUrl, Ht public Task PostJsonAsync(string requestUrl, object postData, IReadOnlyDictionary urlParameters, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Post); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Post); var requestJson = JsonSerializer.Serialize(postData); request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); @@ -414,7 +447,7 @@ protected HttpRequestMessage PrepareJsonHttpRequestMessage(string requestUrl, Ht public Task PostJsonAsync(string requestUrl, object postData, KeyValuePair urlParameter, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Post); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Post); var requestJson = JsonSerializer.Serialize(postData); request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); @@ -424,7 +457,7 @@ protected HttpRequestMessage PrepareJsonHttpRequestMessage(string requestUrl, Ht public Task PostJsonAsync(string requestUrl, object postData, object classUrlParameters, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Post); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Post); var requestJson = JsonSerializer.Serialize(postData); request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); @@ -434,7 +467,7 @@ protected HttpRequestMessage PrepareJsonHttpRequestMessage(string requestUrl, Ht public Task PostJsonAsync(string requestUrl, object postData, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Post); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Post); var requestJson = JsonSerializer.Serialize(postData); request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); @@ -444,63 +477,242 @@ protected HttpRequestMessage PrepareJsonHttpRequestMessage(string requestUrl, Ht public Task GetJsonAsync(string requestUrl, IEnumerable> urlParameters, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get); return SendRequestAsync(request, cancellationToken); } public Task GetJsonAsync(string requestUrl, IReadOnlyDictionary urlParameters, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get); return SendRequestAsync(request, cancellationToken); } public Task GetJsonAsync(string requestUrl, KeyValuePair urlParameter, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Get); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Get); return SendRequestAsync(request, cancellationToken); } public Task GetJsonAsync(string requestUrl, object classUrlParameters, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Get); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Get); return SendRequestAsync(request, cancellationToken); } public Task GetJsonAsync(string requestUrl, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Get); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Get); return SendRequestAsync(request, cancellationToken); } public Task DeleteJsonAsync(string requestUrl, IEnumerable> urlParameters, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Delete); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Delete); return SendRequestAsync(request, cancellationToken); } public Task DeleteJsonAsync(string requestUrl, IReadOnlyDictionary urlParameters, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Delete); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Delete); return SendRequestAsync(request, cancellationToken); } public Task DeleteJsonAsync(string requestUrl, KeyValuePair urlParameter, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Delete); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Delete); return SendRequestAsync(request, cancellationToken); } public Task DeleteJsonAsync(string requestUrl, object classUrlParameters, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Delete); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Delete); return SendRequestAsync(request, cancellationToken); } public Task DeleteJsonAsync(string requestUrl, CancellationToken cancellationToken = default) { - using var request = PrepareJsonHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Delete); + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Delete); return SendRequestAsync(request, cancellationToken); } + #endregion + + #region Xml Requests + public Task PostXmlAsync(string requestUrl, object postData, IEnumerable> urlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Post); + + var requestJson = JsonSerializer.Serialize(postData); + request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); + + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task PostXmlAsync(string requestUrl, object postData, IReadOnlyDictionary urlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Post); + + var requestJson = JsonSerializer.Serialize(postData); + request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); + + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task PostXmlAsync(string requestUrl, object postData, KeyValuePair urlParameter, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Post); + + var requestJson = JsonSerializer.Serialize(postData); + request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); + + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task PostXmlAsync(string requestUrl, object postData, object classUrlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Post); + + var requestJson = JsonSerializer.Serialize(postData); + request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); + + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task PostXmlAsync(string requestUrl, object postData, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Post); + + var requestJson = JsonSerializer.Serialize(postData); + request.Content = new StringContent(requestJson, Encoding.UTF8, "application/json"); + + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task GetXmlAsync(string requestUrl, IEnumerable> urlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task GetXmlAsync(string requestUrl, IReadOnlyDictionary urlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task GetXmlAsync(string requestUrl, KeyValuePair urlParameter, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Get); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task GetXmlAsync(string requestUrl, object classUrlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Get); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task GetXmlAsync(string requestUrl, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Get); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task DeleteXmlAsync(string requestUrl, IEnumerable> urlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Delete); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task DeleteXmlAsync(string requestUrl, IReadOnlyDictionary urlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Delete); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task DeleteXmlAsync(string requestUrl, KeyValuePair urlParameter, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Delete); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task DeleteXmlAsync(string requestUrl, object classUrlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Delete); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + + public Task DeleteXmlAsync(string requestUrl, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Delete); + return SendRequestAsync(request, RequestContentType.Xml, cancellationToken); + } + #endregion + + #region Download Requests + + public Task DownloadAsync(string requestUrl, IEnumerable> urlParameters, Stream stream, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get, RequestContentType.Raw); + return SendDownloadRequestAsync(request, stream, cancellationToken); + } + + public Task DownloadAsync(string requestUrl, IReadOnlyDictionary urlParameters, Stream stream, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get, RequestContentType.Raw); + return SendDownloadRequestAsync(request, stream, cancellationToken); + } + + public Task DownloadAsync(string requestUrl, KeyValuePair urlParameter, Stream stream, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Get, RequestContentType.Raw); + return SendDownloadRequestAsync(request, stream, cancellationToken); + } + + public Task DownloadAsync(string requestUrl, object classUrlParameters, Stream stream, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Get, RequestContentType.Raw); + return SendDownloadRequestAsync(request, stream, cancellationToken); + } + + public Task DownloadAsync(string requestUrl, Stream stream, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Get, RequestContentType.Raw); + return SendDownloadRequestAsync(request, stream, cancellationToken); + } + + #endregion + + #region + public Task GetResponseAsync(string requestUrl, IEnumerable> urlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get, RequestContentType.Raw); + return SendRequestAsync(request, cancellationToken); + } + + public Task GetResponseAsync(string requestUrl, IReadOnlyDictionary urlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameters), HttpMethod.Get, RequestContentType.Raw); + return SendRequestAsync(request, cancellationToken); + } + + public Task GetResponseAsync(string requestUrl, KeyValuePair urlParameter, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, urlParameter), HttpMethod.Get, RequestContentType.Raw); + return SendRequestAsync(request, cancellationToken); + } + + public Task GetResponseAsync(string requestUrl, object classUrlParameters, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl, classUrlParameters), HttpMethod.Get, RequestContentType.Raw); + return SendRequestAsync(request, cancellationToken); + } + + public Task GetResponseAsync(string requestUrl, CancellationToken cancellationToken = default) + { + using var request = CreateHttpRequestMessage(GetRequestUrl(requestUrl), HttpMethod.Get, RequestContentType.Raw); + return SendRequestAsync(request, cancellationToken); + } + #endregion /// /// Trigger before SendRequestAsync is executed. @@ -514,35 +726,62 @@ protected virtual Task OnBeforeSendRequestAsync(HttpRequestMessage request, Canc } /// - /// Sends a request to the Api + /// Sends a request to the Api and return the /// - /// /// /// /// /// - private async Task SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) + /// Don't forget to dispose the + private async Task SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) { await OnBeforeSendRequestAsync(request, cancellationToken).ConfigureAwait(false); +#if DEBUG Debug.WriteLine($"{ClientAcronym}: Sending request {request.RequestUri}"); + Console.WriteLine($"{ClientAcronym}: Sending request {request.RequestUri}"); +#endif if (_autoWaitForRequestLimit && RequestsHitLimit) { do { var waitTime = RandomNumberGenerator.GetInt32(500, 2000); +#if DEBUG Debug.WriteLine($"{ClientAcronym}: Api limit hit, waiting {waitTime}ms before re-try."); + Console.WriteLine($"{ClientAcronym}: Api limit hit, waiting {waitTime}ms before re-try."); +#endif await Task.Delay(waitTime, cancellationToken).ConfigureAwait(false); } while (RequestsHitLimit); } - using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); - + var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); Interlocked.Increment(ref _totalRequests); + if (_autoWaitForRequestLimit && _maximumRequestsPerSecond > 0) + { + Interlocked.Increment(ref _requestsInCurrentSecond); + _resetRequestsTimer.Start(); + } + if (ThrowExceptionIfRequestStatusCodeFails) response.EnsureSuccessStatusCode(); - else if (!response.IsSuccessStatusCode) return default; + + return response; + } + + /// + /// Sends a request to the Api and return a data model from the + /// + /// + /// + /// + /// + /// + /// + private async Task SendRequestAsync(HttpRequestMessage request, RequestContentType contentType = RequestContentType.Json, CancellationToken cancellationToken = default) + { + using var response = await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); + if (!ThrowExceptionIfRequestStatusCodeFails && !response.IsSuccessStatusCode) return default; switch (response.StatusCode) { @@ -550,20 +789,62 @@ protected virtual Task OnBeforeSendRequestAsync(HttpRequestMessage request, Canc return default; } - if (_autoWaitForRequestLimit && _maximumRequestsPerSecond > 0) + switch (contentType) { - Interlocked.Increment(ref _requestsInCurrentSecond); - _resetRequestsTimer.Start(); - } - + case RequestContentType.Json: #if DEBUG - var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - Debug.WriteLine(json); - return JsonSerializer.Deserialize(json, DefaultJsonSerializerOptions); + var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + Debug.WriteLine(json); + Console.WriteLine(json); + return JsonSerializer.Deserialize(json, DefaultJsonSerializerOptions); #endif +#pragma warning disable CS0162 // Unreachable code detected + return await response.Content.ReadFromJsonAsync(DefaultJsonSerializerOptions, cancellationToken).ConfigureAwait(false); +#pragma warning restore CS0162 // Unreachable code detected + case RequestContentType.Xml: + var xmlStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + var serializer = new XmlSerializer(typeof(T)); + var obj = serializer.Deserialize(xmlStream); + if (obj is null) return default; + return (T)obj; + default: + throw new ArgumentOutOfRangeException(nameof(contentType), contentType, null); + } + } + + /// + /// Sends a request to the Api and return a data model from json + /// + /// + /// + /// + /// + /// + private Task SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) + => SendRequestAsync(request, RequestContentType.Json, cancellationToken); + + + /// + /// Sends a download request to the Api and copy the content to a + /// + /// + /// The stream to copy the content to + /// + /// + /// + private async Task SendDownloadRequestAsync(HttpRequestMessage request, Stream stream, CancellationToken cancellationToken = default) + { + using var response = await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); + if (!ThrowExceptionIfRequestStatusCodeFails && !response.IsSuccessStatusCode) return Task.FromException(new HttpRequestException("Request return not success status code", null, response.StatusCode)); - return await response.Content.ReadFromJsonAsync(DefaultJsonSerializerOptions, cancellationToken).ConfigureAwait(false); - } + switch (response.StatusCode) + { + case HttpStatusCode.NoContent: + return Task.FromException(new HttpRequestException("Request return no content", null, response.StatusCode)); + } -#endregion + return response.Content.CopyToAsync(stream, cancellationToken); + } + + #endregion } \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/BoldRecords.cs b/SpeciesDatabaseApi/BoldSystems/BoldRecords.cs new file mode 100644 index 0000000..cda79d2 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/BoldRecords.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class BoldRecords : IEquatable +{ + [JsonPropertyName("records")] + public Dictionary Records { get; set; } = new(); + + /// + public override string ToString() + { + return $"{nameof(Records)}: {Records}"; + } + + /// + public bool Equals(BoldRecords? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Records.Equals(other.Records); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((BoldRecords)obj); + } + + /// + public override int GetHashCode() + { + return Records.GetHashCode(); + } + + public static bool operator ==(BoldRecords? left, BoldRecords? right) + { + return Equals(left, right); + } + + public static bool operator !=(BoldRecords? left, BoldRecords? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/BoldSystemsClient.cs b/SpeciesDatabaseApi/BoldSystems/BoldSystemsClient.cs new file mode 100644 index 0000000..06e8c00 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/BoldSystemsClient.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text.Json.Nodes; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using SpeciesDatabaseApi.Extensions; + +namespace SpeciesDatabaseApi.BoldSystems; + +/// +/// The client for https://iucn.org API +/// +public class BoldSystemsClient : BaseClient +{ + #region Static objects + /// + /// The client full name/provider + /// + public const string FullName = "Barcode of Life Data Sytem"; + + /// + /// The Api default address + /// + public static readonly Uri DefaultApiAddress = new("https://www.boldsystems.org/index.php"); + #endregion + + #region Properties + /// + public override decimal Version => 4; + + /// + public override string ClientAcronym => "BoldSystems"; + + /// + public override string ClientFullName => FullName; + + #endregion + + #region Constructor + + public BoldSystemsClient(HttpClient? httpClient = null) : base(DefaultApiAddress, httpClient) + { + } + + #endregion + + #region Methods + + /// + /// Users only interested in count data for a given query can now use the API to retrieve the summary information that is provided by BOLD public searches. + /// + /// Parameters for the request, null values are ignored + /// Returns all records in one of the specified formats. + /// + /// + public Task GetStats(PublicApiParameters requestParameters, BoldStatsDataType dataType = BoldStatsDataType.DrillDown, CancellationToken token = default) + { + var parameters = GetDictionaryFromClassProperties(requestParameters); + if (parameters.Count == 0) throw new ArgumentException("The call require at least one parameter", nameof(requestParameters)); + parameters.TryAdd("dataType", StringExtensions.PrependCharByUpperChar(dataType.ToString(), '_').ToLowerInvariant()); + parameters.TryAdd("format", "json"); + return GetJsonAsync("API_Public/stats", parameters, token); + } + + /// + /// Users can query the system to retrieve matching specimen data records for a combination of parameters. + /// + /// Parameters for the request, null values are ignored + /// + /// + public Task GetSpecimen(PublicApiParameters requestParameters, CancellationToken token = default) + { + var parameters = GetDictionaryFromClassProperties(requestParameters); + if (parameters.Count == 0) throw new ArgumentException("The call require at least one parameter", nameof(requestParameters)); + parameters.TryAdd("format", "json"); + return GetJsonAsync("API_Public/specimen", parameters, token); + } + + + /// + /// Users can query the system to retrieve matching sequences for a combination of parameters. + /// + /// Parameters for the request, null values are ignored + /// + /// + public async Task> GetSequences(PublicApiParameters requestParameters, CancellationToken token = default) + { + var parameters = GetDictionaryFromClassProperties(requestParameters); + if (parameters.Count == 0) throw new ArgumentException("The call require at least one parameter", nameof(requestParameters)); + using var response = await GetResponseAsync("API_Public/sequence", parameters, token).ConfigureAwait(false); + + var list = new List(); + + if (response.StatusCode == HttpStatusCode.NoContent) return list; + + var stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false); + var streamReader = new StreamReader(stream); + + while (await streamReader.ReadLineAsync().ConfigureAwait(false) is { } line1) + { + // Example: >FCCA006-09|Ogilbia|COI-5P|JQ841943 + if (line1.Length == 0 || line1[0] != '>') continue; + var splitLine = line1[1..].Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (splitLine.Length != 4) continue; + if (await streamReader.ReadLineAsync().ConfigureAwait(false) is not { } line2) break; + if (line2.Length < 10) continue; + if (line2[0] != '-' && !char.IsUpper(line2[0])) continue; + + list.Add(new SequenceData + { + ProcessId = splitLine[0], + Identification = splitLine[1], + Marker = splitLine[2], + Accession = splitLine[3], + Nucleotides = line2 + }); + } + + return list; + } + + /// + /// Users can query the system to retrieve matching specimen data and sequence records for a combination of parameters. + /// + /// Parameters for the request, null values are ignored + /// + /// + public Task GetSpecimenAndSequence(PublicApiParameters requestParameters, CancellationToken token = default) + { + var parameters = GetDictionaryFromClassProperties(requestParameters); + if (parameters.Count == 0) throw new ArgumentException("The call require at least one parameter", nameof(requestParameters)); + parameters.TryAdd("format", "json"); + return GetJsonAsync("API_Public/combined", parameters, token); + } + + /// + /// Users can query the system to retrieve matching specimen data records for a combination of parameters + /// + /// Parameters for the request, null values are ignored + /// The stream to copy the content to + /// + /// + public Task DownloadTraceFile(PublicApiParameters requestParameters, Stream stream, CancellationToken token = default) + { + var parameters = GetDictionaryFromClassProperties(requestParameters); + if (parameters.Count == 0) throw new ArgumentException("The call require at least one parameter", nameof(requestParameters)); + return DownloadAsync("API_Public/trace", parameters, stream, token); + } + + /// + /// Users can query the system to retrieve matching specimen data records for a combination of parameters + /// + /// Parameters for the request, null values are ignored + /// The destination file to save the content + /// + /// + public async Task DownloadTraceFile(PublicApiParameters requestParameters, string filePath, CancellationToken token = default) + { + var parameters = GetDictionaryFromClassProperties(requestParameters); + if (parameters.Count == 0) throw new ArgumentException("The call require at least one parameter", nameof(requestParameters)); + + await using var fs = File.Open(filePath, FileMode.Create); + await DownloadAsync("API_Public/trace", parameters, fs, token).ConfigureAwait(false); + } + + /// + /// Retrieves taxonomy information by BOLD taxonomy ID + /// + /// The taxID to search for. + /// Specifies the datatypes that will be returned. + /// + /// the top public matches (up to 100) can be retrieved by querying a COI sequence. + public Task GetTaxonData(int taxId, BoldDataTypesEnum dataTypes = BoldDataTypesEnum.Basic, CancellationToken token = default) + { + var parameters = new Dictionary + { + {"taxId", taxId}, + {"dataTypes", dataTypes}, + {"includeTree", false} + }; + return GetJsonAsync("API_Tax/TaxonData", parameters, token); + } + + /// + /// Retrieves taxonomy information by BOLD taxonomy ID and list containing information for parent taxa as well as the specified taxon + /// + /// The taxID to search for. + /// Specifies the datatypes that will be returned. + /// + /// the top public matches (up to 100) can be retrieved by querying a COI sequence. + public Task?> GetTaxonDataIncludeTree(int taxId, BoldDataTypesEnum dataTypes = BoldDataTypesEnum.Basic, CancellationToken token = default) + { + var parameters = new Dictionary + { + {"taxId", taxId}, + {"dataTypes", dataTypes}, + {"includeTree", true} + }; + return GetJsonAsync>("API_Tax/TaxonData", parameters, token); + } + + /// + /// Retrieves taxonomy information by taxon name. + /// + /// The tax name to search for. + /// Specifies if the search should only find exact matches. All searches are case sensitive. + /// + /// the top public matches (up to 100) can be retrieved by querying a COI sequence. + public Task GetTaxonData(string taxName, bool fuzzy = false, CancellationToken token = default) + { + var parameters = new Dictionary + { + {"taxName", taxName}, + {"fuzzy", fuzzy}, + }; + return GetJsonAsync("API_Tax/TaxonSearch", parameters, token); + } + + /// + /// Query the COI ID Engine. + /// + /// Specifies the identification database to query (db names are case sensitive). + /// Specifies the query sequence (sequences are not case sensitive). + /// + /// the top public matches (up to 100) can be retrieved by querying a COI sequence. + public Task GetCoiMatches(BoldDatabaseEnum db, string sequence, CancellationToken token = default) + { + var parameters = new Dictionary + { + {"db", db}, + {"sequence", sequence} + }; + return GetXmlAsync("Ids_xml", parameters, token); + } + + #endregion +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/CoiMatch.cs b/SpeciesDatabaseApi/BoldSystems/CoiMatch.cs new file mode 100644 index 0000000..388ce72 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/CoiMatch.cs @@ -0,0 +1,69 @@ +using System; +using System.Xml.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +[XmlRoot("match")] +[XmlType("match")] +public class CoiMatch : IEquatable +{ + [XmlElement("ID")] + public string Id { get; set; } = string.Empty; + + [XmlElement("sequencedescription")] + public string SequenceDescription { get; set; } = string.Empty; + + [XmlElement("database")] + public string Database { get; set; } = string.Empty; + + [XmlElement("citation")] + public string Citation { get; set; } = string.Empty; + + [XmlElement("taxonomicidentification")] + public string TaxonomicIdentification { get; set; } = string.Empty; + + [XmlElement("similarity")] + public decimal Similarity { get; set; } + + [XmlElement("specimen")] + public Specimen Specimen { get; set; } = new(); + + /// + public override string ToString() + { + return $"{nameof(Id)}: {Id}, {nameof(SequenceDescription)}: {SequenceDescription}, {nameof(Database)}: {Database}, {nameof(Citation)}: {Citation}, {nameof(TaxonomicIdentification)}: {TaxonomicIdentification}, {nameof(Similarity)}: {Similarity}, {nameof(Specimen)}: {Specimen}"; + } + + public bool Equals(CoiMatch? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id == other.Id && SequenceDescription == other.SequenceDescription && Database == other.Database && Citation == other.Citation && TaxonomicIdentification == other.TaxonomicIdentification && Similarity == other.Similarity && Specimen.Equals(other.Specimen); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CoiMatch)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Id, SequenceDescription, Database, Citation, TaxonomicIdentification, Similarity, Specimen); + } + + public static bool operator ==(CoiMatch? left, CoiMatch? right) + { + return Equals(left, right); + } + + public static bool operator !=(CoiMatch? left, CoiMatch? right) + { + return !Equals(left, right); + } +} + diff --git a/SpeciesDatabaseApi/BoldSystems/CoiMatches.cs b/SpeciesDatabaseApi/BoldSystems/CoiMatches.cs new file mode 100644 index 0000000..1c55be6 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/CoiMatches.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +[XmlRoot("matches")] +public class CoiMatches : List +{ + +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/CollectionEvent.cs b/SpeciesDatabaseApi/BoldSystems/CollectionEvent.cs new file mode 100644 index 0000000..232208f --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/CollectionEvent.cs @@ -0,0 +1,66 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class CollectionEvent : IEquatable +{ + [JsonPropertyName("collectors")] + public string Collectors { get; set; } = string.Empty; + + [JsonPropertyName("country")] + public string Country { get; set; } = string.Empty; + + [JsonPropertyName("province_state")] + public string? ProvinceState { get; set; } + + [JsonPropertyName("sector")] + public string? Sector { get; set; } + + [JsonPropertyName("exactsite")] + public string? ExactSite { get; set; } + + [JsonPropertyName("coordinates")] + public Coordinate? Coordinates { get; set; } + + [JsonPropertyName("elev")] + public string? Elev { get; set; } + + /// + public override string ToString() + { + return $"{nameof(Collectors)}: {Collectors}, {nameof(Country)}: {Country}, {nameof(ProvinceState)}: {ProvinceState}, {nameof(Sector)}: {Sector}, {nameof(ExactSite)}: {ExactSite}, {nameof(Coordinates)}: {Coordinates}, {nameof(Elev)}: {Elev}"; + } + + public bool Equals(CollectionEvent? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Collectors == other.Collectors && Country == other.Country && ProvinceState == other.ProvinceState && Sector == other.Sector && ExactSite == other.ExactSite && Equals(Coordinates, other.Coordinates) && Elev == other.Elev; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CollectionEvent)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Collectors, Country, ProvinceState, Sector, ExactSite, Coordinates, Elev); + } + + public static bool operator ==(CollectionEvent? left, CollectionEvent? right) + { + return Equals(left, right); + } + + public static bool operator !=(CollectionEvent? left, CollectionEvent? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/CollectionLocation.cs b/SpeciesDatabaseApi/BoldSystems/CollectionLocation.cs new file mode 100644 index 0000000..76ba287 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/CollectionLocation.cs @@ -0,0 +1,52 @@ +using System; +using System.Xml.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +[XmlRoot("collectionlocation")] +public class CollectionLocation : IEquatable +{ + [XmlElement("country")] + public string? Country { get; set; } + + [XmlElement("coord")] + public Coordinate? Coordinate { get; set; } + + /// + public override string ToString() + { + return $"{nameof(Country)}: {Country}, {nameof(Coordinate)}: {Coordinate}"; + } + + public bool Equals(CollectionLocation? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Country == other.Country && Equals(Coordinate, other.Coordinate); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CollectionLocation)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Country, Coordinate); + } + + public static bool operator ==(CollectionLocation? left, CollectionLocation? right) + { + return Equals(left, right); + } + + public static bool operator !=(CollectionLocation? left, CollectionLocation? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/Coordinate.cs b/SpeciesDatabaseApi/BoldSystems/Coordinate.cs new file mode 100644 index 0000000..5e1ceaf --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/Coordinate.cs @@ -0,0 +1,59 @@ +using System; +using System.Text.Json.Serialization; +using System.Xml.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +[XmlRoot("coord")] +public class Coordinate : IEquatable +{ + [XmlElement("lat")] + public decimal? Latitude { get; set; } + + [XmlElement("lon")] + public decimal? Longitude { get; set; } + + [JsonPropertyName("coord_source")] + public string? CoordSource { get; set; } + + [JsonPropertyName("coord_accuracy")] + public string? CoordAccuracy { get; set; } + + /// + public override string ToString() + { + return $"{nameof(Latitude)}: {Latitude}, {nameof(Longitude)}: {Longitude}, {nameof(CoordSource)}: {CoordSource}, {nameof(CoordAccuracy)}: {CoordAccuracy}"; + } + + public bool Equals(Coordinate? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Latitude == other.Latitude && Longitude == other.Longitude && CoordSource == other.CoordSource && CoordAccuracy == other.CoordAccuracy; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Coordinate)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Latitude, Longitude, CoordSource, CoordAccuracy); + } + + public static bool operator ==(Coordinate? left, Coordinate? right) + { + return Equals(left, right); + } + + public static bool operator !=(Coordinate? left, Coordinate? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/Enumerations.cs b/SpeciesDatabaseApi/BoldSystems/Enumerations.cs new file mode 100644 index 0000000..a99aaa4 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/Enumerations.cs @@ -0,0 +1,88 @@ +using System; + +namespace SpeciesDatabaseApi.BoldSystems; + +public enum BoldDatabaseEnum +{ + /// + /// Every COI barcode record on BOLD with a minimum sequence length of 500bp (warning: unvalidated library and includes records without species level identification).
+ /// This includes many species represented by only one or two specimens as well as all species with interim taxonomy.
+ /// This search only returns a list of the nearest matches and does not provide a probability of placement to a taxon. + ///
+ COX1, + + /// + /// Every COI barcode record with a species level identification and a minimum sequence length of 500bp.
+ /// This includes many species represented by only one or two specimens as well as all species with interim taxonomy. + ///
+ COX1_SPECIES, + + /// + /// All published COI records from BOLD and GenBank with a minimum sequence length of 500bp.
+ /// This library is a collection of records from the published projects section of BOLD. + ///
+ COX1_SPECIES_PUBLIC, + + /// + /// Subset of the Species library with a minimum sequence length of 640bp and containing both public and private records.
+ /// This library is intended for short sequence identification as it provides maximum overlap with short reads from the barcode region of COI. + ///
+ COX1_L640bp +} + +[Flags] +public enum BoldDataTypesEnum +{ + /// + /// basic taxonomy info: includes taxid, taxon name, tax rank, tax division, parent taxid, parent taxon name + /// + Basic = 1, + + /// + /// specimen and sequence statistics: includes public species count, public BIN count, public marker counts, public record count, specimen count, sequenced specimen count, barcode specimen count, species count, barcode species count + /// + Stats = 2, + + /// + /// collection site information: includes country, collection site map + /// + Geo = 4, + + /// + /// specimen images: includes copyright information, image URL, image metadata + /// + Images = 8, + + /// + /// sequencing labs: includes lab name, record count + /// + SequencingLabs = 16, + + /// + /// specimen depositories: includes depository name, record count + /// + Depository = 32, + + /// + /// information from third parties: includes Wikipedia summary, Wikipedia URL + /// + ThirdParty = 64, + + /// + /// all information: identical to specifying all data types at once + /// + All = 128, +} + +public enum BoldStatsDataType +{ + /// + /// Provides record counts by [BINs, Country, Storing Institution, Species] + /// + DrillDown, + + /// + /// Provides the total counts of [BINs, Countries, Storing Institutions, Orders, Families, Genus, Species] found by the query + /// + Overview +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/PublicApiParameters.cs b/SpeciesDatabaseApi/BoldSystems/PublicApiParameters.cs new file mode 100644 index 0000000..87c3604 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/PublicApiParameters.cs @@ -0,0 +1,125 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +/// +/// Query parameters for the public API +/// +public class PublicApiParameters : IEquatable +{ + /// + /// Returns all records containing matching taxa, defined in a pipe delimited list.
+ /// Taxa includes scientific names at phylum, class, order, family, subfamily, genus, and species levels. + ///
+ /// + /// Bos taurus returns all records matching the taxon Bos taurus.
+ /// Aves|Reptilia returns all records matching the taxa Aves or Reptilia + ///
+ [JsonPropertyName("taxon")] + public string? Taxon { get; set; } + + /// + /// Returns all records containing matching IDs, defined in a pipe delimited list. + /// IDs include Sample IDs, Process IDs, Museum IDs and Field IDs. + /// + /// + /// ids=ACRJP618-11|ACRJP619-11 returns records matching these Process IDs.
+ /// ids=Example 10|Example 11|Example 12 returns records matching these Sample IDs. + ///
+ [JsonPropertyName("ids")] + public string? Ids { get; set; } + + /// + /// Returns all records contained in matching BINs, defined in a pipe delimited list.
+ /// A BIN is defined by a Barcode Index Number URI. + ///
+ /// + /// bin=BOLD:AAA5125|BOLD:AAA5126 returns records matching these BIN URIs. + /// + [JsonPropertyName("bin")] + public string? Bin { get; set; } + + /// + /// Returns all records contained in matching projects or datasets, in a pipe delimited list.
+ /// Containers include project codes and dataset codes. + ///
+ /// + ///container=SSBAA|SSBAB returns records contained within matching projects.
+ /// container=DS-EZROM returns records contained within the matching dataset. + ///
+ [JsonPropertyName("container")] + public string? Container { get; set; } + + /// + /// Returns all records stored in matching institutions, defined in a pipe delimited list.
+ /// Institutions are the Specimen Storing Site. + ///
+ /// institutions=Biodiversity Institute of Ontario|York University returns records for specimens stored within matching institutions. + [JsonPropertyName("institutions")] + public string? Institutions { get; set; } + + /// + /// Returns all records containing matching researcher names, defined in a pipe delimited list.
+ /// Researchers include collectors and specimen identifiers. + ///
+ [JsonPropertyName("researchers")] + public string? Researchers { get; set; } + + /// + /// Returns all records collected in matching geographic sites, defined in a pipe delimited list.
+ /// Geographic sites includes countries and province/states. + ///
+ /// geo=Canada|Alaska returns records for specimens collected in the matching geographic sites. + [JsonPropertyName("geo")] + public string? Geo { get; set; } + + /// + /// Returns all specimen records containing matching marker codes defined in a pipe delimited list.
+ /// All markers for a specimen matching the search string will be returned. ie. A record with COI-5P and ITS will return sequence data for both markers even if only COI-5P was specified. + ///
+ /// + ///marker=matK|rbcL
+ /// marker=COI-5P + ///
+ [JsonPropertyName("marker")] + public string? Marker { get; set; } + + /// + public override string ToString() + { + return $"{nameof(Taxon)}: {Taxon}, {nameof(Ids)}: {Ids}, {nameof(Bin)}: {Bin}, {nameof(Container)}: {Container}, {nameof(Institutions)}: {Institutions}, {nameof(Researchers)}: {Researchers}, {nameof(Geo)}: {Geo}, {nameof(Marker)}: {Marker}"; + } + + public bool Equals(PublicApiParameters? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Taxon == other.Taxon && Ids == other.Ids && Bin == other.Bin && Container == other.Container && Institutions == other.Institutions && Researchers == other.Researchers && Geo == other.Geo && Marker == other.Marker; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((PublicApiParameters)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Taxon, Ids, Bin, Container, Institutions, Researchers, Geo, Marker); + } + + public static bool operator ==(PublicApiParameters? left, PublicApiParameters? right) + { + return Equals(left, right); + } + + public static bool operator !=(PublicApiParameters? left, PublicApiParameters? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/Sequence.cs b/SpeciesDatabaseApi/BoldSystems/Sequence.cs new file mode 100644 index 0000000..3afd562 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/Sequence.cs @@ -0,0 +1,57 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class Sequence : IEquatable +{ + [JsonPropertyName("sequenceID")] + public int SequenceId { get; set; } + + [JsonPropertyName("markercode")] + public string MarkerCode { get; set; } = string.Empty; + + [JsonPropertyName("genbank_accession")] + public string GenBankAccession { get; set; } = string.Empty; + + [JsonPropertyName("nucleotides")] + public string Nucleotides { get; set; } = string.Empty; + + /// + public override string ToString() + { + return $"{nameof(SequenceId)}: {SequenceId}, {nameof(MarkerCode)}: {MarkerCode}, {nameof(GenBankAccession)}: {GenBankAccession}, {nameof(Nucleotides)}: {Nucleotides}"; + } + + public bool Equals(Sequence? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return SequenceId == other.SequenceId && MarkerCode == other.MarkerCode && GenBankAccession == other.GenBankAccession && Nucleotides == other.Nucleotides; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Sequence)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(SequenceId, MarkerCode, GenBankAccession, Nucleotides); + } + + public static bool operator ==(Sequence? left, Sequence? right) + { + return Equals(left, right); + } + + public static bool operator !=(Sequence? left, Sequence? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/SequenceData.cs b/SpeciesDatabaseApi/BoldSystems/SequenceData.cs new file mode 100644 index 0000000..33ad12b --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/SequenceData.cs @@ -0,0 +1,60 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class SequenceData : IEquatable +{ + [JsonPropertyName("processid")] + public string ProcessId { get; set; } = string.Empty; + + [JsonPropertyName("identification")] + public string Identification { get; set; } = string.Empty; + + [JsonPropertyName("marker")] + public string Marker { get; set; } = string.Empty; + + [JsonPropertyName("accession")] + public string Accession { get; set; } = string.Empty; + + [JsonPropertyName("nucleotides")] + public string Nucleotides { get; set; } = string.Empty; + + /// + public override string ToString() + { + return $"{nameof(ProcessId)}: {ProcessId}, {nameof(Identification)}: {Identification}, {nameof(Marker)}: {Marker}, {nameof(Accession)}: {Accession}, {nameof(Nucleotides)}: {Nucleotides}"; + } + + public bool Equals(SequenceData? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return ProcessId == other.ProcessId && Identification == other.Identification && Marker == other.Marker && Accession == other.Accession && Nucleotides == other.Nucleotides; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((SequenceData)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(ProcessId, Identification, Marker, Accession, Nucleotides); + } + + public static bool operator ==(SequenceData? left, SequenceData? right) + { + return Equals(left, right); + } + + public static bool operator !=(SequenceData? left, SequenceData? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/Sequences.cs b/SpeciesDatabaseApi/BoldSystems/Sequences.cs new file mode 100644 index 0000000..d4ccd91 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/Sequences.cs @@ -0,0 +1,48 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class Sequences : IEquatable +{ + [JsonPropertyName("sequence")] + public Sequence[] Items { get; set; } = Array.Empty(); + + /// + public override string ToString() + { + return $"{nameof(Items)}: {Items.Length}"; + } + + /// + public bool Equals(Sequences? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Items.Equals(other.Items); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Sequences)obj); + } + + /// + public override int GetHashCode() + { + return Items.GetHashCode(); + } + + public static bool operator ==(Sequences? left, Sequences? right) + { + return Equals(left, right); + } + + public static bool operator !=(Sequences? left, Sequences? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/Specimen.cs b/SpeciesDatabaseApi/BoldSystems/Specimen.cs new file mode 100644 index 0000000..e01294f --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/Specimen.cs @@ -0,0 +1,52 @@ +using System; +using System.Xml.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +[XmlRoot("specimen")] +public class Specimen : IEquatable +{ + [XmlElement("url")] + public string? Url { get; set; } + + [XmlElement("collectionlocation")] + CollectionLocation? CollectionLocation { get; set; } + + /// + public override string ToString() + { + return $"{nameof(Url)}: {Url}, {nameof(CollectionLocation)}: {CollectionLocation}"; + } + + public bool Equals(Specimen? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(Url, other.Url) && Equals(CollectionLocation, other.CollectionLocation); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Specimen)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Url, CollectionLocation); + } + + public static bool operator ==(Specimen? left, Specimen? right) + { + return Equals(left, right); + } + + public static bool operator !=(Specimen? left, Specimen? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/SpecimenDesc.cs b/SpeciesDatabaseApi/BoldSystems/SpecimenDesc.cs new file mode 100644 index 0000000..af36c90 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/SpecimenDesc.cs @@ -0,0 +1,63 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class SpecimenDesc : IEquatable +{ + [JsonPropertyName("voucher_status")] + public string VoucherStatus { get; set; } = string.Empty; + + [JsonPropertyName("reproduction")] + public char? Reproduction { get; set; } + + [JsonPropertyName("sex")] + public char? Sex { get; set; } + + [JsonPropertyName("lifestage")] + public string? LifeStage { get; set; } + + [JsonPropertyName("tissue_type")] + public string? TissueType { get; set; } + + [JsonPropertyName("extrainfo")] + public string? ExtraInfo { get; set; } + + /// + public override string ToString() + { + return $"{nameof(VoucherStatus)}: {VoucherStatus}, {nameof(Reproduction)}: {Reproduction}, {nameof(Sex)}: {Sex}, {nameof(LifeStage)}: {LifeStage}, {nameof(TissueType)}: {TissueType}, {nameof(ExtraInfo)}: {ExtraInfo}"; + } + + public bool Equals(SpecimenDesc? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return VoucherStatus == other.VoucherStatus && Reproduction == other.Reproduction && Sex == other.Sex && LifeStage == other.LifeStage && TissueType == other.TissueType && ExtraInfo == other.ExtraInfo; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((SpecimenDesc)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(VoucherStatus, Reproduction, Sex, LifeStage, TissueType, ExtraInfo); + } + + public static bool operator ==(SpecimenDesc? left, SpecimenDesc? right) + { + return Equals(left, right); + } + + public static bool operator !=(SpecimenDesc? left, SpecimenDesc? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/SpecimenIdentifiers.cs b/SpeciesDatabaseApi/BoldSystems/SpecimenIdentifiers.cs new file mode 100644 index 0000000..30aa862 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/SpecimenIdentifiers.cs @@ -0,0 +1,57 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class SpecimenIdentifiers : IEquatable +{ + [JsonPropertyName("sampleid")] + public string SampleId { get; set; } = string.Empty; + + [JsonPropertyName("catalognum")] + public string CatalogNum { get; set; } = string.Empty; + + [JsonPropertyName("fieldnum")] + public string FieldNum { get; set; } = string.Empty; + + [JsonPropertyName("institution_storing")] + public string InstitutionStoring { get; set; } = string.Empty; + + /// + public override string ToString() + { + return $"{nameof(SampleId)}: {SampleId}, {nameof(CatalogNum)}: {CatalogNum}, {nameof(FieldNum)}: {FieldNum}, {nameof(InstitutionStoring)}: {InstitutionStoring}"; + } + + public bool Equals(SpecimenIdentifiers? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return SampleId == other.SampleId && CatalogNum == other.CatalogNum && FieldNum == other.FieldNum && InstitutionStoring == other.InstitutionStoring; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((SpecimenIdentifiers)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(SampleId, CatalogNum, FieldNum, InstitutionStoring); + } + + public static bool operator ==(SpecimenIdentifiers? left, SpecimenIdentifiers? right) + { + return Equals(left, right); + } + + public static bool operator !=(SpecimenIdentifiers? left, SpecimenIdentifiers? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/SpecimenRecord.cs b/SpeciesDatabaseApi/BoldSystems/SpecimenRecord.cs new file mode 100644 index 0000000..5d1cc8c --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/SpecimenRecord.cs @@ -0,0 +1,86 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class SpecimenRecord : IEquatable +{ + [JsonPropertyName("record_id")] + public string RecordId { get; set; } = string.Empty; + + [JsonPropertyName("processid")] + public string ProcessId { get; set; } = string.Empty; + + [JsonPropertyName("bin_uri")] + public string? BinUri { get; set; } + + [JsonPropertyName("specimen_identifiers")] + public SpecimenIdentifiers SpecimenIdentifiers { get; set; } = new(); + + [JsonPropertyName("taxomony")] + public Taxomony Taxomony { get; set; } = new(); + + [JsonPropertyName("specimen_desc")] + public SpecimenDesc SpecimenDesc { get; set; } = new (); + + [JsonPropertyName("collection_event")] + public CollectionEvent CollectionEvent { get; set; } = new(); + + [JsonPropertyName("tracefiles")] + public TraceFiles? TraceFiles { get; set; } + + [JsonPropertyName("sequences")] + public Sequences? Sequences { get; set; } + + [JsonPropertyName("notes")] + public string? Notes { get; set; } + + /// + public override string ToString() + { + return $"{nameof(RecordId)}: {RecordId}, {nameof(ProcessId)}: {ProcessId}, {nameof(BinUri)}: {BinUri}, {nameof(SpecimenIdentifiers)}: {SpecimenIdentifiers}, {nameof(Taxomony)}: {Taxomony}, {nameof(SpecimenDesc)}: {SpecimenDesc}, {nameof(CollectionEvent)}: {CollectionEvent}, {nameof(TraceFiles)}: {TraceFiles}, {nameof(Sequences)}: {Sequences}, {nameof(Notes)}: {Notes}"; + } + + public bool Equals(SpecimenRecord? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return RecordId == other.RecordId && ProcessId == other.ProcessId && BinUri == other.BinUri && SpecimenIdentifiers.Equals(other.SpecimenIdentifiers) && Taxomony.Equals(other.Taxomony) && SpecimenDesc.Equals(other.SpecimenDesc) && CollectionEvent.Equals(other.CollectionEvent) && Equals(TraceFiles, other.TraceFiles) && Equals(Sequences, other.Sequences) && Notes == other.Notes; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((SpecimenRecord)obj); + } + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(RecordId); + hashCode.Add(ProcessId); + hashCode.Add(BinUri); + hashCode.Add(SpecimenIdentifiers); + hashCode.Add(Taxomony); + hashCode.Add(SpecimenDesc); + hashCode.Add(CollectionEvent); + hashCode.Add(TraceFiles); + hashCode.Add(Sequences); + hashCode.Add(Notes); + return hashCode.ToHashCode(); + } + + public static bool operator ==(SpecimenRecord? left, SpecimenRecord? right) + { + return Equals(left, right); + } + + public static bool operator !=(SpecimenRecord? left, SpecimenRecord? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/SpecimenResults.cs b/SpeciesDatabaseApi/BoldSystems/SpecimenResults.cs new file mode 100644 index 0000000..cac1c25 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/SpecimenResults.cs @@ -0,0 +1,48 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class SpecimenResults : IEquatable +{ + [JsonPropertyName("bold_records")] + public BoldRecords BoldRecords { get; set; } = new(); + + /// + public override string ToString() + { + return $"{nameof(BoldRecords)}: {BoldRecords}"; + } + + public bool Equals(SpecimenResults? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return BoldRecords.Equals(other.BoldRecords); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((SpecimenResults)obj); + } + + /// + public override int GetHashCode() + { + return BoldRecords.GetHashCode(); + } + + public static bool operator ==(SpecimenResults? left, SpecimenResults? right) + { + return Equals(left, right); + } + + public static bool operator !=(SpecimenResults? left, SpecimenResults? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/Stat.cs b/SpeciesDatabaseApi/BoldSystems/Stat.cs new file mode 100644 index 0000000..9988cb2 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/Stat.cs @@ -0,0 +1,52 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class Stat : IEquatable +{ + [JsonPropertyName("count")] + public int Count { get; set; } + + [JsonPropertyName("drill_down")] + public StatDrillDown? DrillDown { get; set; } + + + /// + public override string ToString() + { + return $"{nameof(Count)}: {Count}, {nameof(DrillDown)}: {DrillDown}"; + } + + public bool Equals(Stat? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Count == other.Count && Equals(DrillDown, other.DrillDown); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Stat)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Count, DrillDown); + } + + public static bool operator ==(Stat? left, Stat? right) + { + return Equals(left, right); + } + + public static bool operator !=(Stat? left, Stat? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/StatDrillDown.cs b/SpeciesDatabaseApi/BoldSystems/StatDrillDown.cs new file mode 100644 index 0000000..7a98f78 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/StatDrillDown.cs @@ -0,0 +1,48 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class StatDrillDown : IEquatable +{ + [JsonPropertyName("entity")] + public StatEntity[] Entity { get; set; } = Array.Empty(); + + /// + public override string ToString() + { + return $"{nameof(Entity)}: {Entity}"; + } + + public bool Equals(StatDrillDown? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Entity.Equals(other.Entity); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((StatDrillDown)obj); + } + + /// + public override int GetHashCode() + { + return Entity.GetHashCode(); + } + + public static bool operator ==(StatDrillDown? left, StatDrillDown? right) + { + return Equals(left, right); + } + + public static bool operator !=(StatDrillDown? left, StatDrillDown? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/StatEntity.cs b/SpeciesDatabaseApi/BoldSystems/StatEntity.cs new file mode 100644 index 0000000..3cda389 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/StatEntity.cs @@ -0,0 +1,51 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class StatEntity : IEquatable +{ + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("records")] + public int Records { get; set; } + + /// + public override string ToString() + { + return $"{nameof(Name)}: {Name}, {nameof(Records)}: {Records}"; + } + + public bool Equals(StatEntity? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name && Records == other.Records; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((StatEntity)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Name, Records); + } + + public static bool operator ==(StatEntity? left, StatEntity? right) + { + return Equals(left, right); + } + + public static bool operator !=(StatEntity? left, StatEntity? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/Stats.cs b/SpeciesDatabaseApi/BoldSystems/Stats.cs new file mode 100644 index 0000000..7bd190b --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/Stats.cs @@ -0,0 +1,82 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class Stats : IEquatable +{ + [JsonPropertyName("total_records")] + public int TotalRecords { get; set; } + + [JsonPropertyName("records_with_species_name")] + public int RecordsWithSpeciesName { get; set; } + + [JsonPropertyName("bins")] + public Stat Bins { get; set; } = new(); + + [JsonPropertyName("countries")] + public Stat Countries { get; set; } = new(); + + [JsonPropertyName("depositories")] + public Stat Depositories { get; set; } = new(); + + [JsonPropertyName("order")] + public Stat Order { get; set; } = new(); + + [JsonPropertyName("family")] + public Stat Family { get; set; } = new(); + + [JsonPropertyName("genus")] + public Stat Genus { get; set; } = new(); + + [JsonPropertyName("species")] + public Stat Species { get; set; } = new(); + + /// + public override string ToString() + { + return $"{nameof(TotalRecords)}: {TotalRecords}, {nameof(RecordsWithSpeciesName)}: {RecordsWithSpeciesName}, {nameof(Bins)}: {Bins}, {nameof(Countries)}: {Countries}, {nameof(Depositories)}: {Depositories}, {nameof(Order)}: {Order}, {nameof(Family)}: {Family}, {nameof(Genus)}: {Genus}, {nameof(Species)}: {Species}"; + } + + public bool Equals(Stats? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return TotalRecords == other.TotalRecords && RecordsWithSpeciesName == other.RecordsWithSpeciesName && Bins.Equals(other.Bins) && Countries.Equals(other.Countries) && Depositories.Equals(other.Depositories) && Order.Equals(other.Order) && Family.Equals(other.Family) && Genus.Equals(other.Genus) && Species.Equals(other.Species); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Stats)obj); + } + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(TotalRecords); + hashCode.Add(RecordsWithSpeciesName); + hashCode.Add(Bins); + hashCode.Add(Countries); + hashCode.Add(Depositories); + hashCode.Add(Order); + hashCode.Add(Family); + hashCode.Add(Genus); + hashCode.Add(Species); + return hashCode.ToHashCode(); + } + + public static bool operator ==(Stats? left, Stats? right) + { + return Equals(left, right); + } + + public static bool operator !=(Stats? left, Stats? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/Taxomony.cs b/SpeciesDatabaseApi/BoldSystems/Taxomony.cs new file mode 100644 index 0000000..cc12e27 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/Taxomony.cs @@ -0,0 +1,66 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class Taxomony : IEquatable +{ + [JsonPropertyName("identification_provided_by")] + public string IdentificationProvidedBy { get; set; } = string.Empty; + + [JsonPropertyName("phylum")] + public TaxomonyRank? Phylum { get; set; } + + [JsonPropertyName("class")] + public TaxomonyRank? Class { get; set; } + + [JsonPropertyName("order")] + public TaxomonyRank? Order { get; set; } + + [JsonPropertyName("family")] + public TaxomonyRank? Family { get; set; } + + [JsonPropertyName("genus")] + public TaxomonyRank? Genus { get; set; } + + [JsonPropertyName("species")] + public TaxomonyRank? Species { get; set; } + + /// + public override string ToString() + { + return $"{nameof(IdentificationProvidedBy)}: {IdentificationProvidedBy}, {nameof(Phylum)}: {Phylum}, {nameof(Class)}: {Class}, {nameof(Order)}: {Order}, {nameof(Family)}: {Family}, {nameof(Genus)}: {Genus}, {nameof(Species)}: {Species}"; + } + + public bool Equals(Taxomony? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return IdentificationProvidedBy == other.IdentificationProvidedBy && Equals(Phylum, other.Phylum) && Equals(Class, other.Class) && Equals(Order, other.Order) && Equals(Family, other.Family) && Equals(Genus, other.Genus) && Equals(Species, other.Species); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Taxomony)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(IdentificationProvidedBy, Phylum, Class, Order, Family, Genus, Species); + } + + public static bool operator ==(Taxomony? left, Taxomony? right) + { + return Equals(left, right); + } + + public static bool operator !=(Taxomony? left, Taxomony? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/TaxomonyRank.cs b/SpeciesDatabaseApi/BoldSystems/TaxomonyRank.cs new file mode 100644 index 0000000..9042734 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/TaxomonyRank.cs @@ -0,0 +1,48 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class TaxomonyRank : IEquatable +{ + [JsonPropertyName("taxon")] + public Taxon Taxon { get; set; } = new(); + + /// + public override string ToString() + { + return $"{nameof(Taxon)}: {Taxon}"; + } + + public bool Equals(TaxomonyRank? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Taxon.Equals(other.Taxon); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TaxomonyRank)obj); + } + + /// + public override int GetHashCode() + { + return Taxon.GetHashCode(); + } + + public static bool operator ==(TaxomonyRank? left, TaxomonyRank? right) + { + return Equals(left, right); + } + + public static bool operator !=(TaxomonyRank? left, TaxomonyRank? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/Taxon.cs b/SpeciesDatabaseApi/BoldSystems/Taxon.cs new file mode 100644 index 0000000..5454d9d --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/Taxon.cs @@ -0,0 +1,51 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class Taxon : IEquatable +{ + [JsonPropertyName("taxID")] + public int TaxId { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// + public override string ToString() + { + return $"{nameof(TaxId)}: {TaxId}, {nameof(Name)}: {Name}"; + } + + public bool Equals(Taxon? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return TaxId == other.TaxId && Name == other.Name; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Taxon)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(TaxId, Name); + } + + public static bool operator ==(Taxon? left, Taxon? right) + { + return Equals(left, right); + } + + public static bool operator !=(Taxon? left, Taxon? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/TaxonData.cs b/SpeciesDatabaseApi/BoldSystems/TaxonData.cs new file mode 100644 index 0000000..d9e5e67 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/TaxonData.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class TaxonData : TaxonDataBasic, IEquatable +{ + #region Stats + + [JsonPropertyName("stats")] + public TaxonDataStats? Stats { get; set; } + + + [JsonPropertyName("publicrecords")] + public int? PublicRecords { get; set; } + + [JsonPropertyName("specimenrecords")] + public int? SpecimenRecords { get; set; } + + [JsonPropertyName("sequencedspecimens")] + public int? SequencedSpecimens { get; set; } + + [JsonPropertyName("barcodespecimens")] + public int? BarcodeSpecimens { get; set; } + + [JsonPropertyName("species")] + public int? Species { get; set; } + + [JsonPropertyName("barcodespecies")] + public int? BarcodeSpecies { get; set; } + #endregion + + #region Geo + + [JsonPropertyName("country")] + public Dictionary? Country { get; set; } + + [JsonPropertyName("sitemap")] + public string? Sitemap { get; set; } + #endregion + + #region Images + + [JsonPropertyName("images")] + public TaxonDataImage? Images { get; set; } + + [JsonPropertyName("representitive_image")] + public TaxonDataRepresentitiveImage? RepresentitiveImage { get; set; } + + #endregion + + #region SequencingLabs + + /// + /// sequencing labs: includes lab name, record count + /// + [JsonPropertyName("sequencinglabs")] + public Dictionary? SequencingLabs { get; set; } + + #endregion + + #region Depository + + /// + /// specimen depositories: includes depository name, record count + /// + [JsonPropertyName("depository")] + public Dictionary? Depository { get; set; } + + #endregion + + #region Thirdparty + + [JsonPropertyName("wikipedia_summary")] + public string? WikipediaSummary { get; set; } + + [JsonPropertyName("wikipedia_link")] + public string? WikipediaLink { get; set; } + + #endregion + + /// + public override string ToString() + { + return $"{base.ToString()}, {nameof(Stats)}: {Stats}, {nameof(PublicRecords)}: {PublicRecords}, {nameof(SpecimenRecords)}: {SpecimenRecords}, {nameof(SequencedSpecimens)}: {SequencedSpecimens}, {nameof(BarcodeSpecimens)}: {BarcodeSpecimens}, {nameof(Species)}: {Species}, {nameof(BarcodeSpecies)}: {BarcodeSpecies}, {nameof(Country)}: {Country}, {nameof(Sitemap)}: {Sitemap}, {nameof(Images)}: {Images}, {nameof(RepresentitiveImage)}: {RepresentitiveImage}, {nameof(SequencingLabs)}: {SequencingLabs}, {nameof(Depository)}: {Depository}, {nameof(WikipediaSummary)}: {WikipediaSummary}, {nameof(WikipediaLink)}: {WikipediaLink}"; + } + + public bool Equals(TaxonData? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return base.Equals(other) && Equals(Stats, other.Stats) && PublicRecords == other.PublicRecords && SpecimenRecords == other.SpecimenRecords && SequencedSpecimens == other.SequencedSpecimens && BarcodeSpecimens == other.BarcodeSpecimens && Species == other.Species && BarcodeSpecies == other.BarcodeSpecies && Equals(Country, other.Country) && Sitemap == other.Sitemap && Equals(Images, other.Images) && Equals(RepresentitiveImage, other.RepresentitiveImage) && Equals(SequencingLabs, other.SequencingLabs) && Equals(Depository, other.Depository) && WikipediaSummary == other.WikipediaSummary && WikipediaLink == other.WikipediaLink; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TaxonData)obj); + } + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(base.GetHashCode()); + hashCode.Add(Stats); + hashCode.Add(PublicRecords); + hashCode.Add(SpecimenRecords); + hashCode.Add(SequencedSpecimens); + hashCode.Add(BarcodeSpecimens); + hashCode.Add(Species); + hashCode.Add(BarcodeSpecies); + hashCode.Add(Country); + hashCode.Add(Sitemap); + hashCode.Add(Images); + hashCode.Add(RepresentitiveImage); + hashCode.Add(SequencingLabs); + hashCode.Add(Depository); + hashCode.Add(WikipediaSummary); + hashCode.Add(WikipediaLink); + return hashCode.ToHashCode(); + } + + public static bool operator ==(TaxonData? left, TaxonData? right) + { + return Equals(left, right); + } + + public static bool operator !=(TaxonData? left, TaxonData? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/TaxonDataBasic.cs b/SpeciesDatabaseApi/BoldSystems/TaxonDataBasic.cs new file mode 100644 index 0000000..a789623 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/TaxonDataBasic.cs @@ -0,0 +1,63 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class TaxonDataBasic : IEquatable +{ + [JsonPropertyName("taxid")] + public int? TaxId { get; set; } + + [JsonPropertyName("taxon")] + public string? Taxon { get; set; } + + [JsonPropertyName("tax_rank")] + public string? TaxRank { get; set; } + + [JsonPropertyName("tax_division")] + public string? TaxDivision { get; set; } + + [JsonPropertyName("parentid")] + public int? ParentId { get; set; } + + [JsonPropertyName("parentname")] + public string? ParentName { get; set; } + + /// + public override string ToString() + { + return $"{nameof(TaxId)}: {TaxId}, {nameof(Taxon)}: {Taxon}, {nameof(TaxRank)}: {TaxRank}, {nameof(TaxDivision)}: {TaxDivision}, {nameof(ParentId)}: {ParentId}, {nameof(ParentName)}: {ParentName}"; + } + + public bool Equals(TaxonDataBasic? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return TaxId == other.TaxId && Taxon == other.Taxon && TaxRank == other.TaxRank && TaxDivision == other.TaxDivision && ParentId == other.ParentId && ParentName == other.ParentName; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TaxonDataBasic)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(TaxId, Taxon, TaxRank, TaxDivision, ParentId, ParentName); + } + + public static bool operator ==(TaxonDataBasic? left, TaxonDataBasic? right) + { + return Equals(left, right); + } + + public static bool operator !=(TaxonDataBasic? left, TaxonDataBasic? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/TaxonDataImage.cs b/SpeciesDatabaseApi/BoldSystems/TaxonDataImage.cs new file mode 100644 index 0000000..5ac19b3 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/TaxonDataImage.cs @@ -0,0 +1,123 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class TaxonDataImage : IEquatable +{ + + [JsonPropertyName("copyright_institution")] + public string? CopyrightInstitution { get; set; } + + [JsonPropertyName("specimenid")] + public int? SpecimenId { get; set; } + + [JsonPropertyName("copyright")] + public string? Copyright { get; set; } + + [JsonPropertyName("imagequality")] + public int Imagequality { get; set; } + + [JsonPropertyName("photographer")] + public string? Photographer { get; set; } + + [JsonPropertyName("image")] + public string Image { get; set; } = string.Empty; + + [JsonPropertyName("fieldnum")] + public string? FieldNum { get; set; } + + [JsonPropertyName("sampleid")] + public string? SampleId { get; set; } + + [JsonPropertyName("mam_uri")] + public string? MamUri { get; set; } + + [JsonPropertyName("copyright_license")] + public string? CopyrightLicense { get; set; } + + [JsonPropertyName("meta")] + public string? Meta { get; set; } + + [JsonPropertyName("copyright_holder")] + public string? CopyrightHolder { get; set; } + + [JsonPropertyName("catalognum")] + public string? CatalogNum { get; set; } + + [JsonPropertyName("copyright_contact")] + public string? CopyrightContact { get; set; } + + [JsonPropertyName("copyright_year")] + public int? CopyrightYear { get; set; } + + [JsonPropertyName("taxonrep")] + public string? TaxonRep { get; set; } + + [JsonPropertyName("aspectratio")] + public decimal AspectRatio { get; set; } + + [JsonPropertyName("original")] + public bool Original { get; set; } + + [JsonPropertyName("external")] + public string? External { get; set; } + + /// + public override string ToString() + { + return $"{nameof(CopyrightInstitution)}: {CopyrightInstitution}, {nameof(SpecimenId)}: {SpecimenId}, {nameof(Copyright)}: {Copyright}, {nameof(Imagequality)}: {Imagequality}, {nameof(Photographer)}: {Photographer}, {nameof(Image)}: {Image}, {nameof(FieldNum)}: {FieldNum}, {nameof(SampleId)}: {SampleId}, {nameof(MamUri)}: {MamUri}, {nameof(CopyrightLicense)}: {CopyrightLicense}, {nameof(Meta)}: {Meta}, {nameof(CopyrightHolder)}: {CopyrightHolder}, {nameof(CatalogNum)}: {CatalogNum}, {nameof(CopyrightContact)}: {CopyrightContact}, {nameof(CopyrightYear)}: {CopyrightYear}, {nameof(TaxonRep)}: {TaxonRep}, {nameof(AspectRatio)}: {AspectRatio}, {nameof(Original)}: {Original}, {nameof(External)}: {External}"; + } + + public bool Equals(TaxonDataImage? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return CopyrightInstitution == other.CopyrightInstitution && SpecimenId == other.SpecimenId && Copyright == other.Copyright && Imagequality == other.Imagequality && Photographer == other.Photographer && Image == other.Image && FieldNum == other.FieldNum && SampleId == other.SampleId && MamUri == other.MamUri && CopyrightLicense == other.CopyrightLicense && Meta == other.Meta && CopyrightHolder == other.CopyrightHolder && CatalogNum == other.CatalogNum && CopyrightContact == other.CopyrightContact && CopyrightYear == other.CopyrightYear && TaxonRep == other.TaxonRep && AspectRatio == other.AspectRatio && Original == other.Original && External == other.External; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TaxonDataImage)obj); + } + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(CopyrightInstitution); + hashCode.Add(SpecimenId); + hashCode.Add(Copyright); + hashCode.Add(Imagequality); + hashCode.Add(Photographer); + hashCode.Add(Image); + hashCode.Add(FieldNum); + hashCode.Add(SampleId); + hashCode.Add(MamUri); + hashCode.Add(CopyrightLicense); + hashCode.Add(Meta); + hashCode.Add(CopyrightHolder); + hashCode.Add(CatalogNum); + hashCode.Add(CopyrightContact); + hashCode.Add(CopyrightYear); + hashCode.Add(TaxonRep); + hashCode.Add(AspectRatio); + hashCode.Add(Original); + hashCode.Add(External); + return hashCode.ToHashCode(); + } + + public static bool operator ==(TaxonDataImage? left, TaxonDataImage? right) + { + return Equals(left, right); + } + + public static bool operator !=(TaxonDataImage? left, TaxonDataImage? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/TaxonDataRepresentitiveImage.cs b/SpeciesDatabaseApi/BoldSystems/TaxonDataRepresentitiveImage.cs new file mode 100644 index 0000000..64b04e4 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/TaxonDataRepresentitiveImage.cs @@ -0,0 +1,52 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class TaxonDataRepresentitiveImage : IEquatable +{ + [JsonPropertyName("image")] + public string Image { get; set; } = string.Empty; + + [JsonPropertyName("apectratio")] + public decimal AspectRatio { get; set; } + + /// + public override string ToString() + { + return $"{nameof(Image)}: {Image}, {nameof(AspectRatio)}: {AspectRatio}"; + } + + + public bool Equals(TaxonDataRepresentitiveImage? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Image == other.Image && AspectRatio == other.AspectRatio; + } + /// + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TaxonDataRepresentitiveImage)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Image, AspectRatio); + } + + public static bool operator ==(TaxonDataRepresentitiveImage? left, TaxonDataRepresentitiveImage? right) + { + return Equals(left, right); + } + + public static bool operator !=(TaxonDataRepresentitiveImage? left, TaxonDataRepresentitiveImage? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/TaxonDataStats.cs b/SpeciesDatabaseApi/BoldSystems/TaxonDataStats.cs new file mode 100644 index 0000000..2256112 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/TaxonDataStats.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class TaxonDataStats : IEquatable +{ + [JsonPropertyName("publicspecies")] + public int PublicSpecies { get; set; } + + [JsonPropertyName("publicbins")] + public int PublicBins { get; set; } + + [JsonPropertyName("publicmarkersequences")] + public Dictionary? PublicMarkerSequences { get; set; } + + /// + public override string ToString() + { + return $"{nameof(PublicSpecies)}: {PublicSpecies}, {nameof(PublicBins)}: {PublicBins}, {nameof(PublicMarkerSequences)}: {PublicMarkerSequences}"; + } + + public bool Equals(TaxonDataStats? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return PublicSpecies == other.PublicSpecies && PublicBins == other.PublicBins && Equals(PublicMarkerSequences, other.PublicMarkerSequences); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TaxonDataStats)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(PublicSpecies, PublicBins, PublicMarkerSequences); + } + + public static bool operator ==(TaxonDataStats? left, TaxonDataStats? right) + { + return Equals(left, right); + } + + public static bool operator !=(TaxonDataStats? left, TaxonDataStats? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/TaxonSearch.cs b/SpeciesDatabaseApi/BoldSystems/TaxonSearch.cs new file mode 100644 index 0000000..0366734 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/TaxonSearch.cs @@ -0,0 +1,51 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class TaxonSearch : IEquatable +{ + [JsonPropertyName("top_matched_names")] + public TaxonData[] TopMatchedNames { get; set; } = Array.Empty(); + + [JsonPropertyName("total_matched_names")] + public int TotalMatchedNames { get; set; } + + /// + public override string ToString() + { + return $"{nameof(TopMatchedNames)}: {TopMatchedNames}, {nameof(TotalMatchedNames)}: {TotalMatchedNames}"; + } + + public bool Equals(TaxonSearch? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return TopMatchedNames.Equals(other.TopMatchedNames) && TotalMatchedNames == other.TotalMatchedNames; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TaxonSearch)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(TopMatchedNames, TotalMatchedNames); + } + + public static bool operator ==(TaxonSearch? left, TaxonSearch? right) + { + return Equals(left, right); + } + + public static bool operator !=(TaxonSearch? left, TaxonSearch? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/TraceFile.cs b/SpeciesDatabaseApi/BoldSystems/TraceFile.cs new file mode 100644 index 0000000..b9b61ee --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/TraceFile.cs @@ -0,0 +1,66 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class TraceFile : IEquatable +{ + [JsonPropertyName("trace_id")] + public int TraceId { get; set; } + + [JsonPropertyName("run_date")] + public DateTime RunDate { get; set; } + + [JsonPropertyName("sequencing_center")] + public string SequencingCenter { get; set; } = string.Empty; + + [JsonPropertyName("direction")] + public char Direction { get; set; } = char.MinValue; + + [JsonPropertyName("seq_primer")] + public string SeqPrimer { get; set; } = string.Empty; + + [JsonPropertyName("trace_name")] + public Uri? TraceName { get; set; } + + [JsonPropertyName("markercode")] + public string MarkerCode { get; set; } = string.Empty; + + /// + public override string ToString() + { + return $"{nameof(TraceId)}: {TraceId}, {nameof(RunDate)}: {RunDate}, {nameof(SequencingCenter)}: {SequencingCenter}, {nameof(Direction)}: {Direction}, {nameof(SeqPrimer)}: {SeqPrimer}, {nameof(TraceName)}: {TraceName}, {nameof(MarkerCode)}: {MarkerCode}"; + } + + public bool Equals(TraceFile? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return TraceId == other.TraceId && RunDate.Equals(other.RunDate) && SequencingCenter == other.SequencingCenter && Direction == other.Direction && SeqPrimer == other.SeqPrimer && Equals(TraceName, other.TraceName) && MarkerCode == other.MarkerCode; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TraceFile)obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(TraceId, RunDate, SequencingCenter, Direction, SeqPrimer, TraceName, MarkerCode); + } + + public static bool operator ==(TraceFile? left, TraceFile? right) + { + return Equals(left, right); + } + + public static bool operator !=(TraceFile? left, TraceFile? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/BoldSystems/TraceFiles.cs b/SpeciesDatabaseApi/BoldSystems/TraceFiles.cs new file mode 100644 index 0000000..edf38b4 --- /dev/null +++ b/SpeciesDatabaseApi/BoldSystems/TraceFiles.cs @@ -0,0 +1,48 @@ +using System; +using System.Text.Json.Serialization; + +namespace SpeciesDatabaseApi.BoldSystems; + +public class TraceFiles : IEquatable +{ + [JsonPropertyName("read")] + public TraceFile[] Traces { get; set; } = Array.Empty(); + + /// + public override string ToString() + { + return $"{nameof(Traces)}: {Traces.Length}"; + } + + public bool Equals(TraceFiles? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Traces.Equals(other.Traces); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TraceFiles)obj); + } + + /// + public override int GetHashCode() + { + return Traces.GetHashCode(); + } + + public static bool operator ==(TraceFiles? left, TraceFiles? right) + { + return Equals(left, right); + } + + public static bool operator !=(TraceFiles? left, TraceFiles? right) + { + return !Equals(left, right); + } +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/Extensions/StringExtensions.cs b/SpeciesDatabaseApi/Extensions/StringExtensions.cs index 3a688f8..ed38c6e 100644 --- a/SpeciesDatabaseApi/Extensions/StringExtensions.cs +++ b/SpeciesDatabaseApi/Extensions/StringExtensions.cs @@ -22,4 +22,23 @@ public static string PrependSpaceByUpperChar(string str) return sb.ToString(); } + + /// + /// Prepend a defined char on a found Upper char, first char excluded. + /// + /// + public static string PrependCharByUpperChar(string str, char prependChar) + { + if (str.Length <= 1) return str; + var sb = new StringBuilder(); + + sb.Append(str[0]); + for (var i = 1; i < str.Length; i++) + { + if (char.IsUpper(str[i])) sb.Append(prependChar); + sb.Append(str[i]); + } + + return sb.ToString(); + } } \ No newline at end of file diff --git a/SpeciesDatabaseApi/Iucn/IucnClient.cs b/SpeciesDatabaseApi/Iucn/IucnClient.cs index c07c951..64b59ef 100644 --- a/SpeciesDatabaseApi/Iucn/IucnClient.cs +++ b/SpeciesDatabaseApi/Iucn/IucnClient.cs @@ -40,7 +40,7 @@ public IucnClient(string apiToken, HttpClient? httpClient = null) : base(Default #endregion - #region Attributes + #region Methods /// /// Check what version of the IUCN Red List is driving the API /// @@ -614,29 +614,31 @@ public IucnClient(string apiToken, HttpClient? httpClient = null) : base(Default return GetJsonAsync($"weblink/{EscapeDataString(specieName)}", token); } - /// - /// Gets an link which will redirect to the selected specie by name - /// - /// - /// Url - public string GetSpecieRedirectLink(string specieName) - { - return GetRawRequestUrl($"website/{EscapeDataString(specieName)}"); - } - - /// - /// Gets an link which will redirect to the selected specie by taxonId and/or region - /// - /// The taxonId of the specie - /// The region identifier, use null or global for the global link - /// Url - public string GetSpecieRedirectLink(int taxonId, string? regionIdentifier = null) - { - return string.IsNullOrWhiteSpace(regionIdentifier) - ? GetRawRequestUrl($"taxonredirect/{taxonId}") - : GetRawRequestUrl($"taxonredirect/{taxonId}/{EscapeDataString(regionIdentifier)}"); - - } - - #endregion + #endregion + + #region Static Methods + /// + /// Gets an link which will redirect to the selected specie by name + /// + /// + /// Url + public static string GetSpecieRedirectLink(string specieName) + { + return $"{DefaultApiAddress}/website/{EscapeDataString(specieName)}"; + } + + /// + /// Gets an link which will redirect to the selected specie by taxonId and/or region + /// + /// The taxonId of the specie + /// The region identifier, use null or global for the global link + /// Url + public static string GetSpecieRedirectLink(int taxonId, string? regionIdentifier = null) + { + return string.IsNullOrWhiteSpace(regionIdentifier) + ? $"{DefaultApiAddress}/taxonredirect/{taxonId}" + : $"{DefaultApiAddress}/taxonredirect/{taxonId}/{EscapeDataString(regionIdentifier)}"; + + } + #endregion } \ No newline at end of file diff --git a/SpeciesDatabaseApi/MarineRegions/MarineRegionsClient.cs b/SpeciesDatabaseApi/MarineRegions/MarineRegionsClient.cs index c8dd354..aae3242 100644 --- a/SpeciesDatabaseApi/MarineRegions/MarineRegionsClient.cs +++ b/SpeciesDatabaseApi/MarineRegions/MarineRegionsClient.cs @@ -22,7 +22,7 @@ public class MarineRegionsClient : BaseClient /// /// The Api default address /// - public static readonly Uri DefaultApiAddress = new("https://marineregions.org/rest"); + public static readonly Uri DefaultApiAddress = new("https://www.marineregions.org/rest"); #endregion #region Properties diff --git a/SpeciesDatabaseApi/MarineSpecies/WormsClient.cs b/SpeciesDatabaseApi/MarineSpecies/WormsClient.cs index 3c82d8d..d8b584b 100644 --- a/SpeciesDatabaseApi/MarineSpecies/WormsClient.cs +++ b/SpeciesDatabaseApi/MarineSpecies/WormsClient.cs @@ -22,7 +22,7 @@ public class WormsClient : BaseClient /// /// The Api default address /// - public static readonly Uri DefaultApiAddress = new("https://marinespecies.org/rest"); + public static readonly Uri DefaultApiAddress = new("https://www.marinespecies.org/rest"); #endregion #region Properties @@ -401,5 +401,30 @@ public WormsClient(HttpClient? httpClient = null) : base(DefaultApiAddress, http { return GetJsonAsync($"AphiaVernacularsByAphiaID/{aphiaId}", token); } - #endregion + #endregion + + #region Static Methods + + /// + /// Gets the link for an aphia based on aphia name
+ /// (This assumes that you have no idea if the taxon exists, or you do not want to look up the AphiaID) + ///
+ /// The aphia name to look for + /// + public static string GetAphiaLink(string aphiaName) + { + return $"https://www.marinespecies.org/aphia.php?p=taxlist&tName={EscapeDataString(aphiaName)}"; + } + + /// + /// Gets the link for an aphia based on aphiaId + /// + /// The aphiaId to look for + /// + public static string GetAphiaLink(int aphiaId) + { + return $"https://www.marinespecies.org/aphia.php?p=taxdetails&id={aphiaId}"; + } + + #endregion } \ No newline at end of file diff --git a/SpeciesDatabaseApi/RequestContentType.cs b/SpeciesDatabaseApi/RequestContentType.cs new file mode 100644 index 0000000..873ab9c --- /dev/null +++ b/SpeciesDatabaseApi/RequestContentType.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; + +namespace SpeciesDatabaseApi; + +public enum RequestContentType +{ + Raw, + + [Description("application/json")] + Json, + + [Description("application/xml")] + Xml +} \ No newline at end of file diff --git a/SpeciesDatabaseApi/SpeciesPlus/SpeciesPlusClient.cs b/SpeciesDatabaseApi/SpeciesPlus/SpeciesPlusClient.cs index c6b1e46..464282a 100644 --- a/SpeciesDatabaseApi/SpeciesPlus/SpeciesPlusClient.cs +++ b/SpeciesDatabaseApi/SpeciesPlus/SpeciesPlusClient.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using SpeciesDatabaseApi.Extensions; -using static System.Formats.Asn1.AsnWriter; namespace SpeciesDatabaseApi.SpeciesPlus; diff --git a/SpeciesDatabaseCmd/BoldSystemsCommand.cs b/SpeciesDatabaseCmd/BoldSystemsCommand.cs new file mode 100644 index 0000000..4821e44 --- /dev/null +++ b/SpeciesDatabaseCmd/BoldSystemsCommand.cs @@ -0,0 +1,281 @@ +using System.CommandLine; +using SpeciesDatabaseApi.BoldSystems; + +namespace SpeciesDatabaseCmd; + +internal static class BoldSystemsCommand +{ + private static readonly BoldSystemsClient Client = new(); + + private static readonly Argument StatsDataTypeArgument = new("data-type", () => BoldStatsDataType.DrillDown, "Returns all records in one of the specified formats."); + private static readonly Option TaxonOption = new(new[] { "--taxon" }, "Returns all records containing matching taxa, defined in a pipe delimited list."); + private static readonly Option IdsOption = new(new[] { "--ids" }, "Returns all records containing matching IDs, defined in a pipe delimited list."); + private static readonly Option BinOption = new(new[] { "--bin" }, "Returns all records contained in matching BINs, defined in a pipe delimited list."); + private static readonly Option ContainerOption = new(new[] { "--container" }, "Returns all records contained in matching projects or datasets, in a pipe delimited list."); + private static readonly Option InstitutionsOption = new(new[] { "--institutions" }, "Returns all records stored in matching institutions, defined in a pipe delimited list."); + private static readonly Option ResearchersOption = new(new[] { "--researchers" }, "Returns all records containing matching researcher names, defined in a pipe delimited list."); + private static readonly Option GeoOption = new(new[] { "--geo" }, "Returns all records collected in matching geographic sites, defined in a pipe delimited list."); + private static readonly Option MarkerOption = new(new[] { "--marker" }, "Returns all specimen records containing matching marker codes defined in a pipe delimited list."); + + private static readonly Argument FilePathArgument = new("file-path", "Specifies where to save the file."); + + + private static readonly Argument DataBaseArgument = new("db", "Specifies the identification database to query."); + private static readonly Argument SequenceArgument = new("sequence", "Specifies the query sequence."); + private static readonly Argument TaxIdArgument = new("tax-id", "The taxId to search for."); + private static readonly Argument DataTypesArgument = new("data-types", () => BoldDataTypesEnum.Basic, "Specifies the datatypes that will be returned."); + + private static readonly Argument TaxNameArgument = new("tax-name", "The tax name to search for."); + private static readonly Argument FuzzyArgument = new("fuzzy", () => false, "Specifies if the search should only find exact matches."); + + private static readonly Option IncludeTreeOption = new(new[] { "-t", "--include-tree" }, () => false, "Returns a list containing information for parent taxa as well as the specified taxon."); + + internal static Command CreateCommand() + { + var command = new Command(Client.ClientAcronym.ToUpper(), Program.GetRootCommandDescription(Client)) + { + StatsCommand(), + SpecimenCommand(), + SequencesCommand(), + SpecimenAndSequenceCommand(), + + DownloadTraceFileCommand(), + + TaxonDataByIdCommand(), + TaxonDataByNameCommand(), + CoiMatchesCommand(), + }; + + return command; + } + + private static Command StatsCommand() + { + var command = new Command("Stats", "Users only interested in count data for a given query can now use the API to retrieve the summary information that is provided by BOLD public searches.") + { + StatsDataTypeArgument, + TaxonOption, + IdsOption, + BinOption, + ContainerOption, + InstitutionsOption, + ResearchersOption, + GeoOption + }; + + command.SetHandler(async (dataType, taxon, ids, bin, container, institutions, researchers, geo) => + { + var result = await Client.GetStats(new PublicApiParameters + { + Taxon = taxon, + Ids = ids, + Bin = bin, + Container = container, + Institutions = institutions, + Researchers = researchers, + Geo = geo + }, dataType); + Program.Print(result); + + }, StatsDataTypeArgument, TaxonOption, IdsOption, BinOption, ContainerOption, InstitutionsOption, ResearchersOption, GeoOption); + + return command; + } + + private static Command SpecimenCommand() + { + var command = new Command("Specimen", "Users can query the system to retrieve matching specimen data records for a combination of parameters.") + { + TaxonOption, + IdsOption, + BinOption, + ContainerOption, + InstitutionsOption, + ResearchersOption, + GeoOption + }; + + command.SetHandler(async (taxon, ids, bin, container, institutions, researcher, geo) => + { + var result = await Client.GetSpecimen(new PublicApiParameters + { + Taxon = taxon, + Ids = ids, + Bin = bin, + Container = container, + Institutions = institutions, + Researchers = researcher, + Geo = geo + }); + Program.Print(result?.BoldRecords.Records); + + }, TaxonOption, IdsOption, BinOption, ContainerOption, InstitutionsOption, ResearchersOption, GeoOption); + + return command; + } + + private static Command SequencesCommand() + { + var command = new Command("Sequences", "Users can query the system to retrieve matching sequences for a combination of parameters.") + { + TaxonOption, + IdsOption, + BinOption, + ContainerOption, + InstitutionsOption, + ResearchersOption, + GeoOption, + MarkerOption + }; + + command.SetHandler(async (taxon, ids, bin, container, institutions, researcher, geo, marker) => + { + var result = await Client.GetSequences(new PublicApiParameters + { + Taxon = taxon, + Ids = ids, + Bin = bin, + Container = container, + Institutions = institutions, + Researchers = researcher, + Geo = geo, + Marker = marker + }); + Program.Print(result); + + }, TaxonOption, IdsOption, BinOption, ContainerOption, InstitutionsOption, ResearchersOption, GeoOption, MarkerOption); + + return command; + } + + private static Command SpecimenAndSequenceCommand() + { + var command = new Command("SpecimenAndSequence", "Users can query the system to retrieve matching specimen data and sequence records for a combination of parameters.") + { + TaxonOption, + IdsOption, + BinOption, + ContainerOption, + InstitutionsOption, + ResearchersOption, + GeoOption, + MarkerOption + }; + + command.SetHandler(async (taxon, ids, bin, container, institutions, researcher, geo, marker) => + { + var result = await Client.GetSpecimenAndSequence(new PublicApiParameters + { + Taxon = taxon, + Ids = ids, + Bin = bin, + Container = container, + Institutions = institutions, + Researchers = researcher, + Geo = geo, + Marker = marker + }); + Program.Print(result?.BoldRecords.Records); + + }, TaxonOption, IdsOption, BinOption, ContainerOption, InstitutionsOption, ResearchersOption, GeoOption, MarkerOption); + + return command; + } + + private static Command DownloadTraceFileCommand() + { + var command = new Command("DownloadTraceFile", "Users can query the system to retrieve matching specimen data records for a combination of parameters.") + { + FilePathArgument, + TaxonOption, + IdsOption, + BinOption, + ContainerOption, + ResearchersOption, + GeoOption, + MarkerOption + }; + + command.SetHandler(async (filePath, taxon, ids, bin, container, researcher, geo, marker) => + { + await Client.DownloadTraceFile(new PublicApiParameters + { + Taxon = taxon, + Ids = ids, + Bin = bin, + Container = container, + Researchers = researcher, + Geo = geo, + Marker = marker + }, filePath.FullName); + + }, FilePathArgument, TaxonOption, IdsOption, BinOption, ContainerOption, ResearchersOption, GeoOption, MarkerOption); + + return command; + } + + private static Command TaxonDataByIdCommand() + { + var command = new Command("TaxonDataById", "Retrieves taxonomy information by BOLD taxonomy ID.") + { + TaxIdArgument, + DataTypesArgument, + IncludeTreeOption + }; + + command.SetHandler(async (taxId, dataTypes, includeTree) => + { + if (includeTree) + { + var result = await Client.GetTaxonDataIncludeTree(taxId, dataTypes); + Program.Print(result); + } + else + { + var result = await Client.GetTaxonData(taxId, dataTypes); + Program.Print(result); + } + + }, TaxIdArgument, DataTypesArgument, IncludeTreeOption); + + return command; + } + + private static Command TaxonDataByNameCommand() + { + var command = new Command("TaxonDataByName", "Retrieves taxonomy information by BOLD taxonomy name.") + { + TaxNameArgument, + FuzzyArgument, + }; + + command.SetHandler(async (taxName, fuzzy) => + { + var result = await Client.GetTaxonData(taxName, fuzzy); + Program.Print(result?.TopMatchedNames); + + }, TaxNameArgument, FuzzyArgument); + + return command; + } + + private static Command CoiMatchesCommand() + { + var command = new Command("CoiMatches", "Query the COI ID Engine.") + { + DataBaseArgument, + SequenceArgument, + }; + + command.SetHandler(async (db, sequence) => + { + var result = await Client.GetCoiMatches(db, sequence); + Program.Print(result); + }, DataBaseArgument, SequenceArgument); + + return command; + } + + + +} \ No newline at end of file diff --git a/SpeciesDatabaseCmd/Program.cs b/SpeciesDatabaseCmd/Program.cs index 84a8e0d..c022846 100644 --- a/SpeciesDatabaseCmd/Program.cs +++ b/SpeciesDatabaseCmd/Program.cs @@ -10,10 +10,11 @@ static async Task Main(string[] args) { var rootCommand = new RootCommand("Query specific taxonomy and species database") { - WormsCommand.CreateCommand(), + BoldSystemsCommand.CreateCommand(), IucnCommand.CreateCommand(), MarineRegionsCommand.CreateCommand(), - SpeciesPlusCommand.CreateCommand() + SpeciesPlusCommand.CreateCommand(), + WormsCommand.CreateCommand(), }; try diff --git a/SpeciesDatabaseCmd/Properties/launchSettings.json b/SpeciesDatabaseCmd/Properties/launchSettings.json index ef3cec1..7ee2a7a 100644 --- a/SpeciesDatabaseCmd/Properties/launchSettings.json +++ b/SpeciesDatabaseCmd/Properties/launchSettings.json @@ -63,6 +63,22 @@ "SPECIES+ TaxonConcepts carcharodon carcharias": { "commandName": "Project", "commandLineArgs": "SPECIES+ TaxonConcepts apikey \"carcharodon carcharias\" --with-descendants --with-eu-listings" + }, + "BOLDSYSTEMS CoiMatches COX1_SPECIES seq": { + "commandName": "Project", + "commandLineArgs": "BOLDSYSTEMS CoiMatches COX1_SPECIES \"NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNTCTTTAGATTTTATTTTTGGAGCTTGGTCTGGCATAGTAGGCACCGCCCTAAGACTTATTATTCGGGCTGAATTAGGACAACCTGGTAGACTTATTGGTGATGATCAAATTTACAACGTGGTCGTAACAGCTCATGCTTTTGTGATAATTTTTTTTATAGTTATGCCCATTATAATTGGTGGATTTGGAAATTGACTTGTTCCCTTAATATTAGGTGCTCCTGATATAGCTTTTCCTCGTATAAATAATATAAGATTTTGACTTCTTCCACCTTCTTTAACTCTTCTCCTATCCAGAGGAATAGTTGAAAGAGGTGTTGGCACAGGATGAACTGTTTATCCTCCTTTAGCTGCTGGAATCGCCCATGCAGGCGCTTCTGTGGACTTAGGAATTTTTTCTCTTCATATAGCGGGAGCTTCTTCTATTTTAGGGGCGGTAAATTTTATTACTACTTCTATTAATATGCGTGCCAATGGTATAACTTTAGATCGAATACCTTTATTTGTCTGATCCGTTTTTATTACTGCTATTCTTTTACTACTCTCTCTTCCCGTTTTAGCAGGGGCAATCACAATACTTCTCACTGACCGTAACTTAAATACTTCTTTCTTTGACCCCGCTGGAGGAGGAGATCCATTCTTTATCAACATAAATGCC\"" + }, + "BOLDSYSTEMS TaxonData 88899 Images": { + "commandName": "Project", + "commandLineArgs": "BOLDSYSTEMS TaxonData 88899 Images" + }, + "BOLDSYSTEMS Specimen --taxon Aves --geo Canada": { + "commandName": "Project", + "commandLineArgs": "BOLDSYSTEMS Specimen --taxon Aves --geo Canada" + }, + "BOLDSYSTEMS SpecimenAndSequence --taxon Aves --geo Canada": { + "commandName": "Project", + "commandLineArgs": "BOLDSYSTEMS SpecimenAndSequence --taxon Aves --geo Canada" } } } \ No newline at end of file diff --git a/SpeciesDatabaseCmd/icon.ico b/SpeciesDatabaseCmd/icon.ico new file mode 100644 index 0000000..d717bb2 Binary files /dev/null and b/SpeciesDatabaseCmd/icon.ico differ diff --git a/icon.png b/icon.png deleted file mode 100644 index d57460e..0000000 Binary files a/icon.png and /dev/null differ diff --git a/images/banner.png b/images/banner.png new file mode 100644 index 0000000..ad205e4 Binary files /dev/null and b/images/banner.png differ diff --git a/images/icon-128.png b/images/icon-128.png new file mode 100644 index 0000000..756b8fc Binary files /dev/null and b/images/icon-128.png differ diff --git a/images/icon-32.png b/images/icon-32.png new file mode 100644 index 0000000..d0f41a5 Binary files /dev/null and b/images/icon-32.png differ diff --git a/images/icon-512.png b/images/icon-512.png new file mode 100644 index 0000000..70361b9 Binary files /dev/null and b/images/icon-512.png differ diff --git a/images/icon-64.png b/images/icon-64.png new file mode 100644 index 0000000..67dcd68 Binary files /dev/null and b/images/icon-64.png differ diff --git a/images/icon.ico b/images/icon.ico new file mode 100644 index 0000000..d717bb2 Binary files /dev/null and b/images/icon.ico differ