Skip to content

Commit

Permalink
Creating a source generator (#19)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3 people authored Mar 3, 2022
1 parent cc36d29 commit be6d708
Show file tree
Hide file tree
Showing 35 changed files with 2,112 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Fritz.InstantAPIs.Generators.Helpers\Fritz.InstantAPIs.Generators.Helpers.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using Xunit;

namespace Fritz.InstantAPIs.Generators.Helpers.Tests;

public static class InstanceAPIGeneratorConfigBuilderTests
{
[Fact]
public static void BuildWithNoConfiguration()
{
var builder = new InstanceAPIGeneratorConfigBuilder<Values>();
var config = builder.Build();

foreach (var key in Enum.GetValues<Values>())
{
var tableConfig = config[key];

Assert.Equal(key, tableConfig.Key);
Assert.Equal(key.ToString(), tableConfig.Name);
Assert.Equal(Included.Yes, tableConfig.Included);
Assert.Equal(ApisToGenerate.All, tableConfig.APIs);
Assert.Equal("/api/a/{id}", tableConfig.RouteDeleteById("a"));
Assert.Equal("/api/a", tableConfig.RouteGet("a"));
Assert.Equal("/api/a/{id}", tableConfig.RouteGetById("a"));
Assert.Equal("/api/a", tableConfig.RoutePost("a"));
Assert.Equal("/api/a/{id}", tableConfig.RoutePut("a"));
}
}

[Fact]
public static void BuildWithCustomInclude()
{
var builder = new InstanceAPIGeneratorConfigBuilder<Values>();
builder.Include(Values.Two, "a", ApisToGenerate.Get,
routeGet: value => $"get/{value}",
routeGetById: value => $"getById/{value}",
routePost: value => $"post/{value}",
routePut: value => $"put/{value}",
routeDeleteById: value => $"delete/{value}");
var config = builder.Build();

foreach (var key in Enum.GetValues<Values>())
{
var tableConfig = config[key];

if (key != Values.Two)
{
Assert.Equal(key, tableConfig.Key);
Assert.Equal(key.ToString(), tableConfig.Name);
Assert.Equal(Included.Yes, tableConfig.Included);
Assert.Equal(ApisToGenerate.All, tableConfig.APIs);
Assert.Equal("/api/a/{id}", tableConfig.RouteDeleteById("a"));
Assert.Equal("/api/a", tableConfig.RouteGet("a"));
Assert.Equal("/api/a/{id}", tableConfig.RouteGetById("a"));
Assert.Equal("/api/a", tableConfig.RoutePost("a"));
Assert.Equal("/api/a/{id}", tableConfig.RoutePut("a"));
}
else
{
Assert.Equal(Values.Two, tableConfig.Key);
Assert.Equal("a", tableConfig.Name);
Assert.Equal(Included.Yes, tableConfig.Included);
Assert.Equal(ApisToGenerate.Get, tableConfig.APIs);
Assert.Equal("delete/a", tableConfig.RouteDeleteById("a"));
Assert.Equal("get/a", tableConfig.RouteGet("a"));
Assert.Equal("getById/a", tableConfig.RouteGetById("a"));
Assert.Equal("post/a", tableConfig.RoutePost("a"));
Assert.Equal("put/a", tableConfig.RoutePut("a"));
}
}
}

[Fact]
public static void BuildWithCustomExclude()
{
var builder = new InstanceAPIGeneratorConfigBuilder<Values>();
builder.Exclude(Values.Two);
var config = builder.Build();

foreach (var key in Enum.GetValues<Values>())
{
var tableConfig = config[key];

Assert.Equal(key, tableConfig.Key);
Assert.Equal(key.ToString(), tableConfig.Name);
Assert.Equal(key != Values.Two ? Included.Yes : Included.No, tableConfig.Included);
Assert.Equal(ApisToGenerate.All, tableConfig.APIs);
Assert.Equal("/api/a/{id}", tableConfig.RouteDeleteById("a"));
Assert.Equal("/api/a", tableConfig.RouteGet("a"));
Assert.Equal("/api/a/{id}", tableConfig.RouteGetById("a"));
Assert.Equal("/api/a", tableConfig.RoutePost("a"));
Assert.Equal("/api/a/{id}", tableConfig.RoutePut("a"));
}
}

private enum Values
{
One, Two, Three
}
}
50 changes: 50 additions & 0 deletions Fritz.InstantAPIs.Generators.Helpers.Tests/TableConfigTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Xunit;

namespace Fritz.InstantAPIs.Generators.Helpers.Tests
{
public static class TableConfigTests
{
[Fact]
public static void Create()
{
var config = new TableConfig<Values>(Values.Three);

Assert.Equal(Values.Three, config.Key);
Assert.Equal("Three", config.Name);
Assert.Equal(Included.Yes, config.Included);
Assert.Equal(ApisToGenerate.All, config.APIs);
Assert.Equal("/api/a/{id}", config.RouteDeleteById("a"));
Assert.Equal("/api/a", config.RouteGet("a"));
Assert.Equal("/api/a/{id}", config.RouteGetById("a"));
Assert.Equal("/api/a", config.RoutePost("a"));
Assert.Equal("/api/a/{id}", config.RoutePut("a"));
}

[Fact]
public static void CreateWithCustomization()
{
var config = new TableConfig<Values>(Values.Three,
Included.No, "a", ApisToGenerate.Get,
routeGet: value => $"get/{value}",
routeGetById: value => $"getById/{value}",
routePost: value => $"post/{value}",
routePut: value => $"put/{value}",
routeDeleteById: value => $"delete/{value}");

Assert.Equal(Values.Three, config.Key);
Assert.Equal("a", config.Name);
Assert.Equal(Included.No, config.Included);
Assert.Equal(ApisToGenerate.Get, config.APIs);
Assert.Equal("delete/a", config.RouteDeleteById("a"));
Assert.Equal("get/a", config.RouteGet("a"));
Assert.Equal("getById/a", config.RouteGetById("a"));
Assert.Equal("post/a", config.RoutePost("a"));
Assert.Equal("put/a", config.RoutePut("a"));
}

private enum Values
{
One, Two, Three
}
}
}
13 changes: 13 additions & 0 deletions Fritz.InstantAPIs.Generators.Helpers/ApisToGenerate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Fritz.InstantAPIs.Generators.Helpers
{
[Flags]
public enum ApisToGenerate
{
Get = 1,
GetById = 2,
Insert = 4,
Update = 8,
Delete = 16,
All = 31
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
7 changes: 7 additions & 0 deletions Fritz.InstantAPIs.Generators.Helpers/Included.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Fritz.InstantAPIs.Generators.Helpers
{
public enum Included
{
Yes, No
}
}
17 changes: 17 additions & 0 deletions Fritz.InstantAPIs.Generators.Helpers/InstanceAPIGeneratorConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Immutable;

namespace Fritz.InstantAPIs.Generators.Helpers
{
public class InstanceAPIGeneratorConfig<T>
where T : struct, Enum
{
private readonly ImmutableDictionary<T, TableConfig<T>> _tablesConfig;

internal InstanceAPIGeneratorConfig(ImmutableDictionary<T, TableConfig<T>> tablesConfig)
{
_tablesConfig = tablesConfig ?? throw new ArgumentNullException(nameof(tablesConfig));
}

public virtual TableConfig<T> this[T key] => _tablesConfig[key];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Collections.Immutable;

namespace Fritz.InstantAPIs.Generators.Helpers
{
public sealed class InstanceAPIGeneratorConfigBuilder<T>
where T : struct, Enum
{
private readonly Dictionary<T, TableConfig<T>> _tablesConfig = new();

public InstanceAPIGeneratorConfigBuilder()
{
foreach(var key in Enum.GetValues<T>())
{
_tablesConfig.Add(key, new TableConfig<T>(key));
}
}

public InstanceAPIGeneratorConfigBuilder<T> Include(T key, string? name = null, ApisToGenerate apis = ApisToGenerate.All,
Func<string?, string>? routeGet = null, Func<string?, string>? routeGetById = null,
Func<string?, string>? routePost = null, Func<string?, string>? routePut = null,
Func<string?, string>? routeDeleteById = null)
{
_tablesConfig[key] = new TableConfig<T>(key, Included.Yes, name: name,
apis: apis, routeGet: routeGet, routeGetById: routeGetById,
routePost: routePost, routePut: routePut, routeDeleteById: routeDeleteById);
return this;
}

public InstanceAPIGeneratorConfigBuilder<T> Exclude(T key)
{
_tablesConfig[key] = new TableConfig<T>(key, Included.No);
return this;
}

public InstanceAPIGeneratorConfig<T> Build() =>
new InstanceAPIGeneratorConfig<T>(_tablesConfig.ToImmutableDictionary());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Fritz.InstantAPIs.Generators.Helpers
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class InstantAPIsForDbContextAttribute
: Attribute
{
public InstantAPIsForDbContextAttribute(Type dbContextType) =>
DbContextType = dbContextType ?? throw new ArgumentNullException(nameof(dbContextType));

public Type DbContextType { get; }
}
}
46 changes: 46 additions & 0 deletions Fritz.InstantAPIs.Generators.Helpers/TableConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
namespace Fritz.InstantAPIs.Generators.Helpers
{
public sealed class TableConfig<T>
where T : struct, Enum
{
public TableConfig(T key)
{
Key = key;
Name = Enum.GetName(key);
}

public TableConfig(T key, Included included, string? name = null, ApisToGenerate apis = ApisToGenerate.All,
Func<string?, string>? routeGet = null, Func<string?, string>? routeGetById = null,
Func<string?, string>? routePost = null, Func<string?, string>? routePut = null,
Func<string?, string>? routeDeleteById = null)
: this(key)
{
Included = included;
APIs = apis;
if (!string.IsNullOrWhiteSpace(name)) { Name = name; }
if (routeGet is not null) { RouteGet = routeGet; }
if (routeGetById is not null) { RouteGetById = routeGetById; }
if (routePost is not null) { RoutePost = routePost; }
if (routePut is not null) { RoutePut = routePut; }
if (routeDeleteById is not null) { RouteDeleteById = routeDeleteById; }
}

public T Key { get; }

public string? Name { get; } = null;

public Included Included { get; } = Included.Yes;

public ApisToGenerate APIs { get; } = ApisToGenerate.All;

public Func<string?, string> RouteDeleteById { get; } = value => $"/api/{value}/{{id}}";

public Func<string?, string> RouteGet { get; } = value => $"/api/{value}";

public Func<string?, string> RouteGetById { get; } = value => $"/api/{value}/{{id}}";

public Func<string?, string> RoutePost { get; } = value => $"/api/{value}";

public Func<string?, string> RoutePut { get; } = value => $"/api/{value}/{{id}}";
}
}
Loading

0 comments on commit be6d708

Please sign in to comment.