Skip to content

Commit 7418653

Browse files
Create new OpenApi Document in the case of a Hot Reload (#2384)
## Why make this change? Closes #1869 ## What is this change? Create a function in `OpenApiDocumentor` that invokes a new call to the `CreateDocument()` function to refresh the `OpenApiDocument`. Add an event handler, and subscribe the function for calling `CreateDocument()` to that handler when constructing the `OpenApiDocumentor` ## How was this tested? Current test suite, manually ran. ## Sample Request(s) N/A --------- Co-authored-by: Sean Leonard <[email protected]>
1 parent 5b649ea commit 7418653

File tree

12 files changed

+85
-16
lines changed

12 files changed

+85
-16
lines changed

src/Cli/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static int Main(string[] args)
3636

3737
// Sets up the filesystem used for reading and writing runtime configuration files.
3838
IFileSystem fileSystem = new FileSystem();
39-
FileSystemRuntimeConfigLoader loader = new(fileSystem);
39+
FileSystemRuntimeConfigLoader loader = new(fileSystem, handler: null);
4040

4141
return Execute(args, cliLogger, fileSystem, loader);
4242
}

src/Config/FileSystemRuntimeConfigLoader.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ public class FileSystemRuntimeConfigLoader : RuntimeConfigLoader
6060
/// </summary>
6161
public string ConfigFilePath { get; internal set; }
6262

