Skip to content

Commit bdb3cf2

Browse files
authored
Merge pull request #17 from beheshty/refactor/introduce-settings-class
Refactor/introduce settings class
2 parents 37a7915 + 1684b17 commit bdb3cf2

5 files changed

Lines changed: 216 additions & 21 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+

2+
3+
namespace SetSharp.Helpers
4+
{
5+
internal static class SetSharpJsonReader
6+
{
7+
/// <summary>
8+
/// Reads a value from a nested dictionary using a colon-delimited key path.
9+
/// </summary>
10+
/// <param name="json">The top-level dictionary representing the parsed JSON.</param>
11+
/// <param name="keyPath">The path to the desired value (e.g., "SetSharp:OptionPatternGenerationEnabled").</param>
12+
/// <returns>The found value as an object, or null if the path is invalid or the key is not found.</returns>
13+
internal static object? Read(Dictionary<string, object> json, string keyPath)
14+
{
15+
if (json == null || string.IsNullOrWhiteSpace(keyPath))
16+
{
17+
return null;
18+
}
19+
20+
string[] keys = keyPath.Split(':');
21+
object currentNode = json;
22+
23+
foreach (var key in keys)
24+
{
25+
if (currentNode is not Dictionary<string, object> currentDict)
26+
{
27+
return null;
28+
}
29+
30+
if (!currentDict.TryGetValue(key, out currentNode))
31+
{
32+
return null;
33+
}
34+
}
35+
36+
return currentNode;
37+
}
38+
}
39+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+

2+
namespace SetSharp.Models
3+
{
4+
internal class SetSharpSettings
5+
{
6+
public bool OptionPatternGenerationEnabled { get; set; } = true;
7+
}
8+
}

src/SetSharp/Models/SourceGenerationModel.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ namespace SetSharp.Models
55
internal class SourceGenerationModel
66
{
77
internal SourceGenerationModel(List<SettingClassInfo>? classes,
8-
bool generateOptions,
8+
SetSharpSettings setSharpSettings,
99
Diagnostic? diagnostic)
1010
{
1111
Classes = classes;
12-
GenerateOptions = generateOptions;
12+
SetSharpSettings = setSharpSettings;
1313
Diagnostic = diagnostic;
1414
}
1515

1616
internal List<SettingClassInfo>? Classes { get; }
17-
internal bool GenerateOptions { get; }
17+
internal SetSharpSettings SetSharpSettings { get; }
1818
internal Diagnostic? Diagnostic { get; }
1919
}
2020
}

src/SetSharp/SetSharpSourceGenerator.cs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,52 +18,52 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
1818
IncrementalValuesProvider<AdditionalText> textFiles =
1919
context.AdditionalTextsProvider.Where(static file => Path.GetFileName(file.Path).Equals("appsettings.json"));
2020

21-
IncrementalValuesProvider<(List<SettingClassInfo>? Classes, bool GenerateOptions, Diagnostic? Diagnostic)> settingsAndOptionsProvider =
21+
IncrementalValuesProvider<(List<SettingClassInfo>? Classes, SetSharpSettings SetSharpOptions, Diagnostic? Diagnostic)> settingsAndOptionsProvider =
2222
textFiles.Select((text, cancellationToken) =>
2323
{
2424
var content = text.GetText(cancellationToken);
2525
if (content is null)
2626
{
27-
return ((List<SettingClassInfo>?)null, true, null);
27+
return ((List<SettingClassInfo>?)null, new SetSharpSettings(), null);
2828
}
2929

3030
try
3131
{
3232
var json = SetSharpJsonParser.Parse(content.ToString());
3333

34-
bool generateOptions = ReadSetSharpSettingsIfProvided(json);
34+
var setSharpOptions = ReadSetSharpSettings(json);
3535

3636
var modelBuilder = new ConfigurationModelBuilder();
3737
var classes = modelBuilder.BuildFrom(json);
38-
return (classes, generateOptions, (Diagnostic?)null);
38+
return (classes, setSharpOptions, (Diagnostic?)null);
3939
}
4040
catch (Exception e)
4141
{
4242
var diagnostic = Diagnostic.Create(DiagnosticDescriptors.ParsingFailedError, Location.None, e.Message);
43-
return ((List<SettingClassInfo>?)null, true, diagnostic);
43+
return ((List<SettingClassInfo>?)null, new SetSharpSettings(), diagnostic);
4444
}
4545
});
4646

