diff --git a/ApiAggregator.sln b/ApiAggregator.sln
index 6bdaaf4..0a7fbcf 100644
--- a/ApiAggregator.sln
+++ b/ApiAggregator.sln
@@ -5,17 +5,13 @@ VisualStudioVersion = 17.9.34723.18
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4338FF70-3C81-4370-ACFB-00E14545BA99}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAggregator.Net", "src\ApiAggregator\ApiAggregator.Net.csproj", "{8250784C-5415-47C2-9FE4-9E54FA4672B6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiAggregator.Net", "src\ApiAggregator\ApiAggregator.Net.csproj", "{8250784C-5415-47C2-9FE4-9E54FA4672B6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{31E7A02C-167D-46FB-A90A-F3995FD5682D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAggregator.Tests", "tests\ApiAggregator.Tests\ApiAggregator.Tests.csproj", "{C9ED08F3-F754-4D7A-8034-8FC180EA7F7A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiAggregator.Tests", "tests\ApiAggregator.Tests\ApiAggregator.Tests.csproj", "{C9ED08F3-F754-4D7A-8034-8FC180EA7F7A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{D6340772-5767-4604-9E64-04078C0C2CAC}"
- ProjectSection(SolutionItems) = preProject
- LICENSE = LICENSE
- README.md = README.md
- EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{BCE2D3FE-6CF1-4932-9DEE-5B761A132C5E}"
ProjectSection(SolutionItems) = preProject
diff --git a/README.md b/README.md
index 119e167..3774166 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,6 @@
#### Extends `Schemio` for APIs
ApiAggregator uses `Schemio` to extend support for apis to configure hierarchical graph of `query`/`transformer` pairs to return aggregated data in a single response.
> You can read on [Schemio](https://github.com/CodeShayk/Schemio) for more details on the core functionality.
-
Please see appendix for schemio implementation in ApiAggregator.
diff --git a/src/ApiAggregator/ApiAggregator.Net.csproj b/src/ApiAggregator/ApiAggregator.Net.csproj
index 96e0d8d..fa1ca35 100644
--- a/src/ApiAggregator/ApiAggregator.Net.csproj
+++ b/src/ApiAggregator/ApiAggregator.Net.csproj
@@ -16,21 +16,31 @@
api, aggregator, api-aggregator, utility, api-utility, data-aggregator, api-response, api-response-aggregator
True
snupkg
+ LICENSE
-
+
True
\
-
+
+ True
+ \
+
+
True
\
+
+
+
+
+
diff --git a/src/ApiAggregator/BaseWebQuery.cs b/src/ApiAggregator/BaseWebQuery.cs
new file mode 100644
index 0000000..faf6204
--- /dev/null
+++ b/src/ApiAggregator/BaseWebQuery.cs
@@ -0,0 +1,157 @@
+using Microsoft.Extensions.Logging;
+using System.Text.Json;
+using Schemio;
+
+namespace ApiAggregator
+{
+ ///
+ /// Implement to create a Web query using api endpoint.
+ ///
+ /// Type of Query parameter
+ /// Type of Query Result
+ public abstract class BaseWebQuery : BaseQuery, IWebQuery, IRootQuery, IChildQuery
+ where TParameter : IQueryParameter where TResult : IQueryResult
+ {
+ protected BaseWebQuery(string baseAddress)
+ {
+ BaseAddress = baseAddress;
+ Url = GetUrl(QueryParameter);
+ Headers = GetHeaders();
+ }
+
+ ///
+ /// List of Request headers for the api call.
+ ///
+ public List> Headers { get; protected set; }
+
+ ///
+ /// Base address for the api call.
+ ///
+ public string BaseAddress { get; protected set; }
+
+ ///
+ /// Api endpoint - complete or relative.
+ ///
+ public string Url { get; protected set; }
+
+ ///
+ /// Override to pass custom headers with the api request.
+ ///
+ ///
+ protected virtual List>? GetHeaders()
+ { return []; }
+
+ ///
+ /// Implement to construct the api endpoint.
+ ///
+ /// Query Parameter
+ ///
+ protected abstract string? GetUrl(TParameter queryParameter);
+
+ ///
+ /// Implement to resolve query parameter.
+ ///
+ /// root context.
+ /// query result from parent query (when configured as nested query). Can be null.
+ protected abstract void ResolveQueryParameter(IDataContext context, IQueryResult parentQueryResult);
+
+ ///
+ /// Implement to resolve query parameter for nested queries
+ ///
+ /// root context
+ /// query result from parent query.
+ public void ResolveChildQueryParameter(IDataContext context, IQueryResult parentQueryResult)
+ {
+ ResolveQueryParameter(context, parentQueryResult);
+ }
+
+ ///
+ /// Implement to resolve query parameter for first level queries.
+ ///
+ /// root context
+ public void ResolveRootQueryParameter(IDataContext context)
+ {
+ ResolveQueryParameter(context, null);
+ }
+
+ ///
+ /// Run this web query to get results.
+ ///
+ /// HttpClientFactory
+ /// Logger
+ ///
+ /// when httpclientfactory is null.
+ public virtual async Task Run(IHttpClientFactory httpClientFactory, ILogger logger)
+ {
+ if (httpClientFactory == null)
+ throw new ArgumentNullException("HttpClientFactory is required");
+
+ var localStorage = new List();
+
+ logger?.LogInformation($"Run query: {GetType().Name}");
+
+ using (var client = httpClientFactory.CreateClient())
+ {
+ logger?.LogInformation($"Executing web queries on thread {Thread.CurrentThread.ManagedThreadId} (task {Task.CurrentId})");
+
+ try
+ {
+ HttpResponseMessage result;
+
+ try
+ {
+ if (!string.IsNullOrEmpty(BaseAddress))
+ client.BaseAddress = new Uri(BaseAddress);
+
+ if (Headers != null && Headers.Any())
+ foreach (var header in Headers)
+ client.DefaultRequestHeaders.Add(header.Key, header.Value);
+
+ result = await client.GetAsync(Url);
+
+ if (!result.IsSuccessStatusCode)
+ {
+ logger?.LogInformation($"Result of executing web query {Url} is not success status code");
+ }
+
+ var raw = result.Content.ReadAsStringAsync().Result;
+
+ if (string.IsNullOrWhiteSpace(raw))
+ logger?.LogInformation($"Result.Content of executing web query {Url} is null or whitespace");
+
+ if (ResultType.IsArray)
+ {
+ var arrObject = JsonSerializer.Deserialize(raw, ResultType);
+ if (arrObject != null)
+ localStorage.AddRange((IEnumerable)arrObject);
+ }
+ else
+ {
+ var obj = JsonSerializer.Deserialize(raw, ResultType);
+ if (obj != null)
+ localStorage.Add((TResult)obj);
+ }
+ }
+ catch (TaskCanceledException ex)
+ {
+ logger?.LogWarning(ex, $"An error occurred while sending the request. Query URL: {Url}");
+ }
+ catch (HttpRequestException ex)
+ {
+ logger?.LogWarning(ex, $"An error occurred while sending the request. Query URL: {Url}");
+ }
+ }
+ catch (AggregateException ex)
+ {
+ logger?.LogInformation($"Web query {GetType().Name} failed");
+ foreach (var e in ex.InnerExceptions)
+ {
+ logger?.LogError(e, "");
+ }
+ }
+ }
+
+ return localStorage.Cast().ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ApiAggregator/Class1.cs b/src/ApiAggregator/Class1.cs
deleted file mode 100644
index 82aca5f..0000000
--- a/src/ApiAggregator/Class1.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace ApiAggregator
-{
- public class Class1
- {
-
- }
-}
diff --git a/src/ApiAggregator/ColonSeparatedMatcher.cs b/src/ApiAggregator/ColonSeparatedMatcher.cs
new file mode 100644
index 0000000..be4c6ea
--- /dev/null
+++ b/src/ApiAggregator/ColonSeparatedMatcher.cs
@@ -0,0 +1,13 @@
+using Schemio;
+using Schemio.Helpers;
+
+namespace ApiAggregator
+{
+ public class ColonSeparatedMatcher : ISchemaPathMatcher
+ {
+ public bool IsMatch(string inputXPath, ISchemaPaths configuredXPaths) =>
+ // Does the template xpath contain any of the mapping xpaths?
+ inputXPath.IsNotNullOrEmpty()
+ && configuredXPaths.Paths.Any(x => inputXPath.ToLower().Contains(x.ToLower()));
+ }
+}
\ No newline at end of file
diff --git a/src/ApiAggregator/EnumerableExtensions.cs b/src/ApiAggregator/EnumerableExtensions.cs
new file mode 100644
index 0000000..fcbaf52
--- /dev/null
+++ b/src/ApiAggregator/EnumerableExtensions.cs
@@ -0,0 +1,13 @@
+using Schemio;
+
+namespace ApiAggregator
+{
+ public static class EnumerableExtensions
+ {
+ public static IEnumerable GetByType(this IEnumerable list) where T : class, IQuery
+ {
+ var filtered = list.Where(q => (q as T) != null);
+ return filtered.Cast();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ApiAggregator/IWebQuery.cs b/src/ApiAggregator/IWebQuery.cs
new file mode 100644
index 0000000..f89d5ae
--- /dev/null
+++ b/src/ApiAggregator/IWebQuery.cs
@@ -0,0 +1,14 @@
+using Microsoft.Extensions.Logging;
+using Schemio;
+
+namespace ApiAggregator
+{
+ public interface IWebQuery : IQuery
+ {
+ List> Headers { get; }
+ string BaseAddress { get; }
+ string Url { get; }
+
+ Task Run(IHttpClientFactory httpClientFactory, ILogger logger);
+ }
+}
\ No newline at end of file
diff --git a/src/ApiAggregator/QueryEngine.cs b/src/ApiAggregator/QueryEngine.cs
new file mode 100644
index 0000000..7e0562a
--- /dev/null
+++ b/src/ApiAggregator/QueryEngine.cs
@@ -0,0 +1,47 @@
+using Microsoft.Extensions.Logging;
+using Schemio;
+
+namespace ApiAggregator
+{
+ public class QueryEngine : IQueryEngine
+ {
+ private readonly ILogger logger;
+ private readonly IHttpClientFactory httpClientFactory;
+
+ public QueryEngine(IHttpClientFactory httpClientFactory, ILogger logger)
+ {
+ this.httpClientFactory = httpClientFactory;
+ this.logger = logger;
+ }
+
+ public bool CanExecute(IQuery query) => query is IWebQuery;
+
+ public IEnumerable Execute(IEnumerable queries)
+ {
+ if (queries == null || !queries.Any())
+ return [];
+
+ var webQueries = queries.GetByType();
+
+ if (!webQueries.Any())
+ return [];
+
+ logger.LogInformation($"Total web queries to execute: {webQueries.Count()}");
+
+ var tasks = webQueries
+ .Select(q => q.Run(httpClientFactory, logger))
+ .ToArray();
+
+ Task.WhenAll(tasks);
+
+ var result = new List();
+
+ foreach (var task in tasks)
+ {
+ result.AddRange(task.Result);
+ }
+
+ return result.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ApiAggregator/ServicesExtensions.cs b/src/ApiAggregator/ServicesExtensions.cs
new file mode 100644
index 0000000..27e6c3c
--- /dev/null
+++ b/src/ApiAggregator/ServicesExtensions.cs
@@ -0,0 +1,36 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Schemio;
+using Schemio.Impl;
+
+namespace ApiAggregator
+{
+ public static class ServicesExtensions
+ {
+ public static IServiceCollection UseApiAggregator(this IServiceCollection services, Func> schemas)
+ {
+ services.AddTransient(typeof(IQueryBuilder<>), typeof(QueryBuilder<>));
+ services.AddTransient(typeof(ITransformExecutor<>), typeof(TransformExecutor<>));
+ services.AddTransient(typeof(IDataProvider<>), typeof(DataProvider<>));
+ //services.AddTransient(typeof(IEntitySchema<>), typeof(BaseEntitySchema<>));
+
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+
+ //services.AddTransient((c) => schema);
+
+ return services;
+ }
+
+ public static IServiceCollection AddEntitySchema(this IServiceCollection services, IEntitySchema schema)
+ where TEntity : IEntity
+ {
+ if (schema != null)
+ services.AddTransient(c => (IEntitySchema)schema);
+
+ return services;
+ }
+ }
+}
\ No newline at end of file