-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
Added AeroDataBox flight lookup
- Loading branch information
There are no files selected for viewing
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; | ||
} | ||
} | ||
} |
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>(); | ||
Check warning on line 85 in src/FlightRecorder.BusinessLogic/Api/ExternalApiBase.cs GitHub Actions / build
|
||
} | ||
|
||
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"); | ||
} | ||
} | ||
} | ||
} |
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
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
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); | ||
} | ||
} |