Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ To use the the [StronglyTypedId NuGet package](https://www.nuget.org/packages/St
* [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/) (optional, only required if [generating a System.Text `JsonConverter`](https://andrewlock.net/using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-2/#creating-a-custom-jsonconverter)). Note that in .NET Core apps, you will likely already reference this project via transitive dependencies.
* [Dapper](https://www.nuget.org/packages/Dapper/) (optional, only required if [generating a type mapper](https://andrewlock.net/using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-3/#interfacing-with-external-system-using-strongly-typed-ids))
* [EF Core](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) (optional, only required if [generating an EF Core ValueConverter](https://andrewlock.net/strongly-typed-ids-in-ef-core-using-strongly-typed-entity-ids-to-avoid-primitive-obsession-part-4/))
* [Swagger Annotations](https://www.nuget.org/packages/Swashbuckle.AspNetCore.Annotations) (optional, only required if [generating an Swagger Schema Filter](#openapiswagger-specification)


To install the packages, add the references to your _csproj_ file, for example by running

Expand Down Expand Up @@ -160,6 +162,18 @@ public partial struct OrderId { }
public partial struct UserId { }
```

## OpenApi/Swagger Specification

If you wish to use an ID in your Swagger models and want to have schema and model sample reflecting the ID backingfield type you will need:
- Install [Swagger Annotations](https://www.nuget.org/packages/Swashbuckle.AspNetCore.Annotations) `>=5.0.0`
- Enable annotation in swagger gen with `services.AddSwaggerGen(c => c.EnableAnnotations());`
- Use the converter flag `StronglyTypedIdConverter.SwaggerSchemaFilter` on the ID decorator. eg:
```csharp
[StronglyTypedId(
backingType: StronglyTypedIdBackingType.Int,
converters: StronglyTypedIdConverter.SwaggerSchemaFilter | StronglyTypedIdConverter.SystemTextJson)]
public partial struct UserId { }
```

## Embedding the attributes in your project

Expand Down Expand Up @@ -253,4 +267,4 @@ The `struct`s you decorate with the `StronglyTypedId` attribute must be marked `

`StronglyTypedId` wouldn't work if not for [AArnott's CodeGeneration.Roslyn](https://github.com/AArnott/CodeGeneration.Roslyn) library.

The build process and general design of the library was modelled on the [RecordGenerator](https://github.com/amis92/RecordGenerator/blob/master/README.md) project, which is similar to this project, but can be used to generate immutable Record types.
The build process and general design of the library was modelled on the [RecordGenerator](https://github.com/amis92/RecordGenerator/blob/master/README.md) project, which is similar to this project, but can be used to generate immutable Record types.
6 changes: 6 additions & 0 deletions src/StronglyTypedIds.Attributes/StronglyTypedIdConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,11 @@ public enum StronglyTypedIdConverter
/// Creates a Dapper TypeHandler for converting to and from the type
/// </summary>
DapperTypeHandler = 32,

/// <summary>
/// Creates a Swagger SchemaFilter for OpenApi documentation
/// </summary>
SwaggerSchemaFilter = 64,

}
}
10 changes: 10 additions & 0 deletions src/StronglyTypedIds/EmbeddedSources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal static class EmbeddedSources
LoadEmbeddedResource("StronglyTypedIds.Templates.Guid.Guid_EfCoreValueConverter.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.Guid.Guid_DapperTypeHandler.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.Guid.Guid_IComparable.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.Guid.Guid_SwaggerSchemaFilter.cs"),
false
);

Expand All @@ -37,6 +38,7 @@ internal static class EmbeddedSources
LoadEmbeddedResource("StronglyTypedIds.Templates.Int.Int_EfCoreValueConverter.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.Int.Int_DapperTypeHandler.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.Int.Int_IComparable.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.Int.Int_SwaggerSchemaFilter.cs"),
false
);

Expand All @@ -49,6 +51,7 @@ internal static class EmbeddedSources
LoadEmbeddedResource("StronglyTypedIds.Templates.Long.Long_EfCoreValueConverter.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.Long.Long_DapperTypeHandler.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.Long.Long_IComparable.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.Long.Long_SwaggerSchemaFilter.cs"),
false
);

Expand All @@ -61,6 +64,7 @@ internal static class EmbeddedSources
LoadEmbeddedResource("StronglyTypedIds.Templates.String.String_EfCoreValueConverter.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.String.String_DapperTypeHandler.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.String.String_IComparable.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.String.String_SwaggerSchemaFilter.cs"),
false
);

Expand All @@ -73,6 +77,7 @@ internal static class EmbeddedSources
LoadEmbeddedResource("StronglyTypedIds.Templates.NullableString.NullableString_EfCoreValueConverter.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.NullableString.NullableString_DapperTypeHandler.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.NullableString.NullableString_IComparable.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.NullableString.NullableString_SwaggerSchemaFilter.cs"),
true
);

Expand All @@ -85,12 +90,14 @@ internal static class EmbeddedSources
LoadEmbeddedResource("StronglyTypedIds.Templates.NewId.NewId_EfCoreValueConverter.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.NewId.NewId_DapperTypeHandler.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.NewId.NewId_IComparable.cs"),
LoadEmbeddedResource("StronglyTypedIds.Templates.NewId.NewId_SwaggerSchemaFilter.cs"),
false
);

internal const string TypeConverterAttributeSource = " [System.ComponentModel.TypeConverter(typeof(TESTIDTypeConverter))]";
internal const string NewtonsoftJsonAttributeSource = " [Newtonsoft.Json.JsonConverter(typeof(TESTIDNewtonsoftJsonConverter))]";
internal const string SystemTextJsonAttributeSource = " [System.Text.Json.Serialization.JsonConverter(typeof(TESTIDSystemTextJsonConverter))]";
internal const string SwaggerSchemaFilterAttributeSource = " [Swashbuckle.AspNetCore.Annotations.SwaggerSchemaFilter(typeof(TESTIDSchemaFilter))]";

internal static string LoadEmbeddedResource(string resourceName)
{
Expand All @@ -108,6 +115,7 @@ internal static string LoadEmbeddedResource(string resourceName)

public readonly struct ResourceCollection
{
public string SwaggerSchemaFilter { get; }
public string Header { get; }
public bool NullableEnable { get; }
public string BaseId { get; }
Expand All @@ -127,8 +135,10 @@ public ResourceCollection(
string efCoreValueConverter,
string dapperTypeHandler,
string comparable,
string swaggerSchemaFilter,
bool nullableEnable)
{
SwaggerSchemaFilter = swaggerSchemaFilter;
BaseId = baseId;
Newtonsoft = newtonsoft;
SystemTextJson = systemTextJson;
Expand Down
12 changes: 12 additions & 0 deletions src/StronglyTypedIds/SourceGenerationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ static string CreateId(

var hasNamespace = !string.IsNullOrEmpty(idNamespace);

var useSchemaFilter = converters.IsSet(StronglyTypedIdConverter.SwaggerSchemaFilter);
var useTypeConverter = converters.IsSet(StronglyTypedIdConverter.TypeConverter);
var useNewtonsoftJson = converters.IsSet(StronglyTypedIdConverter.NewtonsoftJson);
var useSystemTextJson = converters.IsSet(StronglyTypedIdConverter.SystemTextJson);
Expand Down Expand Up @@ -122,6 +123,12 @@ static string CreateId(
sb.AppendLine(EmbeddedSources.TypeConverterAttributeSource);
}

if (useSchemaFilter)
{
sb.AppendLine(EmbeddedSources.SwaggerSchemaFilterAttributeSource);
}


sb.Append(resources.BaseId);
ReplaceInterfaces(sb, useIEquatable, useIComparable);

Expand Down Expand Up @@ -157,6 +164,11 @@ static string CreateId(
sb.AppendLine(resources.SystemTextJson);
}

if (useSchemaFilter)
{
sb.AppendLine(resources.SwaggerSchemaFilter);
}

sb.Replace("TESTID", idName);
sb.AppendLine(@" }");

Expand Down
13 changes: 13 additions & 0 deletions src/StronglyTypedIds/Templates/Guid/Guid_SwaggerSchemaFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

class TESTIDSchemaFilter : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext context)
{
var idSchema = new Microsoft.OpenApi.Models.OpenApiSchema {Type = "string", Format = "uuid"};
schema.Type = idSchema.Type;
schema.Format = idSchema.Format;
schema.Example = idSchema.Example;
schema.Default = idSchema.Default;
schema.Properties = idSchema.Properties;
}
}
13 changes: 13 additions & 0 deletions src/StronglyTypedIds/Templates/Int/Int_SwaggerSchemaFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

class TESTIDSchemaFilter : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext context)
{
var idSchema = new Microsoft.OpenApi.Models.OpenApiSchema {Type = "integer", Format = "int32"};
schema.Type = idSchema.Type;
schema.Format = idSchema.Format;
schema.Example = idSchema.Example;
schema.Default = idSchema.Default;
schema.Properties = idSchema.Properties;
}
}
13 changes: 13 additions & 0 deletions src/StronglyTypedIds/Templates/Long/Long_SwaggerSchemaFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

class TESTIDSchemaFilter : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext context)
{
var idSchema = new Microsoft.OpenApi.Models.OpenApiSchema {Type = "integer", Format = "int64"};
schema.Type = idSchema.Type;
schema.Format = idSchema.Format;
schema.Example = idSchema.Example;
schema.Default = idSchema.Default;
schema.Properties = idSchema.Properties;
}
}
13 changes: 13 additions & 0 deletions src/StronglyTypedIds/Templates/NewId/NewId_SwaggerSchemaFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

