diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml
new file mode 100644
index 00000000..f9a78fb4
--- /dev/null
+++ b/.github/workflows/qodana_code_quality.yml
@@ -0,0 +1,20 @@
+name: Qodana
+on:
+ workflow_dispatch:
+ pull_request:
+ push:
+ branches:
+ - dev
+ - main
+
+jobs:
+ qodana:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: 'Qodana Scan'
+ uses: JetBrains/qodana-action@v2023.2
+ env:
+ QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
\ No newline at end of file
diff --git a/EliteAPI.Abstractions/EliteAPI.Abstractions.csproj b/EliteAPI.Abstractions/EliteAPI.Abstractions.csproj
index 2a2624ab..ccce42c0 100644
--- a/EliteAPI.Abstractions/EliteAPI.Abstractions.csproj
+++ b/EliteAPI.Abstractions/EliteAPI.Abstractions.csproj
@@ -5,10 +5,10 @@
10
3.0.0.0
3.0.0.0
- 3.0.15+13.Branch.main.Sha.aa3bd21915f470956d90f87a2d089e6d67be09ae
+ 3.1.0-alpha.182+Branch.dev.Sha.0fd6d5a6f5a17cbe313c176d121f8bf359e348de
3.0.0-alpha5155
- 3.0.15.0
- 3.0.15.0
+ 3.1.0.0
+ 3.1.0.0
https://github.com/EliteAPI/EliteAPI
https://github.com/EliteAPI/EliteAPI
@@ -18,7 +18,7 @@
Abstractions for EliteAPI
© Somfic 2022
false
- 3.0.15
+ 3.1.0-alpha0182
true
icon.png
true
diff --git a/EliteAPI.Abstractions/Events/IEvents.cs b/EliteAPI.Abstractions/Events/IEvents.cs
index b84df3d3..4f9df52c 100644
--- a/EliteAPI.Abstractions/Events/IEvents.cs
+++ b/EliteAPI.Abstractions/Events/IEvents.cs
@@ -9,7 +9,7 @@ public interface IEvents
IReadOnlyCollection<(IEvent @event, EventContext context)> Backlog { get; }
/// All event types that have been registered.
- IEnumerable EventTypes { get; }
+ IReadOnlyCollection EventTypes { get; }
/// A collection of previous events since the API was started.
IReadOnlyCollection<(IEvent @event, EventContext context)> PreviousEvents { get; }
diff --git a/EliteAPI.Events/Carriers/CarrierBankTransferEvent.cs b/EliteAPI.Events/Carriers/CarrierBankTransferEvent.cs
index 18fd22cf..36059789 100644
--- a/EliteAPI.Events/Carriers/CarrierBankTransferEvent.cs
+++ b/EliteAPI.Events/Carriers/CarrierBankTransferEvent.cs
@@ -5,6 +5,12 @@ namespace EliteAPI.Events;
public struct CarrierBankTransferEvent : IEvent
{
+ [JsonProperty("timestamp")]
+ public DateTime Timestamp { get; init; }
+
+ [JsonProperty("event")]
+ public string Event { get; init; }
+
[JsonProperty("CarrierID")]
public string CarrierId { get; init; }
@@ -19,8 +25,4 @@ public struct CarrierBankTransferEvent : IEvent
[JsonProperty("CarrierBalance")]
public long CarrierBalance { get; init; }
-
- public DateTime Timestamp { get; }
-
- public string Event { get; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Carriers/CarrierNameChangedEvent.cs b/EliteAPI.Events/Carriers/CarrierNameChangeEvent.cs
similarity index 89%
rename from EliteAPI.Events/Carriers/CarrierNameChangedEvent.cs
rename to EliteAPI.Events/Carriers/CarrierNameChangeEvent.cs
index eb295bff..700e5c2a 100644
--- a/EliteAPI.Events/Carriers/CarrierNameChangedEvent.cs
+++ b/EliteAPI.Events/Carriers/CarrierNameChangeEvent.cs
@@ -3,7 +3,7 @@
namespace EliteAPI.Events;
-public readonly struct CarrierNameChangedEvent : IEvent
+public readonly struct CarrierNameChangeEvent : IEvent
{
[JsonProperty("timestamp")]
public DateTime Timestamp { get; init; }
diff --git a/EliteAPI.Events/Combat/DropShipDeplyEvent.cs b/EliteAPI.Events/Combat/DropShipDeployEvent.cs
similarity index 88%
rename from EliteAPI.Events/Combat/DropShipDeplyEvent.cs
rename to EliteAPI.Events/Combat/DropShipDeployEvent.cs
index 595358e7..15d4f08f 100644
--- a/EliteAPI.Events/Combat/DropShipDeplyEvent.cs
+++ b/EliteAPI.Events/Combat/DropShipDeployEvent.cs
@@ -24,8 +24,8 @@ namespace EliteAPI.Events;
public string BodyId { get; init; }
[JsonProperty("OnStation")]
- public bool OnStation { get; init; }
+ public bool IsOnStation { get; init; }
[JsonProperty("OnPlanet")]
- public bool OnPlanet { get; init; }
+ public bool IsOnPlanet { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/EliteAPI.Events.csproj b/EliteAPI.Events/EliteAPI.Events.csproj
index 0bfa3209..f37856ed 100644
--- a/EliteAPI.Events/EliteAPI.Events.csproj
+++ b/EliteAPI.Events/EliteAPI.Events.csproj
@@ -3,9 +3,9 @@
enable
enable
10
- 3.0.15.0
- 3.0.15.0
- 3.0.15+13.Branch.main.Sha.aa3bd21915f470956d90f87a2d089e6d67be09ae
+ 3.1.0.0
+ 3.1.0.0
+ 3.1.0-alpha.182+Branch.dev.Sha.0fd6d5a6f5a17cbe313c176d121f8bf359e348de
3.0.0-alpha5167
true
https://github.com/EliteAPI/EliteAPI
@@ -17,7 +17,7 @@
Events for EliteAPI
© Somfic 2022
false
- 3.0.15
+ 3.1.0-alpha0182
true
icon.png
netstandard2.0
diff --git a/EliteAPI.Events/Other/BuyMicroResourceEvent.cs b/EliteAPI.Events/Other/BuyMicroResourcesEvent.cs
similarity index 95%
rename from EliteAPI.Events/Other/BuyMicroResourceEvent.cs
rename to EliteAPI.Events/Other/BuyMicroResourcesEvent.cs
index 9a25d44e..da4c9f95 100644
--- a/EliteAPI.Events/Other/BuyMicroResourceEvent.cs
+++ b/EliteAPI.Events/Other/BuyMicroResourcesEvent.cs
@@ -3,7 +3,7 @@
namespace EliteAPI.Events;
-public readonly struct BuyMicroResourceEvent : IEvent
+public readonly struct BuyMicroResourcesEvent : IEvent
{
[JsonProperty("timestamp")]
public DateTime Timestamp { get; init; }
diff --git a/EliteAPI.Events/Station/ResupplyEvent.cs b/EliteAPI.Events/Station/ResupplyEvent.cs
new file mode 100644
index 00000000..1c4db5c6
--- /dev/null
+++ b/EliteAPI.Events/Station/ResupplyEvent.cs
@@ -0,0 +1,13 @@
+using EliteAPI.Abstractions.Events;
+using Newtonsoft.Json;
+
+namespace EliteAPI.Events;
+
+public readonly struct ResupplyEvent : IEvent
+{
+ [JsonProperty("timestamp")]
+ public DateTime Timestamp { get; init; }
+
+ [JsonProperty("event")]
+ public string Event { get; init; }
+}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/Events/AltitudeStatusEvent.cs b/EliteAPI.Events/Status/Ship/Events/AltitudeStatusEvent.cs
index 93113f37..1282a72a 100644
--- a/EliteAPI.Events/Status/Ship/Events/AltitudeStatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/Events/AltitudeStatusEvent.cs
@@ -2,11 +2,11 @@
namespace EliteAPI.Events.Status.Ship.Events;
-public readonly struct AltitudeStatusEvent : IStatusEvent
+public readonly struct AltitudeStatusEvent : IStatusEvent
{
public DateTime Timestamp => DateTime.Now;
public string Event => "Altitude";
- public float Value { get; init; }
+ public double Value { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/Events/BodyRadiusStatusEvent.cs b/EliteAPI.Events/Status/Ship/Events/BodyRadiusStatusEvent.cs
index 2beb55e8..a68e691c 100644
--- a/EliteAPI.Events/Status/Ship/Events/BodyRadiusStatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/Events/BodyRadiusStatusEvent.cs
@@ -2,11 +2,11 @@
namespace EliteAPI.Events.Status.Ship.Events;
-public readonly struct BodyRadiusStatusEvent : IStatusEvent
+public readonly struct BodyRadiusStatusEvent : IStatusEvent
{
public DateTime Timestamp => DateTime.Now;
public string Event => "BodyRadius";
- public float Value { get; init; }
+ public double Value { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/Events/GravityStatusEvent.cs b/EliteAPI.Events/Status/Ship/Events/GravityStatusEvent.cs
index 7af41a76..f9811a79 100644
--- a/EliteAPI.Events/Status/Ship/Events/GravityStatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/Events/GravityStatusEvent.cs
@@ -2,11 +2,11 @@
namespace EliteAPI.Events.Status.Ship.Events;
-public readonly struct GravityStatusEvent : IStatusEvent
+public readonly struct GravityStatusEvent : IStatusEvent
{
public DateTime Timestamp => DateTime.Now;
public string Event => "Gravity";
- public float Value { get; init; }
+ public double Value { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/Events/HeadingStatusEvent.cs b/EliteAPI.Events/Status/Ship/Events/HeadingStatusEvent.cs
index b9d12443..d9e00a43 100644
--- a/EliteAPI.Events/Status/Ship/Events/HeadingStatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/Events/HeadingStatusEvent.cs
@@ -2,11 +2,11 @@
namespace EliteAPI.Events.Status.Ship.Events;
-public readonly struct HeadingStatusEvent : IStatusEvent
+public readonly struct HeadingStatusEvent : IStatusEvent
{
public DateTime Timestamp => DateTime.Now;
public string Event => "Heading";
- public float Value { get; init; }
+ public double Value { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/Events/HealthStatusEvent.cs b/EliteAPI.Events/Status/Ship/Events/HealthStatusEvent.cs
index 549785ca..bc1e3700 100644
--- a/EliteAPI.Events/Status/Ship/Events/HealthStatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/Events/HealthStatusEvent.cs
@@ -2,11 +2,11 @@
namespace EliteAPI.Events.Status.Ship.Events;
-public readonly struct HealthStatusEvent : IStatusEvent
+public readonly struct HealthStatusEvent : IStatusEvent
{
public DateTime Timestamp => DateTime.Now;
public string Event => "Health";
- public float Value { get; init; }
+ public double Value { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/Events/LatitudeStatusEvent.cs b/EliteAPI.Events/Status/Ship/Events/LatitudeStatusEvent.cs
index 6ea06459..a5f3b39a 100644
--- a/EliteAPI.Events/Status/Ship/Events/LatitudeStatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/Events/LatitudeStatusEvent.cs
@@ -2,11 +2,11 @@
namespace EliteAPI.Events.Status.Ship.Events;
-public readonly struct LatitudeStatusEvent : IStatusEvent
+public readonly struct LatitudeStatusEvent : IStatusEvent
{
public DateTime Timestamp => DateTime.Now;
public string Event => "Latitude";
- public float Value { get; init; }
+ public double Value { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/Events/LongitudeStatusEvent.cs b/EliteAPI.Events/Status/Ship/Events/LongitudeStatusEvent.cs
index b5fbbfa2..77b273bf 100644
--- a/EliteAPI.Events/Status/Ship/Events/LongitudeStatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/Events/LongitudeStatusEvent.cs
@@ -2,11 +2,11 @@
namespace EliteAPI.Events.Status.Ship.Events;
-public readonly struct LongitudeStatusEvent : IStatusEvent
+public readonly struct LongitudeStatusEvent : IStatusEvent
{
public DateTime Timestamp => DateTime.Now;
public string Event => "Longitude";
- public float Value { get; init; }
+ public double Value { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/Events/OxygenStatusEvent.cs b/EliteAPI.Events/Status/Ship/Events/OxygenStatusEvent.cs
index 8a91d122..97e33bcb 100644
--- a/EliteAPI.Events/Status/Ship/Events/OxygenStatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/Events/OxygenStatusEvent.cs
@@ -2,11 +2,11 @@
namespace EliteAPI.Events.Status.Ship.Events;
-public readonly struct OxygenStatusEvent : IStatusEvent
+public readonly struct OxygenStatusEvent : IStatusEvent
{
public DateTime Timestamp => DateTime.Now;
public string Event => "Oxygen";
- public float Value { get; init; }
+ public double Value { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/Events/TemperatureStatusEvent.cs b/EliteAPI.Events/Status/Ship/Events/TemperatureStatusEvent.cs
index b5ead477..cf1dd8be 100644
--- a/EliteAPI.Events/Status/Ship/Events/TemperatureStatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/Events/TemperatureStatusEvent.cs
@@ -2,11 +2,11 @@
namespace EliteAPI.Events.Status.Ship.Events;
-public readonly struct TemperatureStatusEvent : IStatusEvent
+public readonly struct TemperatureStatusEvent : IStatusEvent
{
public DateTime Timestamp => DateTime.Now;
public string Event => "Temperature";
- public float Value { get; init; }
+ public double Value { get; init; }
}
\ No newline at end of file
diff --git a/EliteAPI.Events/Status/Ship/StatusEvent.cs b/EliteAPI.Events/Status/Ship/StatusEvent.cs
index 0607bbdd..624dda6f 100644
--- a/EliteAPI.Events/Status/Ship/StatusEvent.cs
+++ b/EliteAPI.Events/Status/Ship/StatusEvent.cs
@@ -100,34 +100,34 @@ namespace EliteAPI.Events.Status.Ship;
public ShipDestination Destination { get; init; }
[JsonProperty("PlanetRadius")]
- public float BodyRadius { get; init; }
+ public double BodyRadius { get; init; }
[JsonProperty("Oxygen")]
- public float Oxygen { get; init; }
+ public double Oxygen { get; init; }
[JsonProperty("Health")]
- public float Health { get; init; }
+ public double Health { get; init; }
[JsonProperty("Temperature")]
- public float Temperature { get; init; }
+ public double Temperature { get; init; }
[JsonProperty("SelectedWeapon")]
public Localised SelectedWeapon { get; init; }
[JsonProperty("Gravity")]
- public float Gravity { get; init; }
+ public double Gravity { get; init; }
[JsonProperty("Latitude")]
- public float Latitude { get; init; }
+ public double Latitude { get; init; }
[JsonProperty("Longitude")]
- public float Longitude { get; init; }
+ public double Longitude { get; init; }
[JsonProperty("Heading")]
- public float Heading { get; init; }
+ public double Heading { get; init; }
[JsonProperty("Altitude")]
- public long Altitude { get; init; }
+ public double Altitude { get; init; }
[JsonProperty("BodyName")]
public string Body { get; init; }
diff --git a/EliteAPI.Tests/Conventions.cs b/EliteAPI.Tests/Conventions.cs
new file mode 100644
index 00000000..fa43e7a4
--- /dev/null
+++ b/EliteAPI.Tests/Conventions.cs
@@ -0,0 +1,114 @@
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Reflection;
+using System.Xml;
+using EliteAPI.Abstractions.Bindings;
+using EliteAPI.Abstractions.Bindings.Models;
+using EliteAPI.Abstractions.Events;
+using EliteAPI.Bindings;
+using EliteAPI.Events;
+using Microsoft.Extensions.Logging;
+using Moq;
+
+namespace EliteAPI.Tests;
+
+[TestFixture]
+public class Convetions
+{
+ [Test(Description = "Event types")]
+ [TestCaseSource(nameof(GetTypes))]
+ public void Events(Type type)
+ {
+ // Namespace should be EliteAPI.Events
+ Assert.That(type.Namespace!.StartsWith("EliteAPI.Events"),
+ $"Type {type.Name} should be in namespace EliteAPI.Events");
+
+ // Name should not have underscores
+ Assert.That(!type.Name.Contains("_"),
+ $"Type {type.Name} should not contain underscores");
+
+ // Name should be PascalCase
+ Assert.That(char.IsUpper(type.Name[0]),
+ $"Type {type.Name} should start with a capital letter");
+
+ // Should be a struct
+ Assert.That(type.IsValueType,
+ $"Type {type.Name} should be a struct");
+
+ // Should implement IEvent
+ Assert.That(type.GetInterfaces().Contains(typeof(IEvent)),
+ $"Type {type.Name} should implement IEvent");
+ }
+
+ [Test(Description = "Properties")]
+ [TestCaseSource(nameof(GetProperties))]
+ public void Properties(PropertyInfo property)
+ {
+ var name = property.DeclaringType!.Name + "." + property.Name;
+
+ // Namespace should be EliteAPI.Events
+ Assert.That(property.DeclaringType!.Namespace!.StartsWith("EliteAPI.Events"),
+ $"Property {name} should be in namespace EliteAPI.Events");
+
+ // Name should not have underscores
+ Assert.That(!property.Name.Contains("_"),
+ $"Property {name} should not contain underscores");
+
+ // Name should be PascalCase
+ Assert.That(char.IsUpper(property.Name[0]),
+ $"Property {name} should start with a capital letter");
+
+ // Name should not be too long
+ Assert.That(property.Name.Length <= 32,
+ $"Property {name} should not be longer than 32 characters");
+
+ if (property.PropertyType == typeof(float) || property.PropertyType == typeof(decimal))
+ {
+ // Property should be of type double
+ Assert.That(property.PropertyType == typeof(double),
+ $"Property {name} should be of type double");
+ }
+
+ if (property.PropertyType == typeof(bool) && !property.DeclaringType.Namespace.StartsWith("EliteAPI.Events.Status"))
+ {
+ // Property should be named IsX or HasX or WasX
+ var prefixes = new[] {"Is", "Has", "Was", "Allows", "Can", "Should"};
+ Assert.That(prefixes.Any(x => property.Name.StartsWith(x)),
+ $"Property {name} should start with {string.Join(" or ", prefixes)}");
+ }
+ }
+
+ static IImmutableList GetProperties()
+ {
+ var eventParser = new EventParser(Mock.Of());
+ eventParser.Use();
+ var events = new Events.Events(Mock.Of>(), eventParser);
+ events.Register();
+
+ var properties = new List();
+
+ if(events == null)
+ throw new Exception("Events not initialized");
+
+ foreach (var eventType in events.EventTypes)
+ {
+ properties.AddRange(eventType.GetProperties());
+ }
+
+ return properties.ToImmutableList();
+ }
+
+ static IImmutableList GetTypes()
+ {
+ var eventParser = new EventParser(Mock.Of());
+ eventParser.Use();
+
+ var events = new Events.Events(Mock.Of>(), eventParser);
+ events.Register();
+
+ if (events == null)
+ throw new Exception("Events not initialized");
+
+ return events.EventTypes.ToImmutableList();
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.Tests/EliteAPI.Tests.csproj b/EliteAPI.Tests/EliteAPI.Tests.csproj
index 376af5a0..16cec1f3 100644
--- a/EliteAPI.Tests/EliteAPI.Tests.csproj
+++ b/EliteAPI.Tests/EliteAPI.Tests.csproj
@@ -5,10 +5,10 @@
enable
false
true
- 3.0.15.0
- 3.0.15.0
- 3.0.15+13.Branch.main.Sha.aa3bd21915f470956d90f87a2d089e6d67be09ae
- 3.0.15
+ 3.1.0.0
+ 3.1.0.0
+ 3.1.0-alpha.182+Branch.dev.Sha.0fd6d5a6f5a17cbe313c176d121f8bf359e348de
+ 3.1.0-alpha0182
diff --git a/EliteAPI.Tests/JournalManual.cs b/EliteAPI.Tests/JournalManual.cs
index 4ce81e63..268e4421 100644
--- a/EliteAPI.Tests/JournalManual.cs
+++ b/EliteAPI.Tests/JournalManual.cs
@@ -17,18 +17,19 @@ namespace EliteAPI.Tests;
public class JournalManual
{
private static IEvents _events;
- private static string[] _legacyEvents = { "BackpackMaterials", "BuyMicroResources", "ShipTargetted" };
+ private static string[] _legacyEvents = { "BackpackMaterials", "BuyMicroResources", "ShipTargetted", "CarrierNameChanged" };
private static string[] _legacyExamples =
{
"\"timestamp\":\"2020-04-27T08:02:52Z\", \"event\":\"Route\"",
- "\"timestamp\":\"2020-04-27T08:02:52Z\", \"event\":\"Route\""
+ "\"timestamp\":\"2020-04-27T08:02:52Z\", \"event\":\"Route\"",
+ "\"timestamp\":\"2020-10-07T14:01:08Z\", \"event\":\"BuyMicroResource\"",
};
[OneTimeSetUp]
public void Setup()
{
var eventParser = new EventParser(Mock.Of());
- eventParser.Use();;
+ eventParser.Use();
_events = new Events.Events(Mock.Of>(), eventParser);
_events.Register();
}
@@ -66,12 +67,22 @@ public void Json(string json)
return;
}
- _events.Invoke(json, new EventContext());
+ var invokedEvent = _events.Invoke(json, new EventContext());
+
+ Assert.That(invokedEvent, Is.Not.Null, $"Event is null");
+
+ // Check if the event is the correct type
+ var eventType = invokedEvent.GetType();
+ var eventName = eventType.Name;
+ if (eventName.EndsWith("Event"))
+ eventName = eventName.Substring(0, eventName.Length - 5);
+
+ Assert.That(string.Equals(eventName, invokedEvent.Event, StringComparison.CurrentCultureIgnoreCase), $"Event is not of type {eventName} but {invokedEvent.Event}");
}
[Test(Description = "Properties")]
[TestCaseSource(nameof(GetProperties))]
- [Ignore("Tests still in progress")]
+ [Ignore("This test is not finished")]
public void Properties((string eventName, Property property) propertyInfo)
{
var (eventName, expectedProperty) = propertyInfo;
@@ -86,7 +97,7 @@ public void Properties((string eventName, Property property) propertyInfo)
// Get the property by the JsonProperty attribute
var property = properties.FirstOrDefault(x => string.Equals(x.GetCustomAttribute()?.PropertyName, expectedProperty.Name, StringComparison.CurrentCultureIgnoreCase));
- Warn.If(property, Is.Not.Null, $"Type '{eventType.Name}' does not contain expected property '{expectedProperty.Name}'");
+ Assert.That(property, Is.Not.Null, $"Type '{eventType.Name}' does not contain expected property '{expectedProperty.Name}'");
if (property == null)
return;
@@ -95,13 +106,13 @@ public void Properties((string eventName, Property property) propertyInfo)
foreach (var expectedChild in expectedProperty.Children)
{
var childProperty = property.PropertyType.GetProperties().FirstOrDefault(x => string.Equals(x.GetCustomAttribute()?.PropertyName, expectedChild.Name, StringComparison.CurrentCultureIgnoreCase));
- Warn.If(childProperty, Is.Not.Null, $"Type '{property.PropertyType.Name}' does not contain expected child property '{expectedChild.Name}'");
+ Assert.That(childProperty, Is.Not.Null, $"Type '{property.PropertyType.Name}' does not contain expected child property '{expectedChild.Name}'");
// Check the child's children
foreach (var expectedGrandChild in expectedChild.Children)
{
var childChildProperty = childProperty.PropertyType.GetProperties().FirstOrDefault(x => string.Equals(x.GetCustomAttribute()?.PropertyName, expectedGrandChild.Name, StringComparison.CurrentCultureIgnoreCase));
- Warn.If(childChildProperty, Is.Not.Null, $"Type '{childProperty.PropertyType.Name}' does not contain expected child property '{expectedGrandChild.Name}'");
+ Assert.That(childChildProperty, Is.Not.Null, $"Type '{childProperty.PropertyType.Name}' does not contain expected child property '{expectedGrandChild.Name}'");
}
}
}
diff --git a/EliteAPI.Tests/Schemas.cs b/EliteAPI.Tests/Schemas.cs
index 99b752ee..76723b2b 100644
--- a/EliteAPI.Tests/Schemas.cs
+++ b/EliteAPI.Tests/Schemas.cs
@@ -1,4 +1,5 @@
using System.Collections;
+using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection;
using EliteAPI.Abstractions.Events;
@@ -11,130 +12,208 @@
namespace EliteAPI.Tests;
[TestFixture]
-[Ignore("Tests still in progress")]
public class Schemas
{
private static IEvents _events;
-
- private static string[] _legacyEvents = { "BackpackMaterials", "BuyMicroResources", "ShipTargetted" };
- private static string[] _legacyExamples =
- {
- "\"timestamp\":\"2020-04-27T08:02:52Z\", \"event\":\"Route\"",
- "\"timestamp\":\"2020-04-27T08:02:52Z\", \"event\":\"Route\""
- };
-
+
[OneTimeSetUp]
public void Setup()
{
var eventParser = new EventParser(Mock.Of());
- eventParser.Use();;
+ eventParser.Use();
+
_events = new Events.Events(Mock.Of>(), eventParser);
_events.Register();
}
-
+
[Test(Description = "Properties")]
- [TestCaseSource(nameof(GetSchemas))]
- public void Properties((string name, string schema) schemaInfo)
+ [TestCaseSource(nameof(GetProperties))]
+ [Ignore("Tests are being worked on")]
+ public void Properties((string name, string type) schemaInfo)
{
- var (name, schema) = schemaInfo;
- name += "Event";
-
- var eventType = _events.EventTypes.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.CurrentCultureIgnoreCase));
+ var eventName = schemaInfo.name.Split('.')[0] + "Event";
+ var name = string.Join(".", schemaInfo.name.Split('.').Skip(1));
+ var type = schemaInfo.type;
- Assert.That(eventType, Is.Not.Null, $"Event {name} not found");
- if (eventType == null)
- return;
-
- var properties = eventType.GetProperties();
- var expectedProperties = JObject.Parse(schema)["properties"]!.ToObject>();
+ var eventType = _events.EventTypes.FirstOrDefault(x => x.Name == eventName);
+ Assert.That(eventType, Is.Not.Null, $"Event type {eventName} not found");
- foreach (var expectedProperty in expectedProperties!)
+ if (type == "object")
{
- if(expectedProperty.Key.EndsWith("_Localised"))
- continue;
+ // Skip
+ Assert.Pass();
+ }
+ else if (type == "array")
+ {
+ // Skip
+ Assert.Pass();
+ }
+ else
+ {
+ // Skip nested properties
+ if(name.Any(x => x == '.'))
+ Assert.Pass();
- var property = properties.FirstOrDefault(x =>
- string.Equals(x.GetCustomAttribute()?.PropertyName, expectedProperty.Key,
- StringComparison.CurrentCultureIgnoreCase));
+ var properties = eventType!
+ .GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(x => x.GetCustomAttributes().Any());
- var expectedType = expectedProperty.Value["type"]!.ToObject();
-
- Assert.That(property, Is.Not.Null, $"Property {name}.{expectedProperty.Key} was not found (type {expectedType})");
- if (property == null)
- continue;
+ var jsonProperty =
+ properties.FirstOrDefault(x => string.Equals(x.GetCustomAttribute()!.PropertyName, name, StringComparison.InvariantCultureIgnoreCase));
+ Assert.That(jsonProperty, Is.Not.Null, $"Property {name} not found on event {eventName}");
- // Make all IDs strings
- if(expectedProperty.Key.EndsWith("ID") || expectedProperty.Key.EndsWith("Address"))
- expectedType = "string";
-
- var type = ConvertToJTokenType(property.PropertyType);
+ var jsonPropertyType = jsonProperty!.PropertyType;
+ var jsonPropertyTypeName = ConvertToJTokenType(jsonPropertyType);
- Assert.That(string.Equals(type.ToString(), expectedType, StringComparison.CurrentCultureIgnoreCase), Is.True,
- $"Property {name}.{expectedProperty.Key} is not the expected type '{expectedType}' (is {type})");
+ Assert.That(jsonPropertyTypeName, Is.EqualTo(type),
+ $"Property {name} on event {eventName} is of type {jsonPropertyTypeName} but should be of type {type}");
}
}
-
+
[Test(Description = "Events")]
[TestCaseSource(nameof(GetSchemas))]
public void Event((string name, string schema) schemaInfo)
{
var (name, schema) = schemaInfo;
+
name += "Event";
-
+
Assert.That(_events.EventTypes.Select(x => x.Name.ToLower()), Does.Contain(name.ToLower()));
}
-
- private static IEnumerable<(string name, string schema)> GetSchemas()
+
+
+ static IImmutableList<(string name, string type)> GetProperties()
{
- Process.Start("git", "clone https://github.com/jixxed/ed-journal-schemas.git schemas-repo").WaitForExit();
-
+ var result = new List<(string, string)>();
+
+ foreach (var (name, schema) in GetSchemas())
+ {
+ Console.WriteLine($"Processing {name}");
+
+ var schemaObject = JObject.Parse(schema);
+ var properties = schemaObject["properties"]?.Children();
+
+ if (properties == null)
+ continue;
+
+ result.AddRange(ExtractProperties(properties, name));
+ }
+
+ return result.ToImmutableList();
+ }
+
+ static IEnumerable<(string name, string type)> ExtractProperties(IEnumerable properties,
+ string namePrefix = "")
+ {
+ foreach (var property in properties)
+ {
+ var type = property.Value["type"]?.Value();
+ var name = property.Value["title"]?.Value();
+
+ if (type == null || name == null)
+ continue;
+
+ if(name.EndsWith("_Localised"))
+ continue;
+
+ if(name.EndsWith("ID") || name.EndsWith("Address") || name.EndsWith("Market"))
+ type = "string";
+
+ var propertyName = (namePrefix + "." + name).TrimStart('.').TrimEnd('.').Replace("..", ".");
+ yield return (propertyName, type);
+
+ switch (type)
+ {
+ case "array":
+ {
+ var items = property.Value.Value("items")?.Value("properties")
+ ?.Children();
+
+ if (items == null)
+ continue;
+
+ var arrayProperties = ExtractProperties(items, namePrefix + "." + name);
+
+ foreach (var arrayProperty in arrayProperties)
+ yield return arrayProperty;
+ break;
+ }
+ case "object":
+ {
+ var items = property.Value["properties"]?.Children();
+
+ if (items == null)
+ continue;
+
+ var objectProperties = ExtractProperties(items, namePrefix + "." + name);
+
+ foreach (var objectProperty in objectProperties)
+ yield return objectProperty;
+ break;
+ }
+ }
+ }
+ }
+
+ static IEnumerable<(string name, string schema)> GetSchemas()
+ {
+ Process.Start("git", "clone https://github.com/Somfic/journal-schemas.git schemas-repo").WaitForExit();
+
var files = Directory.GetFiles("schemas-repo", "*.json", SearchOption.AllDirectories);
var schemas = new List<(string, string)>();
-
+
foreach (var file in files)
{
var name = Path.GetFileNameWithoutExtension(file);
+
+ if (name is "_Event" or "ShipLockerBackpack" or "ShipLockerMaterials")
+ continue;
+
var schema = File.ReadAllText(file);
-
+
schemas.Add((name, schema));
}
-
+
return schemas;
}
-
+
private static string ConvertToJTokenType(Type type)
{
if (type == typeof(string) || type == typeof(Localised))
{
return "string";
}
- else if (type == typeof(int) || type == typeof(long) || type == typeof(short) || type == typeof(byte))
+
+ if (type == typeof(int) || type == typeof(long) || type == typeof(short) || type == typeof(byte))
{
return "integer";
}
- else if (type == typeof(float) || type == typeof(double) || type == typeof(decimal))
+
+ if (type == typeof(float) || type == typeof(double) || type == typeof(decimal))
{
return "number";
}
- else if (type == typeof(bool))
+
+ if (type == typeof(bool))
{
return "boolean";
}
- else if (type == typeof(DateTime))
+
+ if (type == typeof(DateTime))
{
return "string";
}
- else if (type.GetInterfaces().Any(x => x == typeof(IEnumerable)))
+
+ if (type.GetInterfaces().Any(x => x == typeof(IEnumerable)))
{
return "array";
}
- else if (type.IsValueType)
+
+ if (type.IsValueType)
{
return "object";
}
- else
- {
- return "unknown";
- }
+
+ return "unknown";
}
}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/EliteAPI.Web.Spansh.csproj b/EliteAPI.Web.Spansh/EliteAPI.Web.Spansh.csproj
new file mode 100644
index 00000000..b2147031
--- /dev/null
+++ b/EliteAPI.Web.Spansh/EliteAPI.Web.Spansh.csproj
@@ -0,0 +1,21 @@
+
+
+ netstandard2.0
+ enable
+ enable
+ default
+ 3.1.0.0
+ 3.1.0.0
+ 3.1.0-alpha.182+Branch.dev.Sha.0fd6d5a6f5a17cbe313c176d121f8bf359e348de
+ 3.1.0-alpha0182
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/RoutePlanner/Requests/NeutronRequest.cs b/EliteAPI.Web.Spansh/RoutePlanner/Requests/NeutronRequest.cs
new file mode 100644
index 00000000..f254767b
--- /dev/null
+++ b/EliteAPI.Web.Spansh/RoutePlanner/Requests/NeutronRequest.cs
@@ -0,0 +1,32 @@
+using EliteAPI.Web.Attributes;
+using EliteAPI.Web.Models;
+
+namespace EliteAPI.Web.Spansh.RoutePlanner.Requests;
+
+public class NeutronRequest : IWebApiRequest
+{
+ public NeutronRequest(string from, string to, int range = 10)
+ {
+ From = from;
+ To = to;
+ Range = range;
+ }
+
+ [QueryParameter("efficiency")]
+ public int Efficiency { get; init; } = 60;
+
+ [QueryParameter("range")]
+ public int Range { get; init; }
+
+ [QueryParameter("from")]
+ public string From { get; init; }
+
+ [QueryParameter("via")]
+ public string Via { get; init; }
+
+ [QueryParameter("to")]
+ public string To { get; init; }
+
+ public string Endpoint => "route";
+ public HttpMethod Method => HttpMethod.Post;
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/RoutePlanner/Requests/TradeRequest.cs b/EliteAPI.Web.Spansh/RoutePlanner/Requests/TradeRequest.cs
new file mode 100644
index 00000000..0a26b318
--- /dev/null
+++ b/EliteAPI.Web.Spansh/RoutePlanner/Requests/TradeRequest.cs
@@ -0,0 +1,57 @@
+using EliteAPI.Web.Attributes;
+using EliteAPI.Web.Models;
+
+namespace EliteAPI.Web.Spansh.RoutePlanner.Requests;
+
+public class TradeRequest : IWebApiRequest
+{
+ public TradeRequest(string startingSystem, string startingStation, int range, int capacity)
+ {
+ StartingSystem = startingSystem;
+ StartingStation = startingStation;
+ JumpRange = range;
+ CargoCapacity = capacity;
+ }
+
+ [QueryParameter("system")]
+ public string StartingSystem { get; init; }
+
+ [QueryParameter("station")]
+ public string StartingStation { get; init; }
+
+ [QueryParameter("starting_capital")]
+ public int StartingCapital { get; init; } = 100_000_000;
+
+ [QueryParameter("max_hop_distance")]
+ public int JumpRange { get; init; }
+
+ [QueryParameter("max_cargo")]
+ public int CargoCapacity { get; init; }
+
+ [QueryParameter("max_hops")]
+ public int AmountOfStops { get; init; } = 5;
+
+ [QueryParameter("max_system_distance")]
+ public int MaxDistanceToArrival { get; init; } = 10_000;
+
+ [QueryParameter("max_price_age")]
+ public int MaxPriceAge { get; init; } = 60 * 60 * 24 * 7; // 7 days
+
+ [QueryParameter("requires_large_pad")]
+ public bool RequiresLargePad { get; init; } = false;
+
+ [QueryParameter("allow_planetary")]
+ public bool AllowPlanetary { get; init; } = false;
+
+ [QueryParameter("allow_prohibited")]
+ public bool AllowProhibited { get; init; } = false;
+
+ [QueryParameter("permit")]
+ public bool AllowPermitSystems { get; init; } = false;
+
+ [QueryParameter("unique")]
+ public bool AvoidLoops { get; init; } = false;
+
+ public string Endpoint => "trade/route";
+ public HttpMethod Method => HttpMethod.Post;
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/RoutePlanner/Responses/NeutronResponse.cs b/EliteAPI.Web.Spansh/RoutePlanner/Responses/NeutronResponse.cs
new file mode 100644
index 00000000..e7d73b67
--- /dev/null
+++ b/EliteAPI.Web.Spansh/RoutePlanner/Responses/NeutronResponse.cs
@@ -0,0 +1,64 @@
+using EliteAPI.Web.Models;
+using Newtonsoft.Json;
+
+namespace EliteAPI.Web.Spansh.RoutePlanner.Responses;
+
+public class NeutronResponse
+{
+ [JsonProperty("destination_system")]
+ public string DestinationSystem { get; init; }
+
+ [JsonProperty("distance")]
+ public double Distance { get; init; }
+
+ [JsonProperty("efficiency")]
+ public long Efficiency { get; init; }
+
+ [JsonProperty("job")]
+ public string Job { get; init; }
+
+ [JsonProperty("range")]
+ public long Range { get; init; }
+
+ [JsonProperty("source_system")]
+ public string SourceSystem { get; init; }
+
+ [JsonProperty("system_jumps")]
+ public IReadOnlyCollection SystemJumps { get; init; }
+
+ [JsonProperty("total_jumps")]
+ public long TotalJumps { get; init; }
+
+ [JsonProperty("via")]
+ public string[] Via { get; init; }
+
+ public class SystemJump
+ {
+ [JsonProperty("distance_jumped")]
+ public double DistanceJumped { get; init; }
+
+ [JsonProperty("distance_left")]
+ public double DistanceLeft { get; init; }
+
+ [JsonProperty("id64")]
+ public long Id { get; init; }
+
+ [JsonProperty("jumps")]
+ public long Jumps { get; init; }
+
+ [JsonProperty("neutron_star")]
+ public bool NeutronStar { get; init; }
+
+ [JsonProperty("system")]
+ public string System { get; init; }
+
+ [JsonProperty("x")]
+ public double X { get; init; }
+
+ [JsonProperty("y")]
+ public double Y { get; init; }
+
+ [JsonProperty("z")]
+ public double Z { get; init; }
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/RoutePlanner/Responses/TradeResponse.cs b/EliteAPI.Web.Spansh/RoutePlanner/Responses/TradeResponse.cs
new file mode 100644
index 00000000..7fb82a21
--- /dev/null
+++ b/EliteAPI.Web.Spansh/RoutePlanner/Responses/TradeResponse.cs
@@ -0,0 +1,67 @@
+using EliteAPI.Web.Models;
+using Newtonsoft.Json;
+
+namespace EliteAPI.Web.Spansh.RoutePlanner.Responses;
+
+public class TradeResponse : IWebApiResponse
+{
+ [JsonProperty("commodities")] public IReadOnlyCollection Commodities { get; set; }
+
+ [JsonProperty("cumulative_profit")] public long CumulativeProfit { get; set; }
+
+ [JsonProperty("destination")]public SystemInfo Destination { get; set; }
+
+ [JsonProperty("distance")] public double Distance { get; set; }
+
+ [JsonProperty("source")] public SystemInfo System { get; set; }
+
+ [JsonProperty("total_profit")] public long TotalProfit { get; set; }
+
+ public class Commodity
+ {
+ [JsonProperty("amount")] public long Amount { get; set; }
+
+ [JsonProperty("destination_commodity")]
+ public TradeCommodity DestinationCommodity { get; set; }
+
+ [JsonProperty("name")] public string Name { get; set; }
+
+ [JsonProperty("profit")] public long Profit { get; set; }
+
+ [JsonProperty("source_commodity")] public TradeCommodity SourceCommodity { get; set; }
+
+ [JsonProperty("total_profit")] public long TotalProfit { get; set; }
+ }
+
+ public class TradeCommodity
+ {
+ [JsonProperty("buy_price")] public long BuyPrice { get; set; }
+
+ [JsonProperty("demand")] public long Demand { get; set; }
+
+ [JsonProperty("sell_price")] public long SellPrice { get; set; }
+
+ [JsonProperty("supply")] public long Supply { get; set; }
+ }
+
+ public class SystemInfo
+ {
+ [JsonProperty("distance_to_arrival")] public long DistanceToArrival { get; set; }
+
+ [JsonProperty("market_id")] public long MarketId { get; set; }
+
+ [JsonProperty("market_updated_at")] public long MarketUpdatedAt { get; set; }
+
+ [JsonProperty("station")] public string Station { get; set; }
+
+ [JsonProperty("system")] public string Name { get; set; }
+
+ [JsonProperty("system_id64")] public long Id { get; set; }
+
+ [JsonProperty("x")] public double X { get; set; }
+
+ [JsonProperty("y")] public double Y { get; set; }
+
+ [JsonProperty("z")] public double Z { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/RoutePlanner/RoutePlannerApi.cs b/EliteAPI.Web.Spansh/RoutePlanner/RoutePlannerApi.cs
new file mode 100644
index 00000000..321cf021
--- /dev/null
+++ b/EliteAPI.Web.Spansh/RoutePlanner/RoutePlannerApi.cs
@@ -0,0 +1,38 @@
+using EliteAPI.Web.Models;
+using EliteAPI.Web.Spansh.RoutePlanner.Requests;
+using EliteAPI.Web.Spansh.RoutePlanner.Responses;
+using EliteAPI.Web.Spansh.Utilities.Requests;
+using EliteAPI.Web.Spansh.Utilities.Responses;
+using Microsoft.Extensions.Logging;
+using Somfic.Common;
+
+namespace EliteAPI.Web.Spansh.RoutePlanner;
+
+public class RoutePlannerApi : WebApiCategory
+{
+ private readonly ILogger _log;
+
+ public RoutePlannerApi(WebApi api, ILogger log) : base(api)
+ {
+ _log = log;
+ }
+
+ protected override string Endpoint => "";
+
+ public async Task> Neutron(NeutronRequest request)
+ {
+ var api = Api as SpanshApi;
+
+ var job = await Execute(request);
+
+ return await api.Utilities.FromJob(job);
+ }
+
+ public async Task>> Trade(TradeRequest request)
+ {
+ var api = Api as SpanshApi;
+ var job = await Execute(request);
+ return await api.Utilities.FromJob>(job);
+ }
+
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/SpanshApi.cs b/EliteAPI.Web.Spansh/SpanshApi.cs
new file mode 100644
index 00000000..26229861
--- /dev/null
+++ b/EliteAPI.Web.Spansh/SpanshApi.cs
@@ -0,0 +1,21 @@
+using EliteAPI.Web.Models;
+using EliteAPI.Web.Spansh.RoutePlanner;
+using EliteAPI.Web.Spansh.Utilities;
+using Newtonsoft.Json;
+
+namespace EliteAPI.Web.Spansh;
+
+public class SpanshApi : WebApi
+{
+ public SpanshApi(IServiceProvider services) : base(services)
+ {
+ Routes = AddCategory();
+ Utilities = AddCategory();
+ }
+
+ protected override string BaseUrl => "https://www.spansh.co.uk/api";
+
+ public RoutePlannerApi Routes { get; }
+
+ internal UtilitiesApi Utilities { get; }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/Utilities/Requests/JobRequest.cs b/EliteAPI.Web.Spansh/Utilities/Requests/JobRequest.cs
new file mode 100644
index 00000000..e31b1261
--- /dev/null
+++ b/EliteAPI.Web.Spansh/Utilities/Requests/JobRequest.cs
@@ -0,0 +1,15 @@
+using EliteAPI.Web.Models;
+
+namespace EliteAPI.Web.Spansh.Utilities.Requests;
+
+public class JobRequest : IWebApiRequest
+{
+ public JobRequest(string job)
+ {
+ Job = job;
+ }
+
+ public string Job { get; init; }
+ public string Endpoint => $"results/{Job}";
+ public HttpMethod Method => HttpMethod.Get;
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/Utilities/Requests/SystemRequest.cs b/EliteAPI.Web.Spansh/Utilities/Requests/SystemRequest.cs
new file mode 100644
index 00000000..0a766940
--- /dev/null
+++ b/EliteAPI.Web.Spansh/Utilities/Requests/SystemRequest.cs
@@ -0,0 +1,18 @@
+using EliteAPI.Web.Attributes;
+using EliteAPI.Web.Models;
+
+namespace EliteAPI.Web.Spansh.Utilities.Requests;
+
+public class SystemRequest : IWebApiRequest
+{
+ public SystemRequest(string query)
+ {
+ Query = query;
+ }
+
+ [QueryParameter("q")]
+ public string Query { get; init; }
+
+ public string Endpoint => "systems";
+ public HttpMethod Method => HttpMethod.Post;
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/Utilities/Responses/JobResponse.cs b/EliteAPI.Web.Spansh/Utilities/Responses/JobResponse.cs
new file mode 100644
index 00000000..ca6d624e
--- /dev/null
+++ b/EliteAPI.Web.Spansh/Utilities/Responses/JobResponse.cs
@@ -0,0 +1,22 @@
+using EliteAPI.Web.Models;
+using Newtonsoft.Json;
+
+namespace EliteAPI.Web.Spansh.Utilities.Responses;
+
+public class JobResponse : IWebApiResponse
+{
+ [JsonProperty("result")]
+ public T Result { get; init; }
+
+ [JsonProperty("status")]
+ public string Status { get; init; }
+}
+
+public class JobResponse : IWebApiResponse
+{
+ [JsonProperty("job")]
+ public string Job { get; init; }
+
+ [JsonProperty("status")]
+ public string Status { get; init; }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/Utilities/Responses/SystemResponse.cs b/EliteAPI.Web.Spansh/Utilities/Responses/SystemResponse.cs
new file mode 100644
index 00000000..24b5a88c
--- /dev/null
+++ b/EliteAPI.Web.Spansh/Utilities/Responses/SystemResponse.cs
@@ -0,0 +1,10 @@
+using EliteAPI.Web.Models;
+using Newtonsoft.Json;
+
+namespace EliteAPI.Web.Spansh.Utilities.Responses;
+
+public class SystemResponse : IWebApiResponse
+{
+ [JsonProperty("root")] // Json root attribute
+ public string[] Systems { get; init; }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web.Spansh/Utilities/UtilitiesApi.cs b/EliteAPI.Web.Spansh/Utilities/UtilitiesApi.cs
new file mode 100644
index 00000000..37c7d4da
--- /dev/null
+++ b/EliteAPI.Web.Spansh/Utilities/UtilitiesApi.cs
@@ -0,0 +1,60 @@
+using EliteAPI.Web.Models;
+using EliteAPI.Web.Spansh.RoutePlanner.Responses;
+using EliteAPI.Web.Spansh.Utilities.Requests;
+using EliteAPI.Web.Spansh.Utilities.Responses;
+using Microsoft.Extensions.Logging;
+using Somfic.Common;
+
+namespace EliteAPI.Web.Spansh.Utilities;
+
+public class UtilitiesApi : WebApiCategory
+{
+ private readonly ILogger _log;
+
+ public UtilitiesApi(ILogger log, WebApi api) : base(api)
+ {
+ _log = log;
+ }
+
+ protected override string Endpoint => "";
+
+ public async Task>> System(SystemRequest request)
+ {
+ return await Execute(request);
+ }
+
+ internal async Task>>> Job(JobRequest request)
+ {
+ while (true)
+ {
+ var result = await Execute>(request);
+
+ if (result.IsError)
+ return result;
+
+ var response = result.Expect();
+
+ if (response.Content.Status == "queued")
+ {
+ await Task.Delay(1000);
+ continue;
+ }
+
+ return result;
+ }
+ }
+
+ internal async Task> FromJob(WebApiResponse> job) where TResponse : class
+ {
+ var api = Api as SpanshApi;
+
+ var result = await job.MapAsync(
+ ok: x => api.Utilities.Job(new JobRequest(x.Content.Job)),
+ error: x => x
+ );
+
+ return result.Map>(
+ ok: x => x.Content.Result as TResponse,
+ error: x => x);
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/Abstractions/IWebApiCategory.cs b/EliteAPI.Web/Abstractions/IWebApiCategory.cs
new file mode 100644
index 00000000..1d5207d7
--- /dev/null
+++ b/EliteAPI.Web/Abstractions/IWebApiCategory.cs
@@ -0,0 +1,22 @@
+using EliteAPI.Web.Models;
+
+namespace EliteAPI.Web.Abstractions;
+
+public interface IWebApiCategory
+{
+ /// Executes a HTTP request.
+ /// The generic type. The request will be instantiated using the provider
+ /// The generic type
+ /// The parsed generic with the type response
+ internal Task> Execute()
+ where TResponse : IWebApiResponse where TRequest : IWebApiRequest;
+
+ /// Executes a HTTP request.
+ /// The instance of the type
+ /// The generic type
+ /// The generic type
+ /// The parsed generic with the type response
+ /// Throws a if the body could not be parsed into
+ internal Task> Execute(TRequest? request)
+ where TResponse : IWebApiResponse where TRequest : IWebApiRequest;
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/Attributes/BodyParameterAttribute.cs b/EliteAPI.Web/Attributes/BodyParameterAttribute.cs
new file mode 100644
index 00000000..6a03efaa
--- /dev/null
+++ b/EliteAPI.Web/Attributes/BodyParameterAttribute.cs
@@ -0,0 +1,27 @@
+namespace EliteAPI.Web.Attributes;
+
+/// A parameter in the body of a request.
+[AttributeUsage(AttributeTargets.Property)]
+public class BodyParameterAttribute : Attribute
+{
+ public BodyParameterAttribute(string key)
+ {
+ Key = key;
+ }
+
+ /// The key of the parameter in the body.
+ public string Key { get; }
+}
+
+/// A secret value extracted through previous authentication.
+[AttributeUsage(AttributeTargets.Property)]
+public class SecretAttribute : Attribute
+{
+ public SecretAttribute(string key)
+ {
+ Key = key;
+ }
+
+ /// The key of the secret.
+ public string Key { get; }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/Attributes/QueryParameterAttribute.cs b/EliteAPI.Web/Attributes/QueryParameterAttribute.cs
new file mode 100644
index 00000000..ea2f2bcd
--- /dev/null
+++ b/EliteAPI.Web/Attributes/QueryParameterAttribute.cs
@@ -0,0 +1,14 @@
+namespace EliteAPI.Web.Attributes;
+
+/// A parameter in the query of a request.
+[AttributeUsage(AttributeTargets.Property)]
+public class QueryParameterAttribute : Attribute
+{
+ public QueryParameterAttribute(string key)
+ {
+ Key = key;
+ }
+
+ /// The key of the parameter in the query.
+ public string Key { get; }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/EliteAPI.Web.csproj b/EliteAPI.Web/EliteAPI.Web.csproj
new file mode 100644
index 00000000..58d006bb
--- /dev/null
+++ b/EliteAPI.Web/EliteAPI.Web.csproj
@@ -0,0 +1,21 @@
+
+
+ enable
+ enable
+ default
+ netstandard2.0
+ 3.1.0.0
+ 3.1.0.0
+ 3.1.0-alpha.182+Branch.dev.Sha.0fd6d5a6f5a17cbe313c176d121f8bf359e348de
+ 3.1.0-alpha0182
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EliteAPI.Web/Exceptions/WebApiException.cs b/EliteAPI.Web/Exceptions/WebApiException.cs
new file mode 100644
index 00000000..9b6bac2f
--- /dev/null
+++ b/EliteAPI.Web/Exceptions/WebApiException.cs
@@ -0,0 +1,18 @@
+namespace EliteAPI.Web.Exceptions;
+
+public class WebApiException : Exception
+{
+ public WebApiException(string error, HttpResponseMessage response) : base(BuildMessage(error, response))
+ {
+ }
+
+ public WebApiException(string error, HttpResponseMessage response, Exception innerException) : base(
+ BuildMessage(error, response), innerException)
+ {
+ }
+
+ private static string BuildMessage(string error, HttpResponseMessage response)
+ {
+ return $"{(int) response.StatusCode} request failed ({response.StatusCode}). {response.ReasonPhrase}: {error}.";
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/Extensions/WebApiHttpContentExtensions.cs b/EliteAPI.Web/Extensions/WebApiHttpContentExtensions.cs
new file mode 100644
index 00000000..e11e5dd4
--- /dev/null
+++ b/EliteAPI.Web/Extensions/WebApiHttpContentExtensions.cs
@@ -0,0 +1,24 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+// ReSharper disable once CheckNamespace
+namespace System.Net.Http;
+
+public static class WebApiHttpContentExtensions
+{
+ public static async Task<(T parsed, string json)> ReadAsJsonAsync(this HttpContent content)
+ {
+ var json = await content.ReadAsStringAsync();
+
+ var jObject = JToken.Parse(json);
+ if (jObject.Type == JTokenType.Array) json = "{ root: " + json + " }"; // todo: fix this lol
+
+ var body = JsonConvert.DeserializeObject(json);
+
+ if (body == null)
+ throw new JsonException("Could not parse the request's response into the given JSON schema.")
+ {Data = {["Json"] = json, ["Schema"] = typeof(T).Name}};
+
+ return (body, json);
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/Extensions/WebApiServiceCollectionExtensions.cs b/EliteAPI.Web/Extensions/WebApiServiceCollectionExtensions.cs
new file mode 100644
index 00000000..adda5953
--- /dev/null
+++ b/EliteAPI.Web/Extensions/WebApiServiceCollectionExtensions.cs
@@ -0,0 +1,22 @@
+using EliteAPI.Web;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Extensions.DependencyInjection;
+
+public static class WebApiServiceCollectionExtensions
+{
+ /// Adds EliteAPI services to the specified .
+ /// The to add services to.
+ /// The so that additional calls can be chained.
+ public static IServiceCollection AddWebApi(this IServiceCollection services) where T : WebApi
+ {
+ services.AddHttpClient(typeof(T).Name);
+
+ services.TryAddSingleton();
+
+ return services;
+ }
+
+ // todo: add overload with configuration
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/Models/IWebApiRequest.cs b/EliteAPI.Web/Models/IWebApiRequest.cs
new file mode 100644
index 00000000..6ddf0382
--- /dev/null
+++ b/EliteAPI.Web/Models/IWebApiRequest.cs
@@ -0,0 +1,10 @@
+namespace EliteAPI.Web.Models;
+
+public interface IWebApiRequest
+{
+ /// The endpoint of the request, based on the category.
+ public string Endpoint { get; }
+
+ /// The HTTP method of the request.
+ public HttpMethod Method { get; }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/Models/IWebApiResponse.cs b/EliteAPI.Web/Models/IWebApiResponse.cs
new file mode 100644
index 00000000..cfca68da
--- /dev/null
+++ b/EliteAPI.Web/Models/IWebApiResponse.cs
@@ -0,0 +1,5 @@
+namespace EliteAPI.Web.Models;
+
+public interface IWebApiResponse
+{
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/Models/WebApiResponse.cs b/EliteAPI.Web/Models/WebApiResponse.cs
new file mode 100644
index 00000000..c585becd
--- /dev/null
+++ b/EliteAPI.Web/Models/WebApiResponse.cs
@@ -0,0 +1,59 @@
+namespace EliteAPI.Web.Models;
+
+public readonly struct WebApiResponse
+{
+ private readonly TValue? _value;
+ private readonly Exception? _error;
+
+ public static implicit operator WebApiResponse(TValue value) => new(value);
+ private WebApiResponse(TValue value)
+ {
+ _value = value;
+ _error = default;
+ IsOkay = true;
+ }
+
+ public static implicit operator WebApiResponse(Exception error) => new(error);
+ private WebApiResponse(Exception error)
+ {
+ _value = default;
+ _error = error;
+ IsOkay = false;
+ }
+
+ public bool IsOkay { get; }
+
+ public bool IsError => !IsOkay;
+
+ public WebApiResponse On(Action ok, Action error)
+ {
+ if (IsOkay)
+ ok(_value!);
+ else
+ error(_error!);
+ return this;
+ }
+
+
+ public TResult Map(Func ok, Func error) =>
+ IsOkay ? ok(_value!) : error(_error!);
+
+ public WebApiResponse Map(Func ok) =>
+ IsOkay ? ok(_value!) : _error!;
+
+ public async Task MapAsync(Func> ok, Func error)
+ {
+ return IsOkay ? await ok(_value!) : error(_error!);
+ }
+
+ public async Task> MapAsync(Func> ok)
+ {
+ return IsOkay ? await ok(_value!) : _error!;
+ }
+
+ public TValue Expect()
+ {
+ if (IsOkay) return _value!;
+ throw _error!;
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/Models/WebApiResult.cs b/EliteAPI.Web/Models/WebApiResult.cs
new file mode 100644
index 00000000..7c128e05
--- /dev/null
+++ b/EliteAPI.Web/Models/WebApiResult.cs
@@ -0,0 +1,31 @@
+using System.Net;
+using System.Net.Http.Headers;
+
+namespace EliteAPI.Web.Models;
+
+public readonly struct WebApiResult where T : IWebApiResponse
+{
+ private readonly HttpResponseMessage _response;
+ private readonly Exception _exception;
+
+ public WebApiResult(HttpResponseMessage response, (T parsed, string raw) result)
+ {
+ _response = response;
+ Content = result.parsed;
+ RawContent = result.raw;
+ }
+
+ public T Content { get; }
+ public string RawContent { get; }
+ public HttpContentHeaders ContentHeaders { get; }
+ public HttpResponseHeaders Headers { get; }
+ public bool IsSuccessStatusCode { get; }
+ public Version Version { get; }
+ public HttpStatusCode StatusCode { get; }
+ public string? ReasonPhrase { get; }
+
+ public void EnsureSuccessStatusCode()
+ {
+ _response.EnsureSuccessStatusCode();
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/WebApi.cs b/EliteAPI.Web/WebApi.cs
new file mode 100644
index 00000000..e7e15494
--- /dev/null
+++ b/EliteAPI.Web/WebApi.cs
@@ -0,0 +1,43 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace EliteAPI.Web;
+
+public abstract class WebApi
+{
+ internal readonly IServiceProvider Services;
+ private readonly IDictionary _secrets = new Dictionary();
+
+ protected WebApi(IServiceProvider services)
+ {
+ Services = services;
+ }
+
+ protected internal abstract string BaseUrl { get; }
+
+ protected T AddCategory(TWebApi api) where T : WebApiCategory
+ {
+ return ActivatorUtilities.CreateInstance(Services, api);
+ }
+
+ protected T AddCategory() where T : WebApiCategory
+ {
+ return ActivatorUtilities.CreateInstance(Services, this);
+ }
+
+ protected void SetSecret(string key, string value)
+ {
+ _secrets[key] = value;
+ }
+
+ protected internal string GetSecret(string key)
+ {
+ if (!_secrets.ContainsKey(key)) throw new Exception($"Secret {key} not found");
+
+ return _secrets[key];
+ }
+
+ protected internal bool HasSecret(string key)
+ {
+ return _secrets.ContainsKey(key);
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.Web/WebApiCategory.cs b/EliteAPI.Web/WebApiCategory.cs
new file mode 100644
index 00000000..ac68aadf
--- /dev/null
+++ b/EliteAPI.Web/WebApiCategory.cs
@@ -0,0 +1,129 @@
+using System.Reflection;
+using System.Web;
+using EliteAPI.Web.Attributes;
+using EliteAPI.Web.Exceptions;
+using EliteAPI.Web.Models;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json.Linq;
+
+namespace EliteAPI.Web;
+
+public abstract class WebApiCategory
+{
+ private readonly ILogger _log;
+
+ protected WebApiCategory(WebApi api)
+ {
+ Api = api;
+ Http = api.Services.GetRequiredService().CreateClient(); //todo: config through name here?
+ _log = api.Services.GetRequiredService>();
+ }
+
+ protected WebApi Api { get; }
+ protected HttpClient Http { get; }
+
+ protected abstract string Endpoint { get; }
+
+ protected internal async Task>> Execute() where TResponse : IWebApiResponse
+ where TRequest : IWebApiRequest
+ {
+ try
+ {
+ var request = ActivatorUtilities.CreateInstance(Api.Services);
+ return await Execute(request);
+ }
+ catch (Exception ex)
+ {
+ return ex;
+ }
+ }
+
+ protected internal async Task>> Execute(TRequest? request)
+ where TResponse : IWebApiResponse where TRequest : IWebApiRequest
+ {
+ if (request == null)
+ return await Execute();
+
+ var requestMessage = BuildRequest(this, request);
+
+ var response = await Http.SendAsync(requestMessage);
+
+ if (!response.IsSuccessStatusCode)
+ {
+ var errorContent = await response.Content.ReadAsStringAsync();
+ var errorObject = JObject.Parse(errorContent);
+ var error = errorObject["error"]?.ToString() ?? "Unknown error";
+
+ var ex = new WebApiException(error, response);
+
+ return ex;
+ }
+
+ // Deserialize the response
+ var body = await response.Content.ReadAsJsonAsync();
+
+ return new WebApiResult(response, body);
+ }
+
+ private HttpRequestMessage BuildRequest(WebApiCategory category, IWebApiRequest request)
+ {
+ // Trim the url segments
+ var baseUri = category.Api.BaseUrl.Trim('/');
+ var categoryEndpoint = category.Endpoint.Trim('/');
+ var requestEndpoint = request.Endpoint.Trim('/');
+
+ var apiPart = $"{categoryEndpoint}/{requestEndpoint}".Replace("//", "/").Trim('/');
+
+ // Build the request URI
+ var uriBuilder = new UriBuilder($"{baseUri}/{apiPart}");
+
+ // Create a query builder
+ var query = HttpUtility.ParseQueryString(uriBuilder.Query);
+
+ // Find all the properties with the QueryParameter attribute
+ var queryProperties = request
+ .GetType()
+ .GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(p => p.GetCustomAttributes(typeof(QueryParameterAttribute), true).Any());
+
+ // Add the query parameters
+ foreach (var property in queryProperties)
+ {
+ var key = property.GetCustomAttribute()?.Key;
+ var value = GetPropertyValue(property, request);
+
+ query.Add(key, value);
+ }
+
+ // Apply the query parameters
+ uriBuilder.Query = query.ToString();
+
+ // Extract the URI
+ var uri = uriBuilder.Uri;
+
+ //todo: add headers
+ //todo: add body
+
+ return new HttpRequestMessage(request.Method, uri);
+ }
+
+ private string GetPropertyValue(PropertyInfo property, object instance)
+ {
+ var value = property.GetValue(instance)?.ToString();
+
+ var attribute = property.GetCustomAttribute();
+
+ if (attribute != null)
+ {
+ var key = attribute.Key;
+
+ if (Api.HasSecret(key))
+ value = Api.GetSecret(key);
+ else
+ _log.LogWarning("Secret key {Key} not found in secrets", key);
+ }
+
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/EliteAPI.sln b/EliteAPI.sln
index d56f1594..58e5793d 100644
--- a/EliteAPI.sln
+++ b/EliteAPI.sln
@@ -7,14 +7,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EliteAPI", "EliteAPI\EliteA
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EliteAPI.Events", "EliteAPI.Events\EliteAPI.Events.csproj", "{9E3E2DA3-51DF-487A-B85E-CDACE5F1AA29}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{98F4FCB9-F5A2-4159-97F0-3FEEE6620364}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EliteAPI.Abstractions", "EliteAPI.Abstractions\EliteAPI.Abstractions.csproj", "{D8D89BC9-D7AA-447E-BBC7-E744F00720B0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{2EF6A15E-9838-41A6-9A02-DB1CF9FC0203}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EliteAPI.Tests", "EliteAPI.Tests\EliteAPI.Tests.csproj", "{856F6019-7B19-40C7-9E45-A0FC155734E0}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{C7227294-F9EC-4261-A3EC-46905B102AAA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EliteAPI.Web", "EliteAPI.Web\EliteAPI.Web.csproj", "{5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EliteAPI.Web.Spansh", "EliteAPI.Web.Spansh\EliteAPI.Web.Spansh.csproj", "{CD529DF1-E744-4CC5-8403-D9B4491CF039}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.Web", "Examples\Example.Web\Example.Web.csproj", "{227FD5FE-1EC3-4EF6-A91F-258A2212CDD8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Examples\Example\Example.csproj", "{3081E506-FF91-4C57-A687-F5E125AC1EB3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -47,14 +55,6 @@ Global
{9E3E2DA3-51DF-487A-B85E-CDACE5F1AA29}.Release|Any CPU.Build.0 = Release|Any CPU
{9E3E2DA3-51DF-487A-B85E-CDACE5F1AA29}.Release|x86.ActiveCfg = Release|Any CPU
{9E3E2DA3-51DF-487A-B85E-CDACE5F1AA29}.Release|x86.Build.0 = Release|Any CPU
- {98F4FCB9-F5A2-4159-97F0-3FEEE6620364}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {98F4FCB9-F5A2-4159-97F0-3FEEE6620364}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {98F4FCB9-F5A2-4159-97F0-3FEEE6620364}.Debug|x86.ActiveCfg = Debug|Any CPU
- {98F4FCB9-F5A2-4159-97F0-3FEEE6620364}.Debug|x86.Build.0 = Debug|Any CPU
- {98F4FCB9-F5A2-4159-97F0-3FEEE6620364}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {98F4FCB9-F5A2-4159-97F0-3FEEE6620364}.Release|Any CPU.Build.0 = Release|Any CPU
- {98F4FCB9-F5A2-4159-97F0-3FEEE6620364}.Release|x86.ActiveCfg = Release|Any CPU
- {98F4FCB9-F5A2-4159-97F0-3FEEE6620364}.Release|x86.Build.0 = Release|Any CPU
{D8D89BC9-D7AA-447E-BBC7-E744F00720B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8D89BC9-D7AA-447E-BBC7-E744F00720B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8D89BC9-D7AA-447E-BBC7-E744F00720B0}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -71,12 +71,47 @@ Global
{856F6019-7B19-40C7-9E45-A0FC155734E0}.Release|Any CPU.Build.0 = Release|Any CPU
{856F6019-7B19-40C7-9E45-A0FC155734E0}.Release|x86.ActiveCfg = Release|Any CPU
{856F6019-7B19-40C7-9E45-A0FC155734E0}.Release|x86.Build.0 = Release|Any CPU
+ {5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1}.Debug|x86.Build.0 = Debug|Any CPU
+ {5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1}.Release|x86.ActiveCfg = Release|Any CPU
+ {5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1}.Release|x86.Build.0 = Release|Any CPU
+ {CD529DF1-E744-4CC5-8403-D9B4491CF039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CD529DF1-E744-4CC5-8403-D9B4491CF039}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CD529DF1-E744-4CC5-8403-D9B4491CF039}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CD529DF1-E744-4CC5-8403-D9B4491CF039}.Debug|x86.Build.0 = Debug|Any CPU
+ {CD529DF1-E744-4CC5-8403-D9B4491CF039}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CD529DF1-E744-4CC5-8403-D9B4491CF039}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CD529DF1-E744-4CC5-8403-D9B4491CF039}.Release|x86.ActiveCfg = Release|Any CPU
+ {CD529DF1-E744-4CC5-8403-D9B4491CF039}.Release|x86.Build.0 = Release|Any CPU
+ {227FD5FE-1EC3-4EF6-A91F-258A2212CDD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {227FD5FE-1EC3-4EF6-A91F-258A2212CDD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {227FD5FE-1EC3-4EF6-A91F-258A2212CDD8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {227FD5FE-1EC3-4EF6-A91F-258A2212CDD8}.Debug|x86.Build.0 = Debug|Any CPU
+ {227FD5FE-1EC3-4EF6-A91F-258A2212CDD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {227FD5FE-1EC3-4EF6-A91F-258A2212CDD8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {227FD5FE-1EC3-4EF6-A91F-258A2212CDD8}.Release|x86.ActiveCfg = Release|Any CPU
+ {227FD5FE-1EC3-4EF6-A91F-258A2212CDD8}.Release|x86.Build.0 = Release|Any CPU
+ {3081E506-FF91-4C57-A687-F5E125AC1EB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3081E506-FF91-4C57-A687-F5E125AC1EB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3081E506-FF91-4C57-A687-F5E125AC1EB3}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3081E506-FF91-4C57-A687-F5E125AC1EB3}.Debug|x86.Build.0 = Debug|Any CPU
+ {3081E506-FF91-4C57-A687-F5E125AC1EB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3081E506-FF91-4C57-A687-F5E125AC1EB3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3081E506-FF91-4C57-A687-F5E125AC1EB3}.Release|x86.ActiveCfg = Release|Any CPU
+ {3081E506-FF91-4C57-A687-F5E125AC1EB3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {98F4FCB9-F5A2-4159-97F0-3FEEE6620364} = {2EF6A15E-9838-41A6-9A02-DB1CF9FC0203}
+ {5D61C5AF-0D5F-4CFC-9F16-8D57463BC3D1} = {C7227294-F9EC-4261-A3EC-46905B102AAA}
+ {CD529DF1-E744-4CC5-8403-D9B4491CF039} = {C7227294-F9EC-4261-A3EC-46905B102AAA}
+ {227FD5FE-1EC3-4EF6-A91F-258A2212CDD8} = {2EF6A15E-9838-41A6-9A02-DB1CF9FC0203}
+ {3081E506-FF91-4C57-A687-F5E125AC1EB3} = {2EF6A15E-9838-41A6-9A02-DB1CF9FC0203}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0BCC51F1-912C-4035-9484-7A45F70F1790}
diff --git a/EliteAPI/EliteAPI.csproj b/EliteAPI/EliteAPI.csproj
index 2be30af1..cbf03196 100644
--- a/EliteAPI/EliteAPI.csproj
+++ b/EliteAPI/EliteAPI.csproj
@@ -1,7 +1,7 @@
- 3.0.15.0
- 3.0.15.0
+ 3.1.0.0
+ 3.1.0.0
https://github.com/EliteAPI/EliteAPI
https://github.com/EliteAPI/EliteAPI
@@ -11,14 +11,14 @@
A powerful event based .NET API for Elite: Dangerous
© Somfic 2022
false
- 3.0.15
+ 3.1.0-alpha0182
true
icon.png
true
10
true
Somfic
- 3.0.15+13.Branch.main.Sha.aa3bd21915f470956d90f87a2d089e6d67be09ae
+ 3.1.0-alpha.182+Branch.dev.Sha.0fd6d5a6f5a17cbe313c176d121f8bf359e348de
enable
netstandard2.0
diff --git a/EliteAPI/EliteAPI.xml b/EliteAPI/EliteAPI.xml
index 9b8dad0f..c08500ca 100644
--- a/EliteAPI/EliteAPI.xml
+++ b/EliteAPI/EliteAPI.xml
@@ -223,6 +223,9 @@
+
+
+
diff --git a/EliteAPI/Events/Events.cs b/EliteAPI/Events/Events.cs
index 7cc8dd64..02e1515a 100644
--- a/EliteAPI/Events/Events.cs
+++ b/EliteAPI/Events/Events.cs
@@ -36,7 +36,7 @@ public Events(ILogger? log, IEventParser eventParser)
}
///
- public IEnumerable EventTypes => _eventHandlers.Keys;
+ public IReadOnlyCollection EventTypes => _eventHandlers.Keys;
///
public IReadOnlyCollection<(IEvent @event, EventContext context)> PreviousEvents => _previousEvents.AsReadOnly();
diff --git a/Examples/Example.Web/Example.Web.csproj b/Examples/Example.Web/Example.Web.csproj
new file mode 100644
index 00000000..41c4614a
--- /dev/null
+++ b/Examples/Example.Web/Example.Web.csproj
@@ -0,0 +1,19 @@
+
+
+ Exe
+ net7.0
+ enable
+ enable
+ false
+ 3.1.0.0
+ 3.1.0.0
+ 3.1.0-alpha.182+Branch.dev.Sha.0fd6d5a6f5a17cbe313c176d121f8bf359e348de
+ 3.1.0-alpha0182
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Examples/Example.Web/Program.cs b/Examples/Example.Web/Program.cs
new file mode 100644
index 00000000..e868d59c
--- /dev/null
+++ b/Examples/Example.Web/Program.cs
@@ -0,0 +1,40 @@
+using EliteAPI.Web.Spansh;
+using EliteAPI.Web.Spansh.RoutePlanner.Requests;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+var spansh = new HostBuilder()
+ .ConfigureServices(s =>
+ {
+ s.AddWebApi();
+ })
+ .ConfigureLogging(s =>
+ {
+ s.AddSimpleConsole();
+ s.AddFilter("Microsoft", LogLevel.Warning);
+ s.AddFilter("System", LogLevel.Warning);
+
+ })
+ .Build()
+ .Services
+ .GetRequiredService();
+
+Console.WriteLine("Getting route ... ");
+var response = await spansh.Routes.Trade(new TradeRequest("Shinrarta Dezhra", "Jameson Memorial", 50, 720));
+
+response.On(
+ ok: route =>
+ {
+ foreach (var stop in route)
+ {
+ Console.WriteLine(stop.System.Name + " - " + stop.System.Station);
+ foreach (var commodity in stop.Commodities)
+ {
+ Console.WriteLine($" - {commodity.Name} {commodity.Amount}x (+{commodity.TotalProfit:N0}cr)");
+ }
+
+ Console.WriteLine();
+ }
+ },
+ error: e => Console.WriteLine(e.Message));
\ No newline at end of file
diff --git a/Example/Example.csproj b/Examples/Example/Example.csproj
similarity index 64%
rename from Example/Example.csproj
rename to Examples/Example/Example.csproj
index ffb65a22..696ded5d 100644
--- a/Example/Example.csproj
+++ b/Examples/Example/Example.csproj
@@ -2,10 +2,10 @@
enable
enable
- 3.0.15.0
- 3.0.15.0
- 3.0.15+13.Branch.main.Sha.aa3bd21915f470956d90f87a2d089e6d67be09ae
- 3.0.15
+ 3.1.0.0
+ 3.1.0.0
+ 3.1.0-alpha.182+Branch.dev.Sha.0fd6d5a6f5a17cbe313c176d121f8bf359e348de
+ 3.1.0-alpha0182
false
10
Exe
@@ -15,6 +15,8 @@
+
+
diff --git a/Example/Program.cs b/Examples/Example/Program.cs
similarity index 100%
rename from Example/Program.cs
rename to Examples/Example/Program.cs
diff --git a/Example/appsettings.json b/Examples/Example/appsettings.json
similarity index 100%
rename from Example/appsettings.json
rename to Examples/Example/appsettings.json
diff --git a/qodana.yaml b/qodana.yaml
new file mode 100644
index 00000000..99a40de6
--- /dev/null
+++ b/qodana.yaml
@@ -0,0 +1,29 @@
+#-------------------------------------------------------------------------------#
+# Qodana analysis is configured by qodana.yaml file #
+# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
+#-------------------------------------------------------------------------------#
+version: "1.0"
+
+#Specify inspection profile for code analysis
+profile:
+ name: qodana.starter
+
+#Enable inspections
+#include:
+# - name:
+
+#Disable inspections
+#exclude:
+# - name:
+# paths:
+# -
+
+#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
+#bootstrap: sh ./prepare-qodana.sh
+
+#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
+#plugins:
+# - id: #(plugin id can be found at https://plugins.jetbrains.com)
+
+#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
+linter: jetbrains/qodana-dotnet:latest