aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs')
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs237
1 files changed, 199 insertions, 38 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
index 2bba84a..0ae6ed6 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
@@ -1,5 +1,5 @@
/*
-* Copyright (c) 2022 Vaughn Nugent
+* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Extensions.Loading
@@ -23,12 +23,10 @@
*/
using System;
-using System.Linq;
using System.Text.Json;
using System.Reflection;
using System.Collections.Generic;
-
-using VNLib.Utils.Extensions;
+using System.Diagnostics.CodeAnalysis;
namespace VNLib.Plugins.Extensions.Loading
{
@@ -52,8 +50,15 @@ namespace VNLib.Plugins.Extensions.Loading
{
ConfigVarName = configVarName;
}
+
+ /// <summary>
+ /// When true or not configured, signals that the type requires a configuration scope
+ /// when loaded. When false, and configuration is not found, signals to the service loading
+ /// system to continue without configuration
+ /// </summary>
+ public bool Required { get; init; } = true;
}
-
+
/// <summary>
/// Contains extensions for plugin configuration specifc extensions
/// </summary>
@@ -69,22 +74,26 @@ namespace VNLib.Plugins.Extensions.Loading
/// <typeparam name="T">The type to get the configuration of</typeparam>
/// <param name="plugin"></param>
/// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <exception cref="KeyNotFoundException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfigForType<T>(this PluginBase plugin)
+ public static IConfigScope GetConfigForType<T>(this PluginBase plugin)
{
Type t = typeof(T);
return plugin.GetConfigForType(t);
}
+
/// <summary>
- /// Retrieves a top level configuration dictionary of elements with the specified property name,
- /// from the plugin config first, or falls back to the host config file
+ /// Retrieves a top level configuration dictionary of elements with the specified property name.
/// </summary>
+ /// <remarks>
+ /// Search order: Plugin config, fall back to host config, throw if not found
+ /// </remarks>
/// <param name="plugin"></param>
/// <param name="propName">The config property name to retrieve</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
/// <exception cref="KeyNotFoundException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfig(this PluginBase plugin, string propName)
+ public static IConfigScope GetConfig(this PluginBase plugin, string propName)
{
plugin.ThrowIfUnloaded();
try
@@ -96,29 +105,33 @@ namespace VNLib.Plugins.Extensions.Loading
el = plugin.HostConfig.GetProperty(propName);
}
//Get the top level config as a dictionary
- return el.EnumerateObject().ToDictionary(static k => k.Name, static k => k.Value);
+ return new ConfigScope(el, propName);
}
- catch(KeyNotFoundException)
+ catch (KeyNotFoundException)
{
throw new KeyNotFoundException($"Missing required top level configuration object '{propName}', in host/plugin configuration files");
}
}
+
/// <summary>
/// Retrieves a top level configuration dictionary of elements with the specified property name,
- /// from the plugin config first, or falls back to the host config file
+ /// or null if no configuration could be found
/// </summary>
+ /// <remarks>
+ /// Search order: Plugin config, fall back to host config, null not found
+ /// </remarks>
/// <param name="plugin"></param>
/// <param name="propName">The config property name to retrieve</param>
/// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement>? TryGetConfig(this PluginBase plugin, string propName)
+ public static IConfigScope? TryGetConfig(this PluginBase plugin, string propName)
{
plugin.ThrowIfUnloaded();
//Try to get the element from the plugin config first, or fallback to host
if (plugin.PluginConfig.TryGetProperty(propName, out JsonElement el) || plugin.HostConfig.TryGetProperty(propName, out el))
{
//Get the top level config as a dictionary
- return el.EnumerateObject().ToDictionary(static k => k.Name, static k => k.Value);
+ return new ConfigScope(el, propName);
}
//No config found
return null;
@@ -130,15 +143,65 @@ namespace VNLib.Plugins.Extensions.Loading
/// </summary>
/// <param name="plugin"></param>
/// <param name="type">The type to get configuration data for</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfigForType(this PluginBase plugin, Type type)
+ public static IConfigScope GetConfigForType(this PluginBase plugin, Type type)
+ {
+ _ = type ?? throw new ArgumentNullException(nameof(type));
+
+ string? configName = GetConfigNameForType(type);
+
+ if (configName == null)
+ {
+ ThrowConfigNotFoundForType(type);
+ }
+
+ return plugin.GetConfig(configName);
+ }
+
+ /// <summary>
+ /// Gets the configuration property name for the type
+ /// </summary>
+ /// <param name="type">The type to get the configuration name for</param>
+ /// <returns>The configuration property element name</returns>
+ public static string? GetConfigNameForType(Type type)
{
//Get config name attribute from plugin type
- ConfigurationNameAttribute? configName = type.GetCustomAttribute<ConfigurationNameAttribute>();
- return configName?.ConfigVarName == null
- ? throw new KeyNotFoundException("No configuration attribute set")
- : plugin.GetConfig(configName.ConfigVarName);
+ return type.GetCustomAttribute<ConfigurationNameAttribute>()?.ConfigVarName;
+ }
+
+ /// <summary>
+ /// Determines if the type requires a configuration element.
+ /// </summary>
+ /// <param name="type">The type to determine config required status</param>
+ /// <returns>
+ /// True if the configuration is required, or false if the <see cref="ConfigurationNameAttribute"/>
+ /// was not declared, or <see cref="ConfigurationNameAttribute.Required"/> is false
+ /// </returns>
+ public static bool ConfigurationRequired(Type type)
+ {
+ return type.GetCustomAttribute<ConfigurationNameAttribute>()?.Required ?? false;
+ }
+
+ /// <summary>
+ /// Throws a <see cref="KeyNotFoundException"/> with proper diagnostic information
+ /// for missing configuration for a given type
+ /// </summary>
+ /// <param name="type">The type to raise exception for</param>
+ /// <exception cref="KeyNotFoundException"></exception>
+ [DoesNotReturn]
+ public static void ThrowConfigNotFoundForType(Type type)
+ {
+ //Try to get the config property name for the type
+ string? configName = GetConfigNameForType(type);
+ if (configName != null)
+ {
+ throw new KeyNotFoundException($"Missing required configuration key {configName} for type {type.Name}");
+ }
+ else
+ {
+ throw new KeyNotFoundException($"Missing required configuration key for type {type.Name}");
+ }
}
/// <summary>
@@ -147,15 +210,38 @@ namespace VNLib.Plugins.Extensions.Loading
/// </summary>
/// <param name="obj">The object that a configuration can be retrieved for</param>
/// <param name="plugin">The plugin containing configuration variables</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
+ /// <returns>A <see cref="IConfigScope"/> of top level configuration elements for the type</returns>
/// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfig(this PluginBase plugin, object obj)
+ public static IConfigScope GetConfig(this PluginBase plugin, object obj)
{
Type t = obj.GetType();
return plugin.GetConfigForType(t);
}
/// <summary>
+ /// Deserialzes the configuration to the desired object and calls its
+ /// <see cref="IOnConfigValidation.Validate"/> method. Validation exceptions
+ /// are wrapped in a <see cref="ConfigrationValidationException"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="scope"></param>
+ /// <returns></returns>
+ /// <exception cref="ConfigrationValidationException"></exception>
+ public static T DeserialzeAndValidate<T>(this IConfigScope scope) where T : IOnConfigValidation
+ {
+ T conf = scope.Deserialze<T>();
+ try
+ {
+ conf.Validate();
+ }
+ catch(Exception ex)
+ {
+ throw new ConfigrationValidationException($"Configuration validation failed for type {typeof(T).Name}", ex);
+ }
+ return conf;
+ }
+
+ /// <summary>
/// Determines if the current plugin configuration contains the require properties to initialize
/// the type
/// </summary>
@@ -171,6 +257,94 @@ namespace VNLib.Plugins.Extensions.Loading
}
/// <summary>
+ /// Gets a given configuration element from the global configuration scope
+ /// and deserializes it into the desired type.
+ /// <para>
+ /// If the type inherits <see cref="IOnConfigValidation"/> the <see cref="IOnConfigValidation.Validate"/>
+ /// method is invoked, and exceptions are warpped in <see cref="ConfigrationValidationException"/>
+ /// </para>
+ /// <para>
+ /// If the type inherits <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync(PluginBase)"/>
+ /// method is called by the service scheduler
+ /// </para>
+ /// </summary>
+ /// <typeparam name="TConfig">The configuration type</typeparam>
+ /// <param name="plugin"></param>
+ /// <returns>The deserialzed configuration element</returns>
+ /// <exception cref="ConfigrationValidationException"></exception>
+ public static TConfig GetConfigElement<TConfig>(this PluginBase plugin)
+ {
+ //Deserialze the element
+ TConfig config = plugin.GetConfigForType<TConfig>().Deserialze<TConfig>();
+
+ //If the type is validatable, validate it
+ if(config is IOnConfigValidation conf)
+ {
+ try
+ {
+ conf.Validate();
+ }
+ catch (Exception ex)
+ {
+ throw new ConfigrationValidationException($"Configuration validation failed for type {typeof(TConfig).Name}", ex);
+ }
+ }
+
+ //If async config, load async
+ if(config is IAsyncConfigurable ac)
+ {
+ _ = plugin.ConfigureServiceAsync(ac);
+ }
+
+ return config;
+ }
+
+ /// <summary>
+ /// Gets a given configuration element from the global configuration scope
+ /// and deserializes it into the desired type.
+ /// <para>
+ /// If the type inherits <see cref="IOnConfigValidation"/> the <see cref="IOnConfigValidation.Validate"/>
+ /// method is invoked, and exceptions are warpped in <see cref="ConfigrationValidationException"/>
+ /// </para>
+ /// <para>
+ /// If the type inherits <see cref="IAsyncConfigurable"/> the <see cref="IAsyncConfigurable.ConfigureServiceAsync(PluginBase)"/>
+ /// method is called by the service scheduler
+ /// </para>
+ /// </summary>
+ /// <typeparam name="TConfig">The configuration type</typeparam>
+ /// <param name="plugin"></param>
+ /// <param name="elementName">The configuration element name override</param>
+ /// <returns>The deserialzed configuration element</returns>
+ /// <exception cref="ConfigrationValidationException"></exception>
+ public static TConfig GetConfigElement<TConfig>(this PluginBase plugin, string elementName)
+ {
+ //Deserialze the element
+ TConfig config = plugin.GetConfig(elementName).Deserialze<TConfig>();
+
+ //If the type is validatable, validate it
+ if (config is IOnConfigValidation conf)
+ {
+ try
+ {
+ conf.Validate();
+ }
+ catch (Exception ex)
+ {
+ throw new ConfigrationValidationException($"Configuration validation failed for type {typeof(TConfig).Name}", ex);
+ }
+ }
+
+ //If async config, load async
+ if (config is IAsyncConfigurable ac)
+ {
+ _ = plugin.ConfigureServiceAsync(ac);
+ }
+
+ return config;
+ }
+
+
+ /// <summary>
/// Attempts to load the basic S3 configuration variables required
/// for S3 client access
/// </summary>
@@ -179,21 +353,8 @@ namespace VNLib.Plugins.Extensions.Loading
public static S3Config? TryGetS3Config(this PluginBase plugin)
{
//Try get the config
- IReadOnlyDictionary<string, JsonElement>? s3conf = plugin.TryGetConfig(S3_CONFIG);
- if(s3conf == null)
- {
- return null;
- }
-
- //Try get the elements
- return new()
- {
- BaseBucket = s3conf.GetPropString("bucket"),
- ClientId = s3conf.GetPropString("access_key"),
- ServerAddress = s3conf.GetPropString("server_address"),
- UseSsl = s3conf.TryGetValue("use_ssl", out JsonElement el) && el.GetBoolean(),
- Region = s3conf.GetPropString("region"),
- };
+ IConfigScope? s3conf = plugin.TryGetConfig(S3_CONFIG);
+ return s3conf?.Deserialze<S3Config>();
}
}
}