-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d212d60
commit 8a1abb2
Showing
33 changed files
with
2,197 additions
and
13 deletions.
There are no files selected for viewing
111 changes: 111 additions & 0 deletions
111
src/FlightRecorder.BusinessLogic/Api/AeroDataBox/AeroDataBoxFlightsApi.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
using FlightRecorder.Entities.Api; | ||
using FlightRecorder.Entities.Interfaces; | ||
using FlightRecorder.Entities.Logging; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
|
||
namespace FlightRecorder.BusinessLogic.Api.AeroDataBox | ||
{ | ||
public class AeroDataBoxFlightsApi : ExternalApiBase, IFlightsApi | ||
{ | ||
private const string DateFormat = "yyyy-MM-dd"; | ||
|
||
private readonly string _baseAddress; | ||
private readonly string _host; | ||
private readonly string _key; | ||
|
||
public AeroDataBoxFlightsApi( | ||
IFlightRecorderLogger logger, | ||
IFlightRecorderHttpClient client, | ||
string url, | ||
string key) | ||
: base(logger, client) | ||
{ | ||
_baseAddress = url; | ||
_key = key; | ||
|
||
// The URL contains the protocol, host and base route (if any), but we need to extract the host name only | ||
// to pass in the headers as the RapidAPI host, so capture the host and the full URL | ||
Uri uri = new Uri(url); | ||
_host = uri.Host; | ||
} | ||
|
||
/// <summary> | ||
/// Look up flight details given a flight number | ||
/// </summary> | ||
/// <param name="number"></param> | ||
/// <param name="date"></param> | ||
/// <returns></returns> | ||
public async Task<Dictionary<ApiPropertyType, string>> LookupFlightByNumber(string number) | ||
{ | ||
Logger.LogMessage(Severity.Info, $"Looking up flight {number}"); | ||
var properties = await MakeApiRequest(number); | ||
return properties; | ||
} | ||
|
||
/// <summary> | ||
/// Look up flight details given a flight number and date | ||
/// </summary> | ||
/// <param name="number"></param> | ||
/// <param name="date"></param> | ||
/// <returns></returns> | ||
public async Task<Dictionary<ApiPropertyType, string>> LookupFlightByNumberAndDate(string number, DateTime date) | ||
{ | ||
Logger.LogMessage(Severity.Info, $"Looking up flight {number} on {date}"); | ||
var parameters = $"{number}/{date.ToString(DateFormat)}"; | ||
var properties = await MakeApiRequest(parameters); | ||
return properties; | ||
} | ||
|
||
/// <summary> | ||
/// Make a request for flight details using the specified parameters | ||
/// </summary> | ||
/// <param name="parameters"></param> | ||
/// <returns></returns> | ||
private async Task<Dictionary<ApiPropertyType, string>> MakeApiRequest(string parameters) | ||
{ | ||
Dictionary<ApiPropertyType, string> properties = null; | ||
|
||
// Definte the properties to be | ||
List<ApiPropertyDefinition> definitions = new List<ApiPropertyDefinition> | ||
{ | ||
new ApiPropertyDefinition{ PropertyType = ApiPropertyType.DepartureAirportIATA, JsonPath = "departure.airport.iata" }, | ||
new ApiPropertyDefinition{ PropertyType = ApiPropertyType.DestinationAirportIATA, JsonPath = "arrival.airport.iata" }, | ||
new ApiPropertyDefinition{ PropertyType = ApiPropertyType.AirlineName, JsonPath = "airline.name" } | ||
}; | ||
|
||
// Set the headers | ||
SetHeaders(new Dictionary<string, string> | ||
{ | ||
{ "X-RapidAPI-Host", _host }, | ||
{ "X-RapidAPI-Key", _key } | ||
}); | ||
|
||
// Make a request for the data from the API | ||
var url = $"{_baseAddress}{parameters}"; | ||
var node = await SendRequest(url); | ||
|
||
if (node != null) | ||
{ | ||
try | ||
{ | ||
// Extract the required properties from the response | ||
properties = GetPropertyValuesFromResponse(node![0], definitions); | ||
|
||
// Log the properties dictionary | ||
LogProperties(properties!); | ||
} | ||
catch (Exception ex) | ||
{ | ||
var message = $"Error processing response: {ex.Message}"; | ||
Logger.LogMessage(Severity.Error, message); | ||
Logger.LogException(ex); | ||
properties = null; | ||
} | ||
} | ||
|
||
return properties; | ||
} | ||
} | ||
} |
143 changes: 143 additions & 0 deletions
143
src/FlightRecorder.BusinessLogic/Api/ExternalApiBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
using FlightRecorder.Entities.Api; | ||
using FlightRecorder.Entities.Interfaces; | ||
using FlightRecorder.Entities.Logging; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Text.Json.Nodes; | ||
using System.Threading.Tasks; | ||
|
||
namespace FlightRecorder.BusinessLogic.Api | ||
{ | ||
public abstract class ExternalApiBase | ||
{ | ||
private readonly IFlightRecorderHttpClient _client; | ||
protected IFlightRecorderLogger Logger { get; private set; } | ||
|
||
protected ExternalApiBase(IFlightRecorderLogger logger, IFlightRecorderHttpClient client) | ||
{ | ||
Logger = logger; | ||
_client = client; | ||
} | ||
|
||
/// <summary> | ||
/// Set the request headers | ||
/// </summary> | ||
/// <param name="headers"></param> | ||
protected virtual void SetHeaders(Dictionary<string, string> headers) | ||
=> _client.SetHeaders(headers); | ||
|
||
/// <summary> | ||
/// Make a request to the specified URL and return the response properties as a JSON DOM | ||
/// </summary> | ||
/// <param name="endpoint"></param> | ||
/// <returns></returns> | ||
protected virtual async Task<JsonNode> SendRequest(string endpoint) | ||
{ | ||
JsonNode node = null; | ||
|
||
try | ||
{ | ||
// Make a request for the data from the API | ||
using (var response = await _client.GetAsync(endpoint)) | ||
{ | ||
// Check the request was successful | ||
if (response.IsSuccessStatusCode) | ||
{ | ||
// Read the response, parse to a JSON DOM | ||
var json = await response.Content.ReadAsStringAsync(); | ||
node = JsonNode.Parse(json); | ||
} | ||
} | ||
} | ||
catch (Exception ex) | ||
{ | ||
var message = $"Error calling {endpoint}: {ex.Message}"; | ||
Logger.LogMessage(Severity.Error, message); | ||
Logger.LogException(ex); | ||
node = null; | ||
} | ||
|
||
return node; | ||
} | ||
|
||
/// <summary> | ||
/// Given a JSON node and the path to an element, return the value at that element | ||
/// </summary> | ||
/// <param name="node"></param> | ||
/// <param name="path"></param> | ||
/// <returns></returns> | ||
private static string GetPropertyValueByPath(JsonNode node, ApiPropertyDefinition definition) | ||
{ | ||
string value = null; | ||
var current = node; | ||
|
||
// Walk the JSON document to the requested element | ||
foreach (var element in definition.JsonPath.Split(".", StringSplitOptions.RemoveEmptyEntries)) | ||
{ | ||
current = current?[element]; | ||
} | ||
|
||
// Check the element is a type that can yield a value | ||
if (current is JsonValue) | ||
{ | ||
// Extract the value as a string and if "cleanup" has been specified perform it | ||
value = current?.GetValue<string>(); | ||
} | ||
|
||
return value; | ||
} | ||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <param name="node"></param> | ||
/// <param name="propertyDefinitions"></param> | ||
protected virtual Dictionary<ApiPropertyType, string> GetPropertyValuesFromResponse(JsonNode node, IEnumerable<ApiPropertyDefinition> propertyDefinitions) | ||
{ | ||
var properties = new Dictionary<ApiPropertyType, string>(); | ||
|
||
// Iterate over the property definitions | ||
foreach (var definition in propertyDefinitions) | ||
{ | ||
// Get the value from | ||
var value = GetPropertyValueByPath(node, definition); | ||
properties.Add(definition.PropertyType, value ?? ""); | ||
} | ||
|
||
// Log the properties dictionary | ||
LogProperties(properties!); | ||
|
||
return properties; | ||
} | ||
|
||
/// <summary> | ||
/// Log the content of a properties dictionary resulting from an external API call | ||
/// </summary> | ||
/// <param name="properties"></param> | ||
[ExcludeFromCodeCoverage] | ||
protected void LogProperties(Dictionary<ApiPropertyType, string> properties) | ||
{ | ||
// Check the properties dictionary isn't NULL | ||
if (properties != null) | ||
{ | ||
// Not a NULL dictionary, so iterate over all the properties it contains | ||
foreach (var property in properties) | ||
{ | ||
// Construct a message containing the property name and the value, replacing | ||
// null values with "NULL" | ||
var value = property.Value != null ? property.Value.ToString() : "NULL"; | ||
var message = $"API property {property.Key.ToString()} = {value}"; | ||
|
||
// Log the message for this property | ||
Logger.LogMessage(Severity.Info, message); | ||
} | ||
} | ||
else | ||
{ | ||
// Log the fact that the properties dictionary is NULL | ||
Logger.LogMessage(Severity.Warning, "API lookup generated a NULL properties dictionary"); | ||
} | ||
} | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
src/FlightRecorder.BusinessLogic/Api/FlightRecorderHttpClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using FlightRecorder.Entities.Interfaces; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
|
||
namespace FlightRecorder.BusinessLogic.Api | ||
{ | ||
[ExcludeFromCodeCoverage] | ||
public class FlightRecorderHttpClient : IFlightRecorderHttpClient | ||
{ | ||
private readonly static HttpClient _client = new(); | ||
private static FlightRecorderHttpClient? _instance = null; | ||
Check warning on line 13 in src/FlightRecorder.BusinessLogic/Api/FlightRecorderHttpClient.cs GitHub Actions / build
Check warning on line 13 in src/FlightRecorder.BusinessLogic/Api/FlightRecorderHttpClient.cs GitHub Actions / build
|
||
private readonly static object _lock = new(); | ||
|
||
private FlightRecorderHttpClient() { } | ||
|
||
/// <summary> | ||
/// Return the singleton instance of the client | ||
/// </summary> | ||
public static FlightRecorderHttpClient Instance | ||
{ | ||
get | ||
{ | ||
if (_instance == null) | ||
{ | ||
lock (_lock) | ||
{ | ||
if (_instance == null) | ||
{ | ||
_instance = new FlightRecorderHttpClient(); | ||
} | ||
} | ||
} | ||
|
||
return _instance; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Set the request headers | ||
/// </summary> | ||
/// <param name="headers"></param> | ||
public void SetHeaders(Dictionary<string, string> headers) | ||
{ | ||
_client.DefaultRequestHeaders.Clear(); | ||
foreach (var header in headers) | ||
{ | ||
_client.DefaultRequestHeaders.Add(header.Key, header.Value); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Send a GET request to the specified URI and return the response | ||
/// </summary> | ||
/// <param name="uri"></param> | ||
/// <returns></returns> | ||
public async Task<HttpResponseMessage> GetAsync(string uri) | ||
=> await _client.GetAsync(uri); | ||
} | ||
} |
Oops, something went wrong.