63-
public FileSystemRuntimeConfigLoader(IFileSystem fileSystem, string baseConfigFilePath = DEFAULT_CONFIG_FILE_NAME, string? connectionString = null)
64-
: base(connectionString)
63+
public FileSystemRuntimeConfigLoader(IFileSystem fileSystem, HotReloadEventHandler<HotReloadEventArgs>? handler = null, string baseConfigFilePath = DEFAULT_CONFIG_FILE_NAME, string? connectionString = null)
64+
: base(handler, connectionString)
6565
{
6666
_fileSystem = fileSystem;
6767
_baseConfigFilePath = baseConfigFilePath;
@@ -199,6 +199,7 @@ public void HotReloadConfig(string defaultDataSourceName, ILogger? logger = null
199199
{
200200
logger?.LogInformation(message: "Starting hot-reload process for config: {ConfigFilePath}", ConfigFilePath);
201201
TryLoadConfig(ConfigFilePath, out _, replaceEnvVar: true, defaultDataSourceName: defaultDataSourceName);
202+
SendEventNotification();
202203
}
203204

204205
/// <summary>

src/Config/HotReloadEventArgs.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.DataApiBuilder.Config;
5+
6+
public class HotReloadEventArgs : EventArgs
7+
{
8+
public string Message { get; set; }
9+
10+
public HotReloadEventArgs(string message)
11+
{
12+
Message = message;
13+
}
14+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.DataApiBuilder.Config;
5+
6+
/// <summary>
7+
/// HotReloadEventHandler defines event invocation and subscription functions that are
8+
/// used to facilitate updating DAB components' state due to a hot reload.
9+
/// The events defined in this class are invoked in this class (versus being invoked in RuntimeConfigLoader)
10+
/// because events are a special type of delegate that can only be invoked from within the class that declared them.
11+
/// For more information about where events should be invoked, please see:
12+
/// https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/how-to-raise-base-class-events-in-derived-classes
13+
/// </summary>
14+
/// <typeparam name="TEventArgs">Args used for hot reload events.</typeparam>
15+
public class HotReloadEventHandler<TEventArgs> where TEventArgs : HotReloadEventArgs
16+
{
17+
public event EventHandler<TEventArgs>? DocumentorOnConfigChanged;
18+
19+
public void DocumentorOnConfigChangedEvent(object sender, TEventArgs args)
20+
{
21+
DocumentorOnConfigChanged?.Invoke(sender, args);
22+
}
23+
public void DocumentorSubscribe(EventHandler<TEventArgs> handler)
24+
{
25+
DocumentorOnConfigChanged += handler;
26+
}
27+
}

src/Config/ObjectModel/RuntimeConfig.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public RuntimeConfig(string? Schema, DataSource DataSource, RuntimeEntities Enti
206206
IEnumerable<KeyValuePair<string, Entity>> allEntities = Entities.AsEnumerable();
207207
// Iterate through all the datasource files and load the config.
208208
IFileSystem fileSystem = new FileSystem();
209-
FileSystemRuntimeConfigLoader loader = new(fileSystem);
209+
FileSystemRuntimeConfigLoader loader = new(fileSystem, handler: null);
210210

211211
foreach (string dataSourceFile in DataSourceFiles.SourceFiles)
212212
{

src/Config/RuntimeConfigLoader.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,30 @@ namespace Azure.DataApiBuilder.Config;
2222

2323
public abstract class RuntimeConfigLoader
2424
{
25+
private HotReloadEventHandler<HotReloadEventArgs>? _handler;
2526
protected readonly string? _connectionString;
2627

2728
// Public to allow the RuntimeProvider and other users of class to set via out param.
2829
// May be candidate to refactor by changing all of the Parse/Load functions to save
2930
// state in place of using out params.
3031
public RuntimeConfig? RuntimeConfig;
3132

32-
public RuntimeConfigLoader(string? connectionString = null)
33+
// Signals a hot reload event for OpenApiDocumentor due to config change.
34+
protected virtual void DocumentorOnConfigChanged(HotReloadEventArgs args)
3335
{
36+
_handler?.DocumentorOnConfigChangedEvent(this, args);
37+
}
38+
39+
// Sends all of the notifications when a hot reload occurs.
40+
public void SendEventNotification(string message = "")
41+
{
42+
HotReloadEventArgs args = new(message);
43+
DocumentorOnConfigChanged(args);
44+
}
45+
46+
public RuntimeConfigLoader(HotReloadEventHandler<HotReloadEventArgs>? handler = null, string? connectionString = null)
47+
{
48+
_handler = handler;
3449
_connectionString = connectionString;
3550
}
3651

src/Core/Services/OpenAPI/IOpenApiDocumentor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ public interface IOpenApiDocumentor
2626
/// <exception cref="DataApiBuilderException">Raised when document is already generated
2727
/// or a failure occurs during generation.</exception>
2828
/// <seealso cref="https://github.com/microsoft/OpenAPI.NET/blob/1.6.3/src/Microsoft.OpenApi/OpenApiSpecVersion.cs"/>
29-
public void CreateDocument();
29+
public void CreateDocument(bool isHotReloadScenario);
3030
}
3131
}

src/Core/Services/OpenAPI/OpenApiDocumentor.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Net;
77
using System.Net.Mime;
88
using System.Text;
9+
using Azure.DataApiBuilder.Config;
910
using Azure.DataApiBuilder.Config.DatabasePrimitives;
1011
using Azure.DataApiBuilder.Config.ObjectModel;
1112
using Azure.DataApiBuilder.Core.Authorization;
@@ -56,13 +57,22 @@ public class OpenApiDocumentor : IOpenApiDocumentor
5657
public const string DOCUMENT_CREATION_UNSUPPORTED_ERROR = "OpenAPI description document can't be created when the REST endpoint is disabled globally.";
5758
public const string DOCUMENT_CREATION_FAILED_ERROR = "OpenAPI description document creation failed";
5859

60+
public void DocumentorOnConfigChanged(object? sender, HotReloadEventArgs args)
61+
{
62+
Console.BackgroundColor = ConsoleColor.Yellow;
63+
Console.WriteLine($"[OpenApiDocumentor]: Received event with message: {args.Message}");
64+
Console.ResetColor();
65+
CreateDocument(doOverrideExistingDocument: true);
66+
}
67+
5968
/// <summary>
6069
/// Constructor denotes required services whose metadata is used to generate the OpenAPI description document.
6170
/// </summary>
6271
/// <param name="sqlMetadataProvider">Provides database object metadata.</param>
6372
/// <param name="runtimeConfigProvider">Provides entity/REST path metadata.</param>
64-
public OpenApiDocumentor(IMetadataProviderFactory metadataProviderFactory, RuntimeConfigProvider runtimeConfigProvider)
73+
public OpenApiDocumentor(IMetadataProviderFactory metadataProviderFactory, RuntimeConfigProvider runtimeConfigProvider, HotReloadEventHandler<HotReloadEventArgs> handler)
6574
{
75+
handler.DocumentorSubscribe(DocumentorOnConfigChanged);
6676
_metadataProviderFactory = metadataProviderFactory;
6777
_runtimeConfigProvider = runtimeConfigProvider;
6878
_defaultOpenApiResponses = CreateDefaultOpenApiResponses();
@@ -100,10 +110,10 @@ public bool TryGetDocument([NotNullWhen(true)] out string? document)
100110
/// <exception cref="DataApiBuilderException">Raised when document is already generated
101111
/// or a failure occurs during generation.</exception>
102112
/// <seealso cref="https://github.com/microsoft/OpenAPI.NET/blob/1.6.3/src/Microsoft.OpenApi/OpenApiSpecVersion.cs"/>
103-
public void CreateDocument()
113+
public void CreateDocument(bool doOverrideExistingDocument = false)
104114
{
105115
RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig();
106-
if (_openApiDocument is not null)
116+
if (_openApiDocument is not null && !doOverrideExistingDocument)
107117
{
108118
throw new DataApiBuilderException(
109119
message: DOCUMENT_ALREADY_GENERATED_ERROR,

src/Service.Tests/Unittests/ConfigFileWatcherUnitTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public void HotReloadConfigRestRuntimeOptions()
116116
// Not using mocked filesystem so we pick up real file changes for hot reload
117117
FileSystem fileSystem = new();
118118
fileSystem.File.WriteAllText(configName, initialConfig);
119-
FileSystemRuntimeConfigLoader configLoader = new(fileSystem, configName, string.Empty);
119+
FileSystemRuntimeConfigLoader configLoader = new(fileSystem, handler: null, configName, string.Empty);
120120
RuntimeConfigProvider configProvider = new(configLoader);
121121

122122
// Must GetConfig() to start file watching

src/Service.Tests/Unittests/ConfigValidationUnitTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2276,7 +2276,7 @@ public void TestCorrectConfigFileIsSelectedForRuntimeEngine(
22762276
FileSystemRuntimeConfigLoader runtimeConfigLoader;
22772277
if (userProvidedConfigFilePath is not null)
22782278
{
2279-
runtimeConfigLoader = new(fileSystem, userProvidedConfigFilePath);
2279+
runtimeConfigLoader = new(fileSystem, handler: null, userProvidedConfigFilePath);
22802280
}
22812281
else
22822282
{

0 commit comments

Comments
 (0)