4747
var combinedProvider = settingsAndOptionsProvider.Combine(context.CompilationProvider);
4848

4949
var finalProvider = combinedProvider.Select((source, cancellationToken) =>
5050
{
51-
var ((classes, generateOptions, diagnostic), compilation) = source;
51+
var ((classes, setSharpOptions, diagnostic), compilation) = source;
5252

5353
// If parsing already failed, just pass the error through.
5454
if (diagnostic != null)
5555
{
56-
return new SourceGenerationModel(null, false, diagnostic);
56+
return new SourceGenerationModel(null, setSharpOptions, diagnostic);
5757
}
5858

59-
var dependencyDiagnostic = CheckDependencies(compilation, generateOptions);
59+
var dependencyDiagnostic = CheckDependencies(compilation, setSharpOptions.OptionPatternGenerationEnabled);
6060

6161
if (dependencyDiagnostic != null)
6262
{
63-
return new SourceGenerationModel(classes, generateOptions, dependencyDiagnostic);
63+
return new SourceGenerationModel(classes, setSharpOptions, dependencyDiagnostic);
6464
}
6565

66-
return new SourceGenerationModel(classes, generateOptions, null);
66+
return new SourceGenerationModel(classes, setSharpOptions, null);
6767
});
6868

6969
context.RegisterSourceOutput(finalProvider, (spc, model) =>
@@ -82,7 +82,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
8282
spc.AddSource("AppSettings.g.cs", SourceText.From(pocoSourceCode, Encoding.UTF8));
8383
}
8484

85-
if (model.GenerateOptions && model.Classes is { Count: > 0 })
85+
if (model.SetSharpSettings.OptionPatternGenerationEnabled && model.Classes is { Count: > 0 })
8686
{
8787
var extensionsSourceCode = OptionsPatternGenerator.Generate(model.Classes);
8888
spc.AddSource("OptionsExtensions.g.cs", SourceText.From(extensionsSourceCode, Encoding.UTF8));
@@ -91,20 +91,21 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
9191
});
9292
}
9393

94-
private static bool ReadSetSharpSettingsIfProvided(Dictionary<string, object> json)
94+
private static SetSharpSettings ReadSetSharpSettings(Dictionary<string, object> json)
9595
{
96-
if (json.TryGetValue("SetSharp", out var setSharpObject)
97-
&& setSharpObject is Dictionary<string, object> setSharpDict)
96+
var setSharpOptions = new SetSharpSettings();
97+
var settingOption = SetSharpJsonReader.Read(json, "SetSharp");
98+
if (settingOption is not null and Dictionary<string, object> setSharpDict)
9899
{
100+
99101
if (setSharpDict.TryGetValue("OptionPatternGenerationEnabled", out var enabledValue)
100102
&& bool.TryParse(enabledValue?.ToString(), out var parsedBool))
101103
{
102-
return parsedBool;
104+
setSharpOptions.OptionPatternGenerationEnabled = parsedBool;
103105
}
104-
}
105106

106-
// Default to true if the setting is not present.
107-
return true;
107+
}
108+
return setSharpOptions;
108109
}
109110