class TESTIDSchemaFilter : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext context)
{
var idSchema = new Microsoft.OpenApi.Models.OpenApiSchema {Type = "string", Format = "uuid"};
schema.Type = idSchema.Type;
schema.Format = idSchema.Format;
schema.Example = idSchema.Example;
schema.Default = idSchema.Default;
schema.Properties = idSchema.Properties;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

class TESTIDSchemaFilter : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext context)
{
var idSchema = new Microsoft.OpenApi.Models.OpenApiSchema {Type = "string", Format = ""};
schema.Type = idSchema.Type;
schema.Format = idSchema.Format;
schema.Example = idSchema.Example;
schema.Default = idSchema.Default;
schema.Properties = idSchema.Properties;
schema.Nullable = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

class TESTIDSchemaFilter : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(Microsoft.OpenApi.Models.OpenApiSchema schema, Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext context)
{
var idSchema = new Microsoft.OpenApi.Models.OpenApiSchema {Type = "string", Format = ""};
schema.Type = idSchema.Type;
schema.Format = idSchema.Format;
schema.Example = idSchema.Example;
schema.Default = idSchema.Default;
schema.Properties = idSchema.Properties;
schema.Nullable = false;
}
}
23 changes: 23 additions & 0 deletions test/StronglyTypedIds.IntegrationTests/GuidIdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,28 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
}
#endif

