Allowing untyped sections in the config

This commit is contained in:
Zoe Roux 2021-05-20 01:14:48 +02:00
parent 5f7604a563
commit 52e7093c26
7 changed files with 80 additions and 12 deletions

View File

@ -132,7 +132,7 @@ namespace Kyoo.Authentication
} }
string permStr = $"{permission.ToLower()}.{kind.ToString()!.ToLower()}"; string permStr = $"{permission.ToLower()}.{kind.ToString()!.ToLower()}";
string overallStr = $"{_group.ToString()}.{kind.ToString()!.ToLower()}"; string overallStr = $"{_group.ToString().ToLower()}.{kind.ToString()!.ToLower()}";
AuthenticateResult res = await context.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); AuthenticateResult res = await context.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
if (res.Succeeded) if (res.Succeeded)
{ {

View File

@ -87,5 +87,11 @@ namespace Kyoo.Models
{ {
return CreateReference(path, typeof(T)); return CreateReference(path, typeof(T));
} }
public static ConfigurationReference CreateUntyped(string path)
{
return new(path, null);
}
} }
} }

View File

@ -82,6 +82,20 @@ namespace Kyoo
services.AddSingleton(confRef); services.AddSingleton(confRef);
return services; return services;
} }
/// <summary>
/// Add an editable configuration to the editable configuration list.
/// WARNING: this method allow you to add an unmanaged type. This type won't be editable. This can be used
/// for external libraries or variable arguments.
/// </summary>
/// <param name="services">The service collection to edit</param>
/// <param name="path">The root path of the editable configuration. It should not be a nested type.</param>
/// <returns>The given service collection is returned.</returns>
public static IServiceCollection AddUntypedConfiguration(this IServiceCollection services, string path)
{
services.AddSingleton(ConfigurationReference.CreateUntyped(path));
return services;
}
/// <summary> /// <summary>
/// Get the public URL of kyoo using the given configuration instance. /// Get the public URL of kyoo using the given configuration instance.

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic; using System.Dynamic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -35,13 +36,30 @@ namespace Kyoo.Controllers
_references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase); _references = references.ToDictionary(x => x.Path, x => x.Type, StringComparer.OrdinalIgnoreCase);
} }
private Type GetType(string path)
{
path = path.Replace("__", ":");
// TODO handle lists and dictionaries.
if (_references.TryGetValue(path, out Type type))
{
if (type != null)
return type;
throw new ArgumentException($"The configuration at {path} is not editable or readable.");
}
string parent = path.Contains(':') ? path[..path.IndexOf(':')] : null;
if (parent != null && _references.TryGetValue(parent, out type) && type == null)
throw new ArgumentException($"The configuration at {path} is not editable or readable.");
throw new ItemNotFoundException($"No configuration exists for the name: {path}");
}
/// <inheritdoc /> /// <inheritdoc />
public object GetValue(string path) public object GetValue(string path)
{ {
path = path.Replace("__", ":"); path = path.Replace("__", ":");
// TODO handle lists and dictionaries. // TODO handle lists and dictionaries.
if (!_references.TryGetValue(path, out Type type)) Type type = GetType(path);
throw new ItemNotFoundException($"No configuration exists for the name: {path}");
object ret = _configuration.GetValue(type, path); object ret = _configuration.GetValue(type, path);
if (ret != null) if (ret != null)
return ret; return ret;
@ -55,8 +73,7 @@ namespace Kyoo.Controllers
{ {
path = path.Replace("__", ":"); path = path.Replace("__", ":");
// TODO handle lists and dictionaries. // TODO handle lists and dictionaries.
if (!_references.TryGetValue(path, out Type type)) Type type = GetType(path);
throw new ItemNotFoundException($"No configuration exists for the name: {path}");
if (typeof(T).IsAssignableFrom(type)) if (typeof(T).IsAssignableFrom(type))
throw new InvalidCastException($"The type {typeof(T).Name} is not valid for " + throw new InvalidCastException($"The type {typeof(T).Name} is not valid for " +
$"a resource of type {type.Name}."); $"a resource of type {type.Name}.");
@ -67,8 +84,7 @@ namespace Kyoo.Controllers
public async Task EditValue(string path, object value) public async Task EditValue(string path, object value)
{ {
path = path.Replace("__", ":"); path = path.Replace("__", ":");
if (!_references.TryGetValue(path, out Type type)) Type type = GetType(path);
throw new ItemNotFoundException($"No configuration exists for the name: {path}");
value = JObject.FromObject(value).ToObject(type); value = JObject.FromObject(value).ToObject(type);
if (value == null) if (value == null)
throw new ArgumentException("Invalid value format."); throw new ArgumentException("Invalid value format.");
@ -87,18 +103,48 @@ namespace Kyoo.Controllers
/// </summary> /// </summary>
/// <param name="config">The configuration to transform</param> /// <param name="config">The configuration to transform</param>
/// <returns>A strongly typed representation of the configuration.</returns> /// <returns>A strongly typed representation of the configuration.</returns>
[SuppressMessage("ReSharper", "RedundantJumpStatement")]
private ExpandoObject ToObject(IConfiguration config) private ExpandoObject ToObject(IConfiguration config)
{ {
ExpandoObject obj = new(); ExpandoObject obj = new();
foreach (IConfigurationSection section in config.GetChildren()) foreach (IConfigurationSection section in config.GetChildren())
{ {
if (!_references.TryGetValue(section.Path, out Type type)) try
{
Type type = GetType(section.Path);
obj.TryAdd(section.Key, section.Get(type));
}
catch (ArgumentException)
{
obj.TryAdd(section.Key, ToUntyped(section));
}
catch
{
continue; continue;
obj.TryAdd(section.Key, section.Get(type)); }
} }
return obj; return obj;
} }
/// <summary>
/// Transform the configuration section in nested expando objects.
/// </summary>
/// <param name="config">The section to convert</param>
/// <returns>The converted section</returns>
private static object ToUntyped(IConfigurationSection config)
{
ExpandoObject obj = new();
foreach (IConfigurationSection section in config.GetChildren())
{
obj.TryAdd(section.Key, ToUntyped(section));
}
if (!obj.Any())
return config.Value;
return obj;
}
} }
} }

View File

@ -99,6 +99,8 @@ namespace Kyoo
services.AddConfiguration<TaskOptions>(TaskOptions.Path); services.AddConfiguration<TaskOptions>(TaskOptions.Path);
services.Configure<MediaOptions>(_configuration.GetSection(MediaOptions.Path)); services.Configure<MediaOptions>(_configuration.GetSection(MediaOptions.Path));
services.AddConfiguration<MediaOptions>(MediaOptions.Path); services.AddConfiguration<MediaOptions>(MediaOptions.Path);
services.AddUntypedConfiguration("database");
services.AddUntypedConfiguration("logging");
services.AddControllers() services.AddControllers()
.AddNewtonsoftJson(x => .AddNewtonsoftJson(x =>

View File

@ -109,7 +109,7 @@ namespace Kyoo
.UseKestrel(options => { options.AddServerHeader = false; }) .UseKestrel(options => { options.AddServerHeader = false; })
.UseIIS() .UseIIS()
.UseIISIntegration() .UseIISIntegration()
.UseUrls(configuration.GetValue<string>("basics:urls")) .UseUrls(configuration.GetValue<string>("basics:url"))
.UseStartup<Startup>(); .UseStartup<Startup>();
} }
} }

View File

@ -71,9 +71,9 @@ namespace Kyoo.Api
{ {
return NotFound(); return NotFound();
} }
catch (ArgumentException) catch (ArgumentException ex)
{ {
return BadRequest(); return BadRequest(ex.Message);
} }
} }
} }