110111
/// <summary>
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
using SetSharp.Helpers;
2+
3+
namespace SetSharp.Tests.Helpers
4+
{
5+
public class SetSharpJsonReaderTests
6+
{
7+
private readonly Dictionary<string, object> _testJson = new()
8+
{
9+
{ "TopLevelString", "Hello World" },
10+
{ "TopLevelInt", 123 },
11+
{ "SetSharp", new Dictionary<string, object>
12+
{
13+
{ "Enabled", true },
14+
{ "Generation", new Dictionary<string, object>
15+
{
16+
{ "Poco", true },
17+
{ "OptionsPattern", false }
18+
}
19+
}
20+
}
21+
}
22+
};
23+
24+
[Fact]
25+
public void Read_WithValidTopLevelPath_ReturnsCorrectValue()
26+
{
27+
// Arrange
28+
var keyPath = "TopLevelString";
29+
30+
// Act
31+
var result = SetSharpJsonReader.Read(_testJson, keyPath);
32+
33+
// Assert
34+
Assert.Equal("Hello World", result);
35+
}
36+
37+
[Fact]
38+
public void Read_WithValidNestedPath_ReturnsCorrectValue()
39+
{
40+
// Arrange
41+
var keyPath = "SetSharp:Enabled";
42+
43+
// Act
44+
var result = SetSharpJsonReader.Read(_testJson, keyPath);
45+
46+
// Assert
47+
Assert.IsType<bool>(result);
48+
Assert.Equal(true, result);
49+
}
50+
51+
[Fact]
52+
public void Read_WithDeeplyNestedPath_ReturnsCorrectValue()
53+
{
54+
// Arrange
55+
var keyPath = "SetSharp:Generation:Poco";
56+
57+
// Act
58+
var result = SetSharpJsonReader.Read(_testJson, keyPath);
59+
60+
// Assert
61+
Assert.IsType<bool>(result);
62+
Assert.Equal(true, result);
63+
}
64+
65+
[Fact]
66+
public void Read_PathToADictionary_ReturnsDictionaryObject()
67+
{
68+
// Arrange
69+
var keyPath = "SetSharp:Generation";
70+
71+
// Act
72+
var result = SetSharpJsonReader.Read(_testJson, keyPath);
73+
74+
// Assert
75+
var dictResult = Assert.IsType<Dictionary<string, object>>(result);
76+
Assert.True((bool)dictResult["Poco"]);
77+
Assert.False((bool)dictResult["OptionsPattern"]);
78+
}
79+
80+
[Fact]
81+
public void Read_WithNonExistentTopLevelKey_ReturnsNull()
82+
{
83+
// Arrange
84+
var keyPath = "NonExistent";
85+
86+
// Act
87+
var result = SetSharpJsonReader.Read(_testJson, keyPath);
88+
89+
// Assert
90+
Assert.Null(result);
91+
}
92+
93+
[Fact]
94+
public void Read_WithNonExistentNestedKey_ReturnsNull()
95+
{
96+
// Arrange
97+
var keyPath = "SetSharp:Generation:NonExistent";
98+
99+
// Act
100+
var result = SetSharpJsonReader.Read(_testJson, keyPath);
101+
102+
// Assert
103+
Assert.Null(result);
104+
}
105+
106+
[Fact]
107+
public void Read_PathGoesThroughLeafValue_ReturnsNull()
108+
{
109+
// Arrange
110+
// Trying to navigate deeper into "TopLevelString", which is not a dictionary
111+
var keyPath = "TopLevelString:Deeper";
112+
113+
// Act
114+
var result = SetSharpJsonReader.Read(_testJson, keyPath);
115+
116+
// Assert
117+
Assert.Null(result);
118+
}
119+
120+
[Fact]
121+
public void Read_WithNullJson_ReturnsNull()
122+
{
123+
// Arrange
124+
Dictionary<string, object> nullJson = null;
125+
var keyPath = "Any:Path";
126+
127+
// Act
128+
var result = SetSharpJsonReader.Read(nullJson, keyPath);
129+
130+
// Assert
131+
Assert.Null(result);
132+
}
133+
134+
[Theory]
135+
[InlineData(null)]
136+
[InlineData("")]
137+
[InlineData(" ")]
138+
public void Read_WithInvalidKeyPath_ReturnsNull(string invalidKeyPath)
139+
{
140+
// Act
141+
var result = SetSharpJsonReader.Read(_testJson, invalidKeyPath);
142+
143+
// Assert
144+
Assert.Null(result);
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)