#if NET5_0_OR_GREATER
[Fact]
public void CanShowImplementationTypeExample_WithSwaggerSchemaFilter()
{
var schemaGenerator = new Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator(
new Swashbuckle.AspNetCore.SwaggerGen.SchemaGeneratorOptions(),
new Swashbuckle.AspNetCore.SwaggerGen.JsonSerializerDataContractResolver(
new System.Text.Json.JsonSerializerOptions()));
var provider = Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(
new Microsoft.Extensions.DependencyInjection.ServiceCollection());
var schemaFilter = new Swashbuckle.AspNetCore.Annotations.AnnotationsSchemaFilter(provider);
var schemaRepository = new Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository();

var idType = typeof(SwaggerGuidId);
var schema = schemaGenerator.GenerateSchema(idType, schemaRepository);
schemaFilter.Apply(schema, new Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext(idType, schemaGenerator, schemaRepository));

Assert.Equal("string", schema.Type);
Assert.Equal("uuid", schema.Format);
}
#endif

public class TestDbContext : DbContext
{
public DbSet<TestEntity> Entities { get; set; }
Expand All @@ -344,6 +366,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.ValueGeneratedNever();
});
}

}

public class TestEntity
Expand Down
21 changes: 21 additions & 0 deletions test/StronglyTypedIds.IntegrationTests/IntIdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,27 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
}
#endif

