Skip to content

Commit be6d708

Browse files
JasonBockcsharpfritzsoftchris
authored
Creating a source generator (#19)
* First NuGet Release (#16) * Added NuGet publish * Updated to only deploy when release is merged * Separating publish step * Added release to build process on PR * #12 FINALLY got the source generator to work * #12 YES!! The source generator is working! * #12 got the parsing methods working * #12 added more tests * #12 added support for key attribute * #12 added some comment notes on how to use the source generator in the web app * #12 removed some code for key attributes that's no longer used * #12 added a way to do configuration * #12 made progress on the new approach that has configuration. Tests need to be reworked, but it seems promising * #12 hey, it works in the WorkingApi project! * #12 all tests pass now * #12 got all the API verbs in now * #12 changed "db.Set<tableType>" to "db.{propertyName}" * #12 very minor code ordering change * Publishing release with new JSON APIs (#44) * Added video demo * adding JSON Mock API * adding instructions * Added HTTP Results calls to fix #7 * Added configuration for JSON APIs * Tagging for a preview release * Added Release Notes file * Moved release notes to top level Co-authored-by: softchris <[email protected]> * #12 updated to IEndpointRouteBuilder * #12 got the APIs to return the "right" thing * #12 confirmed new changes work in API app * #12 NICE! Just updated config for SG, initial results are good, added a separate WorkingApi.Generators project to run the SG in its own project. * #12 updated configuration to take an Action<Builder>, makes it more similiar to the Reflection approach * #12 fixed an issue with ignoring Included value * #12 started working on logging, tests WILL break right now * #12 logging is in, all tests pass now. * #12 added an attribute approach to define which DbContext classes should be mapped * #12 got all tests for the SG approach in (for now) Co-authored-by: Jeffrey T. Fritz <[email protected]> Co-authored-by: softchris <[email protected]>
1 parent cc36d29 commit be6d708

35 files changed

+2112
-1
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net6.0</TargetFramework>
4+
<Nullable>enable</Nullable>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
8+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
9+
<PackageReference Include="xunit" Version="2.4.1" />
10+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
11+
<PrivateAssets>all</PrivateAssets>
12+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
13+
</PackageReference>
14+
</ItemGroup>
15+
<ItemGroup>
16+
<ProjectReference Include="..\Fritz.InstantAPIs.Generators.Helpers\Fritz.InstantAPIs.Generators.Helpers.csproj" />
17+
</ItemGroup>
18+
</Project>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System;
2+
using Xunit;
3+
4+
namespace Fritz.InstantAPIs.Generators.Helpers.Tests;
5+
6+
public static class InstanceAPIGeneratorConfigBuilderTests
7+
{
8+
[Fact]
9+
public static void BuildWithNoConfiguration()
10+
{
11+
var builder = new InstanceAPIGeneratorConfigBuilder<Values>();
12+
var config = builder.Build();
13+
14+
foreach (var key in Enum.GetValues<Values>())
15+
{
16+
var tableConfig = config[key];
17+
18+
Assert.Equal(key, tableConfig.Key);
19+
Assert.Equal(key.ToString(), tableConfig.Name);
20+
Assert.Equal(Included.Yes, tableConfig.Included);
21+
Assert.Equal(ApisToGenerate.All, tableConfig.APIs);
22+
Assert.Equal("/api/a/{id}", tableConfig.RouteDeleteById("a"));
23+
Assert.Equal("/api/a", tableConfig.RouteGet("a"));
24+
Assert.Equal("/api/a/{id}", tableConfig.RouteGetById("a"));
25+
Assert.Equal("/api/a", tableConfig.RoutePost("a"));
26+
Assert.Equal("/api/a/{id}", tableConfig.RoutePut("a"));
27+
}
28+
}
29+
30+
[Fact]
31+
public static void BuildWithCustomInclude()
32+
{
33+
var builder = new InstanceAPIGeneratorConfigBuilder<Values>();
34+
builder.Include(Values.Two, "a", ApisToGenerate.Get,
35+
routeGet: value => $"get/{value}",
36+
routeGetById: value => $"getById/{value}",
37+
routePost: value => $"post/{value}",
38+
routePut: value => $"put/{value}",
39+
routeDeleteById: value => $"delete/{value}");
40+
var config = builder.Build();
41+
42+
foreach (var key in Enum.GetValues<Values>())
43+
{
44+
var tableConfig = config[key];
45+
46+
if (key != Values.Two)
47+
{
48+
Assert.Equal(key, tableConfig.Key);
49+
Assert.Equal(key.ToString(), tableConfig.Name);
50+
Assert.Equal(Included.Yes, tableConfig.Included);
51+
Assert.Equal(ApisToGenerate.All, tableConfig.APIs);
52+
Assert.Equal("/api/a/{id}", tableConfig.RouteDeleteById("a"));
53+
Assert.Equal("/api/a", tableConfig.RouteGet("a"));
54+
Assert.Equal("/api/a/{id}", tableConfig.RouteGetById("a"));
55+
Assert.Equal("/api/a", tableConfig.RoutePost("a"));
56+
Assert.Equal("/api/a/{id}", tableConfig.RoutePut("a"));
57+
}
58+
else
59+
{
60+
Assert.Equal(Values.Two, tableConfig.Key);
61+
Assert.Equal("a", tableConfig.Name);
62+
Assert.Equal(Included.Yes, tableConfig.Included);
63+
Assert.Equal(ApisToGenerate.Get, tableConfig.APIs);
64+
Assert.Equal("delete/a", tableConfig.RouteDeleteById("a"));
65+
Assert.Equal("get/a", tableConfig.RouteGet("a"));
66+
Assert.Equal("getById/a", tableConfig.RouteGetById("a"));
67+
Assert.Equal("post/a", tableConfig.RoutePost("a"));
68+
Assert.Equal("put/a", tableConfig.RoutePut("a"));
69+
}
70+
}
71+
}
72+
73+
[Fact]
74+
public static void BuildWithCustomExclude()
75+
{
76+
var builder = new InstanceAPIGeneratorConfigBuilder<Values>();
77+
builder.Exclude(Values.Two);
78+
var config = builder.Build();
79+
80+
foreach (var key in Enum.GetValues<Values>())
81+
{
82+
var tableConfig = config[key];
83+
84+
Assert.Equal(key, tableConfig.Key);
85+
Assert.Equal(key.ToString(), tableConfig.Name);
86+
Assert.Equal(key != Values.Two ? Included.Yes : Included.No, tableConfig.Included);
87+
Assert.Equal(ApisToGenerate.All, tableConfig.APIs);
88+
Assert.Equal("/api/a/{id}", tableConfig.RouteDeleteById("a"));
89+
Assert.Equal("/api/a", tableConfig.RouteGet("a"));
90+
Assert.Equal("/api/a/{id}", tableConfig.RouteGetById("a"));
91+
Assert.Equal("/api/a", tableConfig.RoutePost("a"));
92+
Assert.Equal("/api/a/{id}", tableConfig.RoutePut("a"));
93+
}
94+
}
95+
96+
private enum Values
97+
{
98+
One, Two, Three
99+
}
100+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Xunit;
2+
3+
namespace Fritz.InstantAPIs.Generators.Helpers.Tests
4+
{
5+
public static class TableConfigTests
6+
{
7+
[Fact]
8+
public static void Create()
9+
{
10+
var config = new TableConfig<Values>(Values.Three);
11+
12+
Assert.Equal(Values.Three, config.Key);
13+
Assert.Equal("Three", config.Name);
14+
Assert.Equal(Included.Yes, config.Included);
15+
Assert.Equal(ApisToGenerate.All, config.APIs);
16+
Assert.Equal("/api/a/{id}", config.RouteDeleteById("a"));
17+
Assert.Equal("/api/a", config.RouteGet("a"));
18+
Assert.Equal("/api/a/{id}", config.RouteGetById("a"));
19+
Assert.Equal("/api/a", config.RoutePost("a"));
20+
Assert.Equal("/api/a/{id}", config.RoutePut("a"));
21+
}
22+
23+
[Fact]
24+
public static void CreateWithCustomization()
25+
{
26+
var config = new TableConfig<Values>(Values.Three,
27+
Included.No, "a", ApisToGenerate.Get,
28+
routeGet: value => $"get/{value}",
29+
routeGetById: value => $"getById/{value}",
30+
routePost: value => $"post/{value}",
31+
routePut: value => $"put/{value}",
32+
routeDeleteById: value => $"delete/{value}");
33+
34+
Assert.Equal(Values.Three, config.Key);
35+
Assert.Equal("a", config.Name);
36+
Assert.Equal(Included.No, config.Included);
37+
Assert.Equal(ApisToGenerate.Get, config.APIs);
38+
Assert.Equal("delete/a", config.RouteDeleteById("a"));
39+
Assert.Equal("get/a", config.RouteGet("a"));
40+
Assert.Equal("getById/a", config.RouteGetById("a"));
41+
Assert.Equal("post/a", config.RoutePost("a"));
42+
Assert.Equal("put/a", config.RoutePut("a"));
43+
}
44+
45+
private enum Values
46+
{
47+
One, Two, Three
48+
}
49+
}
50+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Fritz.InstantAPIs.Generators.Helpers
2+
{
3+
[Flags]
4+
public enum ApisToGenerate
5+
{
6+
Get = 1,
7+
GetById = 2,
8+
Insert = 4,
9+
Update = 8,
10+
Delete = 16,
11+
All = 31
12+
}
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
</Project>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Fritz.InstantAPIs.Generators.Helpers
2+
{
3+
public enum Included
4+
{
5+
Yes, No
6+
}
7+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System.Collections.Immutable;
2+
3+
namespace Fritz.InstantAPIs.Generators.Helpers
4+
{
5+
public class InstanceAPIGeneratorConfig<T>
6+
where T : struct, Enum
7+
{
8+
private readonly ImmutableDictionary<T, TableConfig<T>> _tablesConfig;
9+
10+
internal InstanceAPIGeneratorConfig(ImmutableDictionary<T, TableConfig<T>> tablesConfig)
11+
{
12+
_tablesConfig = tablesConfig ?? throw new ArgumentNullException(nameof(tablesConfig));
13+
}
14+
15+
public virtual TableConfig<T> this[T key] => _tablesConfig[key];
16+
}
17+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Collections.Immutable;
2+
3+
namespace Fritz.InstantAPIs.Generators.Helpers
4+
{
5+
public sealed class InstanceAPIGeneratorConfigBuilder<T>
6+
where T : struct, Enum
7+
{
8+
private readonly Dictionary<T, TableConfig<T>> _tablesConfig = new();
9+
10+
public InstanceAPIGeneratorConfigBuilder()
11+
{
12+
foreach(var key in Enum.GetValues<T>())
13+
{
14+
_tablesConfig.Add(key, new TableConfig<T>(key));
15+
}
16+
}
17+
18+
public InstanceAPIGeneratorConfigBuilder<T> Include(T key, string? name = null, ApisToGenerate apis = ApisToGenerate.All,
19+
Func<string?, string>? routeGet = null, Func<string?, string>? routeGetById = null,
20+
Func<string?, string>? routePost = null, Func<string?, string>? routePut = null,
21+
Func<string?, string>? routeDeleteById = null)
22+
{
23+
_tablesConfig[key] = new TableConfig<T>(key, Included.Yes, name: name,
24+
apis: apis, routeGet: routeGet, routeGetById: routeGetById,
25+
routePost: routePost, routePut: routePut, routeDeleteById: routeDeleteById);
26+
return this;
27+
}
28+
29+
public InstanceAPIGeneratorConfigBuilder<T> Exclude(T key)
30+
{
31+
_tablesConfig[key] = new TableConfig<T>(key, Included.No);
32+
return this;
33+
}
34+
35+
public InstanceAPIGeneratorConfig<T> Build() =>
36+
new InstanceAPIGeneratorConfig<T>(_tablesConfig.ToImmutableDictionary());
37+
}
38+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Fritz.InstantAPIs.Generators.Helpers
2+
{
3+
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
4+
public sealed class InstantAPIsForDbContextAttribute
5+
: Attribute
6+
{
7+
public InstantAPIsForDbContextAttribute(Type dbContextType) =>
8+
DbContextType = dbContextType ?? throw new ArgumentNullException(nameof(dbContextType));
9+
10+
public Type DbContextType { get; }
11+
}
12+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace Fritz.InstantAPIs.Generators.Helpers
2+
{
3+
public sealed class TableConfig<T>
4+
where T : struct, Enum
5+
{
6+
public TableConfig(T key)
7+
{
8+
Key = key;
9+
Name = Enum.GetName(key);
10+
}
11+
12+
public TableConfig(T key, Included included, string? name = null, ApisToGenerate apis = ApisToGenerate.All,
13+
Func<string?, string>? routeGet = null, Func<string?, string>? routeGetById = null,
14+
Func<string?, string>? routePost = null, Func<string?, string>? routePut = null,
15+
Func<string?, string>? routeDeleteById = null)
16+
: this(key)
17+
{
18+
Included = included;
19+
APIs = apis;
20+
if (!string.IsNullOrWhiteSpace(name)) { Name = name; }
21+
if (routeGet is not null) { RouteGet = routeGet; }
22+
if (routeGetById is not null) { RouteGetById = routeGetById; }
23+
if (routePost is not null) { RoutePost = routePost; }
24+
if (routePut is not null) { RoutePut = routePut; }
25+
if (routeDeleteById is not null) { RouteDeleteById = routeDeleteById; }
26+
}
27+
28+
public T Key { get; }
29+
30+
public string? Name { get; } = null;
31+
32+
public Included Included { get; } = Included.Yes;
33+
34+
public ApisToGenerate APIs { get; } = ApisToGenerate.All;
35+
36+
public Func<string?, string> RouteDeleteById { get; } = value => $"/api/{value}/{{id}}";
37+
38+
public Func<string?, string> RouteGet { get; } = value => $"/api/{value}";
39+
40+
public Func<string?, string> RouteGetById { get; } = value => $"/api/{value}/{{id}}";
41+
42+
public Func<string?, string> RoutePost { get; } = value => $"/api/{value}";
43+
44+
public Func<string?, string> RoutePut { get; } = value => $"/api/{value}/{{id}}";
45+
}
46+
}

0 commit comments

Comments
 (0)