#if NET5_0_OR_GREATER
[Fact]
public void CanShowImplementationTypeExample_WithSwaggerSchemaFilter()
{
var schemaGenerator = new Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator(
new Swashbuckle.AspNetCore.SwaggerGen.SchemaGeneratorOptions(),
new Swashbuckle.AspNetCore.SwaggerGen.JsonSerializerDataContractResolver(
new System.Text.Json.JsonSerializerOptions()));
var provider = Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(
new Microsoft.Extensions.DependencyInjection.ServiceCollection());
var schemaFilter = new Swashbuckle.AspNetCore.Annotations.AnnotationsSchemaFilter(provider);
var schemaRepository = new Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository();

var idType = typeof(SwaggerIntId);
var schema = schemaGenerator.GenerateSchema(idType, schemaRepository);
schemaFilter.Apply(schema, new Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext(idType, schemaGenerator, schemaRepository));

Assert.Equal("integer", schema.Type);
Assert.Equal("int32", schema.Format);
}
#endif
public class TestDbContext : DbContext
{
public DbSet<TestEntity> Entities { get; set; }
Expand Down
21 changes: 21 additions & 0 deletions test/StronglyTypedIds.IntegrationTests/LongIdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,27 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
}
#endif

#if NET5_0_OR_GREATER
[Fact]
public void CanShowImplementationTypeExample_WithSwaggerSchemaFilter()
{
var schemaGenerator = new Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator(
new Swashbuckle.AspNetCore.SwaggerGen.SchemaGeneratorOptions(),
new Swashbuckle.AspNetCore.SwaggerGen.JsonSerializerDataContractResolver(
new System.Text.Json.JsonSerializerOptions()));
var provider = Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(
new Microsoft.Extensions.DependencyInjection.ServiceCollection());
var schemaFilter = new Swashbuckle.AspNetCore.Annotations.AnnotationsSchemaFilter(provider);
var schemaRepository = new Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository();

var idType = typeof(SwaggerLongId);
var schema = schemaGenerator.GenerateSchema(idType, schemaRepository);
schemaFilter.Apply(schema, new Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext(idType, schemaGenerator, schemaRepository));

Assert.Equal("integer", schema.Type);
Assert.Equal("int64", schema.Format);
}
#endif
public class TestDbContext : DbContext
{
public DbSet<TestEntity> Entities { get; set; }
Expand Down
21 changes: 21 additions & 0 deletions test/StronglyTypedIds.IntegrationTests/MassTransitNewIdTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,27 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
}
#endif

#if NET5_0_OR_GREATER
[Fact]
public void CanShowImplementationTypeExample_WithSwaggerSchemaFilter()
{
var schemaGenerator = new Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator(
new Swashbuckle.AspNetCore.SwaggerGen.SchemaGeneratorOptions(),
new Swashbuckle.AspNetCore.SwaggerGen.JsonSerializerDataContractResolver(
new System.Text.Json.JsonSerializerOptions()));
var provider = Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(
new Microsoft.Extensions.DependencyInjection.ServiceCollection());
var schemaFilter = new Swashbuckle.AspNetCore.Annotations.AnnotationsSchemaFilter(provider);
var schemaRepository = new Swashbuckle.AspNetCore.SwaggerGen.SchemaRepository();

var idType = typeof(SwaggerNewIdId);
var schema = schemaGenerator.GenerateSchema(idType, schemaRepository);
schemaFilter.Apply(schema, new Swashbuckle.AspNetCore.SwaggerGen.SchemaFilterContext(idType, schemaGenerator, schemaRepository));

Assert.Equal("string", schema.Type);
Assert.Equal("uuid", schema.Format);
}
#endif
public class TestDbContext : DbContext
{
public DbSet<TestEntity> Entities { get; set; }
Expand Down
Loading