diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index d1eb44aa8..de20c17b2 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -18,6 +18,12 @@
"type": "boolean",
"description": "Indicates to continue a previously failed build attempt"
},
+ "Filter": {
+ "type": "string"
+ },
+ "Framework": {
+ "type": "string"
+ },
"GithubToken": {
"type": "string"
},
diff --git a/build/Build.cs b/build/Build.cs
index 39bb2c919..fa309a7b9 100644
--- a/build/Build.cs
+++ b/build/Build.cs
@@ -48,6 +48,8 @@ class Build : NukeBuild
[Parameter] readonly string GithubToken;
[Parameter] readonly string NuGetToken;
[Parameter] readonly AbsolutePath PackagesDirectory = RootDirectory / "packages";
+ [Parameter] readonly string Filter;
+ [Parameter] readonly string Framework;
const string NugetOrgUrl = "https://api.nuget.org/v3/index.json";
bool IsTag => GitHubActions.Instance?.GitHubRef?.StartsWith("refs/tags/") ?? false;
@@ -88,6 +90,8 @@ class Build : NukeBuild
DotNetTest(s => s
.SetProjectFile(Solution)
.SetConfiguration(Configuration)
+ .When(!string.IsNullOrEmpty(Filter), x => x.SetFilter(Filter))
+ .When(!string.IsNullOrEmpty(Framework), x => x.SetFramework(Framework))
.EnableNoBuild()
.EnableNoRestore());
});
diff --git a/src/StronglyTypedIds.Attributes/StronglyTypedIdConvertersAttribute.cs b/src/StronglyTypedIds.Attributes/StronglyTypedIdConvertersAttribute.cs
new file mode 100644
index 000000000..05de31caa
--- /dev/null
+++ b/src/StronglyTypedIds.Attributes/StronglyTypedIdConvertersAttribute.cs
@@ -0,0 +1,38 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+
+namespace StronglyTypedIds
+{
+ ///
+ /// Place on partial structs to generate converters for a strongly-typed ID, .
+ ///
+ [global::System.AttributeUsage(global::System.AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
+ [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")]
+ public sealed class StronglyTypedIdConvertersAttribute : global::System.Attribute
+ where T: struct
+ {
+ ///
+ /// Generate converters for a strongly typed ID
+ ///
+ /// The names of the template to use to generate the converters.
+ /// Templates must be added to the project using the format NAME.typedid,
+ /// where NAME is the name of the template passed in .
+ /// If no templates are provided, the default value is used, as specified by
+ ///
+ /// The StronglyTyped ID implementation for which these converters are associated
+ ///
+ public StronglyTypedIdConvertersAttribute(params string[] templateNames)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StronglyTypedIds.Attributes/StronglyTypedIdConvertersDefaultsAttribute.cs b/src/StronglyTypedIds.Attributes/StronglyTypedIdConvertersDefaultsAttribute.cs
new file mode 100644
index 000000000..f75b9ab4e
--- /dev/null
+++ b/src/StronglyTypedIds.Attributes/StronglyTypedIdConvertersDefaultsAttribute.cs
@@ -0,0 +1,49 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+
+namespace StronglyTypedIds
+{
+ ///
+ /// Used to control the default templates to use with instances
+ ///
+ [global::System.AttributeUsage(global::System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
+ [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")]
+ public sealed class StronglyTypedIdConvertersDefaultsAttribute : global::System.Attribute
+ {
+ ///
+ /// Set the default template to use for strongly typed ID converter types
+ ///
+ /// The name of the template to use to generate the converter type.
+ /// Templates must be added to the project using the format NAME.typedid,
+ /// where NAME is the name of the template passed in .
+ ///
+ public StronglyTypedIdConvertersDefaultsAttribute(string templateName)
+ {
+ }
+
+ ///
+ /// Set the default templates to use for strongly typed ID converter types
+ ///
+ /// The name of the template to use to generate the converter type.
+ /// Templates must be added to the project using the format NAME.typedid,
+ /// where NAME is the name of the template passed in .
+ ///
+ /// The names of additional custom templates to use to generate the converter type.
+ /// Templates must be added to the project using the format NAME.typedid,
+ /// where NAME is the name of the template passed in .
+ ///
+ public StronglyTypedIdConvertersDefaultsAttribute(string templateName, params string[] templateNames)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/StronglyTypedIds.Templates/guid-dapper.typedid b/src/StronglyTypedIds.Templates/guid-dapper.typedid
index 2453e2c83..36ab5c4fa 100644
--- a/src/StronglyTypedIds.Templates/guid-dapper.typedid
+++ b/src/StronglyTypedIds.Templates/guid-dapper.typedid
@@ -1,4 +1,4 @@
- partial struct PLACEHOLDERID
+ partial struct TARGETTYPE
{
public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
{
diff --git a/src/StronglyTypedIds.Templates/guid-efcore.typedid b/src/StronglyTypedIds.Templates/guid-efcore.typedid
index f6a59d0a5..055614151 100644
--- a/src/StronglyTypedIds.Templates/guid-efcore.typedid
+++ b/src/StronglyTypedIds.Templates/guid-efcore.typedid
@@ -1,4 +1,4 @@
- partial struct PLACEHOLDERID
+ partial struct TARGETTYPE
{
public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter
{
diff --git a/src/StronglyTypedIds.Templates/int-dapper.typedid b/src/StronglyTypedIds.Templates/int-dapper.typedid
index 8a10bf16f..23c03aad2 100644
--- a/src/StronglyTypedIds.Templates/int-dapper.typedid
+++ b/src/StronglyTypedIds.Templates/int-dapper.typedid
@@ -1,4 +1,4 @@
- partial struct PLACEHOLDERID
+ partial struct TARGETTYPE
{
public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
{
diff --git a/src/StronglyTypedIds.Templates/int-efcore.typedid b/src/StronglyTypedIds.Templates/int-efcore.typedid
index c1f8fb3c6..85c5b4bae 100644
--- a/src/StronglyTypedIds.Templates/int-efcore.typedid
+++ b/src/StronglyTypedIds.Templates/int-efcore.typedid
@@ -1,4 +1,4 @@
- partial struct PLACEHOLDERID
+ partial struct TARGETTYPE
{
public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter
{
diff --git a/src/StronglyTypedIds.Templates/long-dapper.typedid b/src/StronglyTypedIds.Templates/long-dapper.typedid
index 9466a251e..9a003d5a5 100644
--- a/src/StronglyTypedIds.Templates/long-dapper.typedid
+++ b/src/StronglyTypedIds.Templates/long-dapper.typedid
@@ -1,4 +1,4 @@
- partial struct PLACEHOLDERID
+ partial struct TARGETTYPE
{
public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
{
diff --git a/src/StronglyTypedIds.Templates/long-efcore.typedid b/src/StronglyTypedIds.Templates/long-efcore.typedid
index ef88f8b7d..4836d4226 100644
--- a/src/StronglyTypedIds.Templates/long-efcore.typedid
+++ b/src/StronglyTypedIds.Templates/long-efcore.typedid
@@ -1,4 +1,4 @@
- partial struct PLACEHOLDERID
+ partial struct TARGETTYPE
{
public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter
{
diff --git a/src/StronglyTypedIds.Templates/string-dapper.typedid b/src/StronglyTypedIds.Templates/string-dapper.typedid
index 4af050343..7071db1ad 100644
--- a/src/StronglyTypedIds.Templates/string-dapper.typedid
+++ b/src/StronglyTypedIds.Templates/string-dapper.typedid
@@ -1,4 +1,4 @@
- partial struct PLACEHOLDERID
+ partial struct TARGETTYPE
{
public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
{
diff --git a/src/StronglyTypedIds.Templates/string-efcore.typedid b/src/StronglyTypedIds.Templates/string-efcore.typedid
index 6fd5fc403..a1f03d2af 100644
--- a/src/StronglyTypedIds.Templates/string-efcore.typedid
+++ b/src/StronglyTypedIds.Templates/string-efcore.typedid
@@ -1,4 +1,4 @@
- partial struct PLACEHOLDERID
+ partial struct TARGETTYPE
{
public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter
{
diff --git a/src/StronglyTypedIds/Diagnostics/MissingDefaultsDiagnostic.cs b/src/StronglyTypedIds/Diagnostics/MissingDefaultsDiagnostic.cs
new file mode 100644
index 000000000..d7843580b
--- /dev/null
+++ b/src/StronglyTypedIds/Diagnostics/MissingDefaultsDiagnostic.cs
@@ -0,0 +1,16 @@
+using Microsoft.CodeAnalysis;
+
+namespace StronglyTypedIds.Diagnostics;
+
+internal static class MissingDefaultsDiagnostic
+{
+ internal const string Id = "STRONGID005";
+ internal const string Message = "You must specify the default template to use for converters using [StronglyTypedIdConvertersDefaults]";
+ internal const string Title = "Missing [StronglyTypedIdConvertersDefaults] attribute";
+
+ public static DiagnosticInfo CreateInfo(Location location) =>
+ new(new DiagnosticDescriptor(
+ Id, Title, Message, category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true),
+ location);
+}
\ No newline at end of file
diff --git a/src/StronglyTypedIds/EmbeddedSources.cs b/src/StronglyTypedIds/EmbeddedSources.cs
index e54f812ab..7eca7a334 100644
--- a/src/StronglyTypedIds/EmbeddedSources.cs
+++ b/src/StronglyTypedIds/EmbeddedSources.cs
@@ -10,6 +10,8 @@ internal static partial class EmbeddedSources
private static readonly Assembly ThisAssembly = typeof(EmbeddedSources).Assembly;
internal static readonly string StronglyTypedIdAttributeSource = LoadAttributeTemplateForEmitting("StronglyTypedIdAttribute");
internal static readonly string StronglyTypedIdDefaultsAttributeSource = LoadAttributeTemplateForEmitting("StronglyTypedIdDefaultsAttribute");
+ internal static readonly string StronglyTypedIdConvertersAttributeSource = LoadAttributeTemplateForEmitting("StronglyTypedIdConvertersAttribute");
+ internal static readonly string StronglyTypedIdConvertersDefaultsAttributeSource = LoadAttributeTemplateForEmitting("StronglyTypedIdConvertersDefaultsAttribute");
internal static readonly string TemplateSource = LoadAttributeTemplateForEmitting("Template");
internal static readonly string AutoGeneratedHeader = LoadEmbeddedResource("StronglyTypedIds.Templates.AutoGeneratedHeader.cs");
diff --git a/src/StronglyTypedIds/Parser.cs b/src/StronglyTypedIds/Parser.cs
index 2f4162f89..1eb6342dc 100644
--- a/src/StronglyTypedIds/Parser.cs
+++ b/src/StronglyTypedIds/Parser.cs
@@ -12,8 +12,10 @@ internal static class Parser
{
public const string StronglyTypedIdAttribute = "StronglyTypedIds.StronglyTypedIdAttribute";
public const string StronglyTypedIdDefaultsAttribute = "StronglyTypedIds.StronglyTypedIdDefaultsAttribute";
+ public const string StronglyTypedIdConvertersAttribute = "StronglyTypedIds.StronglyTypedIdConvertersAttribute`1";
+ public const string StronglyTypedIdConvertersDefaultsAttribute = "StronglyTypedIds.StronglyTypedIdConvertersDefaultsAttribute";
- public static Result<(StructToGenerate info, bool valid)> GetStructSemanticTarget(GeneratorAttributeSyntaxContext ctx, CancellationToken ct)
+ public static Result<(StructToGenerate info, bool valid)> GetIdSemanticTarget(GeneratorAttributeSyntaxContext ctx, CancellationToken ct)
{
var structSymbol = ctx.TargetSymbol as INamedTypeSymbol;
if (structSymbol is null)
@@ -88,7 +90,7 @@ internal static class Parser
return new Result<(StructToGenerate, bool)>((toGenerate, true), errors);
}
- public static Result<(Defaults defaults, bool valid)> GetDefaults(
+ public static Result<(Defaults defaults, bool valid)> GetIdDefaults(
GeneratorAttributeSyntaxContext ctx, CancellationToken ct)
{
var assemblyAttributes = ctx.TargetSymbol.GetAttributes();
@@ -147,6 +149,147 @@ internal static class Parser
}
var defaults = new Defaults(template, templateNames, attributeLocation!, hasMultiple);
+ return new Result<(Defaults, bool)>((defaults, true), errors);
+ }
+
+ public static Result<(ConverterToGenerate info, bool valid)> GetConvertersSemanticTarget(GeneratorAttributeSyntaxContext ctx, CancellationToken ct)
+ {
+ var structSymbol = ctx.TargetSymbol as INamedTypeSymbol;
+ if (structSymbol is null)
+ {
+ return Result.Fail();
+ }
+
+ var structSyntax = (StructDeclarationSyntax)ctx.TargetNode;
+
+ var hasMisconfiguredInput = false;
+ List? diagnostics = null;
+ string[]? templateNames = null;
+ LocationInfo? attributeLocation = null;
+ string? idName = null;
+
+ foreach (AttributeData attribute in structSymbol.GetAttributes())
+ {
+ if (!((attribute.AttributeClass?.Name == "StronglyTypedIdConvertersAttribute" ||
+ attribute.AttributeClass?.Name == "StronglyTypedIdConverters") &&
+ attribute.AttributeClass.IsGenericType &&
+ attribute.AttributeClass.TypeArguments.Length == 1))
+ {
+ // wrong attribute
+ continue;
+ }
+
+ // Can never have template
+ (var result, (_, templateNames)) = GetConstructorValues(attribute);
+ hasMisconfiguredInput |= result;
+
+ if (attribute.ApplicationSyntaxReference?.GetSyntax() is { } s)
+ {
+ attributeLocation = LocationInfo.CreateFrom(s);
+ }
+
+ var typeParameter = attribute.AttributeClass.TypeArguments[0];
+ idName = typeParameter.ToString();
+ }
+
+ var hasPartialModifier = false;
+ foreach (var modifier in structSyntax.Modifiers)
+ {
+ if (modifier.IsKind(SyntaxKind.PartialKeyword))
+ {
+ hasPartialModifier = true;
+ break;
+ }
+ }
+
+ if (!hasPartialModifier)
+ {
+ diagnostics ??= new();
+ diagnostics.Add(NotPartialDiagnostic.CreateInfo(structSyntax));
+ }
+
+ var errors = diagnostics is null
+ ? EquatableArray.Empty
+ : new EquatableArray(diagnostics.ToArray());
+
+ if (hasMisconfiguredInput || idName is null)
+ {
+ return new Result<(ConverterToGenerate, bool)>((default, false), errors);
+ }
+
+ string nameSpace = GetNameSpace(structSyntax);
+ ParentClass? parentClass = GetParentClasses(structSyntax);
+ var name = structSymbol.Name;
+
+ var toGenerate = new ConverterToGenerate(
+ name: name,
+ nameSpace: nameSpace,
+ idName: idName,
+ templateNames: templateNames,
+ templateLocation: attributeLocation!,
+ parent: parentClass);
+
+ return new Result<(ConverterToGenerate, bool)>((toGenerate, true), errors);
+ }
+
+ public static Result<(Defaults defaults, bool valid)> GetConverterDefaults(
+ GeneratorAttributeSyntaxContext ctx, CancellationToken ct)
+ {
+ var assemblyAttributes = ctx.TargetSymbol.GetAttributes();
+ if (assemblyAttributes.IsDefaultOrEmpty)
+ {
+ return Result.Fail();
+ }
+
+ // We only return the first config that we find
+ string[]? templateNames = null;
+ LocationInfo? attributeLocation = null;
+ List? diagnostics = null;
+ bool hasMisconfiguredInput = false;
+ bool hasMultiple = false;
+
+ // if we have multiple attributes we still check them, so that we can add extra diagnostics if necessary
+ // the "first" one found won't be flagged as a duplicate though.
+ foreach (AttributeData attribute in assemblyAttributes)
+ {
+ if (!((attribute.AttributeClass?.Name == "StronglyTypedIdConvertersDefaultsAttribute" ||
+ attribute.AttributeClass?.Name == "StronglyTypedIdConvertersDefaults") &&
+ attribute.AttributeClass.ToDisplayString() == StronglyTypedIdConvertersDefaultsAttribute))
+ {
+ // wrong attribute
+ continue;
+ }
+
+ var syntax = attribute.ApplicationSyntaxReference?.GetSyntax();
+ if (templateNames is not null || hasMisconfiguredInput)
+ {
+ hasMultiple = true;
+ if (syntax is not null)
+ {
+ diagnostics ??= new();
+ diagnostics.Add(MultipleAssemblyAttributeDiagnostic.CreateInfo(syntax));
+ }
+ }
+
+ (var result, (_, templateNames)) = GetConstructorValues(attribute);
+ hasMisconfiguredInput |= result;
+
+ if (syntax is not null)
+ {
+ attributeLocation = LocationInfo.CreateFrom(syntax);
+ }
+ }
+
+ var errors = diagnostics is null
+ ? EquatableArray.Empty
+ : new EquatableArray(diagnostics.ToArray());
+
+ if (hasMisconfiguredInput)
+ {
+ return new Result<(Defaults, bool)>((default, false), errors);
+ }
+
+ var defaults = new Defaults(template: null, templateNames, attributeLocation!, hasMultiple);
return new Result<(Defaults, bool)>((defaults, true), errors);
}
diff --git a/src/StronglyTypedIds/SourceGenerationHelper.cs b/src/StronglyTypedIds/SourceGenerationHelper.cs
index 5bebbee85..bb47c13d1 100644
--- a/src/StronglyTypedIds/SourceGenerationHelper.cs
+++ b/src/StronglyTypedIds/SourceGenerationHelper.cs
@@ -6,7 +6,8 @@ namespace StronglyTypedIds
internal static class SourceGenerationHelper
{
public static string CreateId(
- string idNamespace,
+ string targetNamespace,
+ string targetName,
string idName,
ParentClass? parentClass,
string template,
@@ -14,12 +15,12 @@ public static string CreateId(
bool addGeneratedCodeAttribute,
StringBuilder? sb)
{
- if (string.IsNullOrEmpty(idName))
+ if (string.IsNullOrEmpty(targetName))
{
- throw new ArgumentException("Value cannot be null or empty.", nameof(idName));
+ throw new ArgumentException("Value cannot be null or empty.", nameof(targetName));
}
- var hasNamespace = !string.IsNullOrEmpty(idNamespace);
+ var hasNamespace = !string.IsNullOrEmpty(targetNamespace);
var parentsCount = 0;
@@ -38,7 +39,7 @@ public static string CreateId(
{
sb
.Append("namespace ")
- .Append(idNamespace)
+ .Append(targetNamespace)
.AppendLine(@"
{");
}
@@ -85,6 +86,7 @@ public static string CreateId(
sb.AppendLine(template);
+ sb.Replace("TARGETTYPE", targetName);
sb.Replace("PLACEHOLDERID", idName);
for (int i = 0; i < parentsCount; i++)
diff --git a/src/StronglyTypedIds/StronglyTypedIdGenerator.cs b/src/StronglyTypedIds/StronglyTypedIdGenerator.cs
index 2801dc58c..a71a0c0a3 100644
--- a/src/StronglyTypedIds/StronglyTypedIdGenerator.cs
+++ b/src/StronglyTypedIds/StronglyTypedIdGenerator.cs
@@ -26,9 +26,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
{
i.AddSource("StronglyTypedIdAttribute.g.cs", EmbeddedSources.StronglyTypedIdAttributeSource);
i.AddSource("StronglyTypedIdDefaultsAttribute.g.cs", EmbeddedSources.StronglyTypedIdDefaultsAttributeSource);
+ i.AddSource("StronglyTypedIdConvertersAttribute.g.cs", EmbeddedSources.StronglyTypedIdConvertersAttributeSource);
+ i.AddSource("StronglyTypedIdConvertersDefaultsAttribute.g.cs", EmbeddedSources.StronglyTypedIdConvertersDefaultsAttributeSource);
i.AddSource("Template.g.cs", EmbeddedSources.TemplateSource);
});
+ // Templates
IncrementalValuesProvider<(string Path, string Name, string? Content)> allTemplates = context.AdditionalTextsProvider
.Where(template => Path.GetExtension(template.Path).Equals(TemplateSuffix, StringComparison.OrdinalIgnoreCase))
.Select((template, ct) => (
@@ -39,48 +42,95 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var templates = allTemplates
.Where(template => !string.IsNullOrWhiteSpace(template.Name) && template.Content is not null)
.Collect();
-
- IncrementalValuesProvider> structAndDiagnostics = context.SyntaxProvider
- .ForAttributeWithMetadataName(
- Parser.StronglyTypedIdAttribute,
- predicate: (node, _) => node is StructDeclarationSyntax,
- transform: Parser.GetStructSemanticTarget)
- .Where(static m => m is not null);
- IncrementalValuesProvider> defaultsAndDiagnostics = context.SyntaxProvider
+ // ID defaults
+ IncrementalValuesProvider> idDefaultsAndDiagnostics = context.SyntaxProvider
.ForAttributeWithMetadataName(
Parser.StronglyTypedIdDefaultsAttribute,
predicate: (node, _) => node is CompilationUnitSyntax,
- transform: Parser.GetDefaults)
+ transform: Parser.GetIdDefaults)
.Where(static m => m is not null);
- context.RegisterSourceOutput(
- structAndDiagnostics.SelectMany((x, _) => x.Errors),
- static (context, info) => context.ReportDiagnostic(info));
+ IncrementalValueProvider<(EquatableArray<(string Name, string Content)> Content, bool isValid, DiagnosticInfo? Diagnostic)> idDefaultTemplateContent = idDefaultsAndDiagnostics
+ .Where(static x => x.Value.valid)
+ .Select((result, _) => result.Value.defaults)
+ .Collect()
+ .Combine(templates)
+ .Select(ProcessIdDefaults);
context.RegisterSourceOutput(
- defaultsAndDiagnostics.SelectMany((x, _) => x.Errors),
+ idDefaultsAndDiagnostics.SelectMany((x, _) => x.Errors),
static (context, info) => context.ReportDiagnostic(info));
- IncrementalValuesProvider structs = structAndDiagnostics
+ // IDs
+ IncrementalValuesProvider> idsAndDiagnostics = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ Parser.StronglyTypedIdAttribute,
+ predicate: (node, _) => node is StructDeclarationSyntax,
+ transform: Parser.GetIdSemanticTarget)
+ .Where(static m => m is not null);
+
+ IncrementalValuesProvider ids = idsAndDiagnostics
.Where(static x => x.Value.valid)
.Select((result, _) => result.Value.info);
- IncrementalValueProvider<(EquatableArray<(string Name, string Content)> Content, bool isValid, DiagnosticInfo? Diagnostic)> defaultTemplateContent = defaultsAndDiagnostics
+ context.RegisterSourceOutput(
+ idsAndDiagnostics.SelectMany((x, _) => x.Errors),
+ static (context, info) => context.ReportDiagnostic(info));
+
+ // Converter defaults
+ IncrementalValuesProvider> converterDefaultsAndDiagnostics = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ Parser.StronglyTypedIdConvertersDefaultsAttribute,
+ predicate: (node, _) => node is CompilationUnitSyntax,
+ transform: Parser.GetConverterDefaults)
+ .Where(static m => m is not null);
+
+ IncrementalValueProvider<(EquatableArray<(string Name, string Content)> Content, bool isValid, DiagnosticInfo? Diagnostic)> converterDefaultTemplateContent = converterDefaultsAndDiagnostics
.Where(static x => x.Value.valid)
.Select((result, _) => result.Value.defaults)
.Collect()
.Combine(templates)
- .Select(ProcessDefaults);
+ .Select(ProcessConverterDefaults);
+
+ context.RegisterSourceOutput(
+ converterDefaultsAndDiagnostics.SelectMany((x, _) => x.Errors),
+ static (context, info) => context.ReportDiagnostic(info));
+
+ // Converters
+ IncrementalValuesProvider> convertersAndDiagnostics = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ Parser.StronglyTypedIdConvertersAttribute,
+ predicate: (node, _) => node is StructDeclarationSyntax,
+ transform: Parser.GetConvertersSemanticTarget)
+ .Where(static m => m is not null);
+
+ IncrementalValuesProvider converters = convertersAndDiagnostics
+ .Where(static x => x.Value.valid)
+ .Select((result, _) => result.Value.info);
+
+ context.RegisterSourceOutput(
+ convertersAndDiagnostics.SelectMany((x, _) => x.Errors),
+ static (context, info) => context.ReportDiagnostic(info));
- var structsWithDefaultsAndTemplates = structs
+ // Combined
+ var idsWithDefaultsAndTemplates = ids
.Combine(templates)
- .Combine(defaultTemplateContent);
+ .Combine(idDefaultTemplateContent);
- context.RegisterSourceOutput(structsWithDefaultsAndTemplates,
- static (spc, source) => Execute(source.Left.Left, source.Left.Right, source.Right, spc));
+ var convertersWithDefaultsAndTemplates = converters
+ .Combine(templates)
+ .Combine(converterDefaultTemplateContent);
+
+ // Output
+ context.RegisterSourceOutput(idsWithDefaultsAndTemplates,
+ static (spc, source) => GenerateIds(source.Left.Left, source.Left.Right, source.Right, spc));
+
+ context.RegisterSourceOutput(convertersWithDefaultsAndTemplates,
+ static (spc, source) => GenerateConverters(source.Left.Left, source.Left.Right, source.Right, spc));
}
- private static void Execute(
+
+ private static void GenerateIds(
StructToGenerate idToGenerate,
ImmutableArray<(string Path, string Name, string? Content)> templates,
(EquatableArray<(string Name, string Content)>, bool IsValid, DiagnosticInfo? Diagnostic) defaults,
@@ -92,7 +142,7 @@ private static void Execute(
context.ReportDiagnostic(diagnostic);
}
- if (!TryGetTemplateContent(idToGenerate, templates, defaults, in context, out var templateContents))
+ if (!TryGetTemplateContent(idToGenerate.Template, idToGenerate.TemplateNames, idToGenerate.TemplateLocation, templates, defaults, in context, out var templateContents))
{
return;
}
@@ -104,6 +154,7 @@ private static void Execute(
var result = SourceGenerationHelper.CreateId(
idToGenerate.NameSpace,
idToGenerate.Name,
+ idToGenerate.Name, // same type
idToGenerate.Parent,
content,
addDefaultAttributes: string.IsNullOrEmpty(name),
@@ -123,23 +174,99 @@ private static void Execute(
}
}
+ private static void GenerateConverters(
+ ConverterToGenerate converterToGenerate,
+ ImmutableArray<(string Path, string Name, string? Content)> templates,
+ (EquatableArray<(string Name, string Content)> Templates, bool IsValid, DiagnosticInfo? Diagnostic) defaults,
+ SourceProductionContext context)
+ {
+ if (defaults.Diagnostic is { } diagnostic)
+ {
+ // report error with the default template
+ context.ReportDiagnostic(diagnostic);
+ }
+
+ if (converterToGenerate.TemplateNames.Count == 0
+ && defaults is { IsValid: false, Templates.Count: 0 })
+ {
+ // not allowed this, so add a diagnostic
+ if (converterToGenerate.TemplateLocation is { } l)
+ {
+ var location = Location.Create(l.FilePath, l.TextSpan, l.LineSpan);
+ context.ReportDiagnostic(MissingDefaultsDiagnostic.CreateInfo(location));
+ }
- private static (EquatableArray<(string Name, string Content)>, bool, DiagnosticInfo?) ProcessDefaults((ImmutableArray Left, ImmutableArray<(string Path, string Name, string? Content)> Right) all, CancellationToken _)
+ return;
+ }
+
+ if (!TryGetTemplateContent(selectedTemplate: null, converterToGenerate.TemplateNames, converterToGenerate.TemplateLocation, templates, defaults, in context, out var templateContents))
+ {
+ return;
+ }
+
+ var addGeneratedCodeAttribute = true;
+
+ var sb = new StringBuilder();
+ foreach (var (name, content) in templateContents.Distinct())
+ {
+ var result = SourceGenerationHelper.CreateId(
+ converterToGenerate.NameSpace,
+ converterToGenerate.Name,
+ converterToGenerate.IdName,
+ converterToGenerate.Parent,
+ content,
+ addDefaultAttributes: string.IsNullOrEmpty(name),
+ addGeneratedCodeAttribute: addGeneratedCodeAttribute,
+ sb);
+
+ addGeneratedCodeAttribute = false; // We can only add it once, so just add to the first rendering
+
+ var fileName = SourceGenerationHelper.CreateSourceName(
+ sb,
+ converterToGenerate.NameSpace,
+ converterToGenerate.Parent,
+ converterToGenerate.Name,
+ name);
+
+ context.AddSource(fileName, SourceText.From(result, Encoding.UTF8));
+ }
+ }
+
+
+ private static (EquatableArray<(string Name, string Content)>, bool, DiagnosticInfo?) ProcessIdDefaults(
+ (ImmutableArray Selected, ImmutableArray<(string Path, string Name, string? Content)> Templates) all,
+ CancellationToken _)
+ => ProcessDefaults(all, allowEmptyDefaults: true);
+
+ private static (EquatableArray<(string Name, string Content)>, bool, DiagnosticInfo?) ProcessConverterDefaults(
+ (ImmutableArray Selected, ImmutableArray<(string Path, string Name, string? Content)> Templates) all,
+ CancellationToken _)
+ => ProcessDefaults(all, allowEmptyDefaults: false);
+
+ private static (EquatableArray<(string Name, string Content)>, bool, DiagnosticInfo?) ProcessDefaults(
+ (ImmutableArray Selected, ImmutableArray<(string Path, string Name, string? Content)> Templates) all,
+ bool allowEmptyDefaults)
{
- if (all.Left.IsDefaultOrEmpty)
+ if (all.Selected.IsDefaultOrEmpty)
{
// no default attributes, valid, but no content
- return (EquatableArray<(string Name, string Content)>.Empty, true, null);
+ if (allowEmptyDefaults)
+ {
+ return (EquatableArray<(string Name, string Content)>.Empty, true, null);
+ }
+
+ // Not allowed empty
+ return (EquatableArray<(string Name, string Content)>.Empty, false, null);
}
// technically we can never have more than one `Defaults` here
// but check for it just in case
- if (all.Left is {IsDefaultOrEmpty: false, Length: > 1})
+ if (all.Selected is {IsDefaultOrEmpty: false, Length: > 1})
{
return (EquatableArray<(string Name, string Content)>.Empty, false, null);
}
- var defaults = all.Left[0];
+ var defaults = all.Selected[0];
if (defaults.HasMultiple)
{
// not valid
@@ -168,7 +295,7 @@ private static (EquatableArray<(string Name, string Content)>, bool, DiagnosticI
}
// We have already checked for null/empty template name and flagged it as an error
- if (!GetContent(templateNames, defaults.TemplateLocation!, builtInTemplate.HasValue, in all.Right, out var contents, out var diagnostic))
+ if (!GetContent(templateNames, defaults.TemplateLocation!, builtInTemplate.HasValue, in all.Templates, out var contents, out var diagnostic))
{
return (EquatableArray<(string Name, string Content)>.Empty, false, diagnostic);
}
@@ -183,26 +310,28 @@ private static (EquatableArray<(string Name, string Content)>, bool, DiagnosticI
}
private static bool TryGetTemplateContent(
- in StructToGenerate idToGenerate,
+ Template? selectedTemplate,
+ EquatableArray selectedTemplateNames,
+ LocationInfo? attributeLocation,
in ImmutableArray<(string Path, string Name, string? Content)> templates,
(EquatableArray<(string Name, string Content)> Contents, bool IsValid, DiagnosticInfo? Diagnostics) defaults,
in SourceProductionContext context,
[NotNullWhen(true)] out (string Name, string Content)[]? templateContents)
{
(string, string)? builtIn = null;
- if (idToGenerate.Template is { } templateId)
+ if (selectedTemplate is { } templateId)
{
// built-in template specified
var content = EmbeddedSources.GetTemplate(templateId);
builtIn = (string.Empty, content);
}
- if (idToGenerate.TemplateNames.GetArray() is {Length: > 0} templateNames)
+ if (selectedTemplateNames.GetArray() is {Length: > 0} templateNames)
{
// custom template specified
if (GetContent(
templateNames,
- idToGenerate.TemplateLocation,
+ attributeLocation,
builtIn.HasValue,
in templates,
out templateContents,
diff --git a/src/StronglyTypedIds/StructToGenerate.cs b/src/StronglyTypedIds/StructToGenerate.cs
index 2f2a666c7..0c3fe15e2 100644
--- a/src/StronglyTypedIds/StructToGenerate.cs
+++ b/src/StronglyTypedIds/StructToGenerate.cs
@@ -24,6 +24,26 @@ public StructToGenerate(string name, string nameSpace, Template? template, strin
public LocationInfo? TemplateLocation { get; }
}
+internal readonly record struct ConverterToGenerate
+{
+ public ConverterToGenerate(string name, string nameSpace, string idName, string[]? templateNames, ParentClass? parent, LocationInfo templateLocation)
+ {
+ Name = name;
+ NameSpace = nameSpace;
+ IdName = idName;
+ TemplateNames = templateNames is null ? EquatableArray.Empty : new EquatableArray(templateNames);
+ Parent = parent;
+ TemplateLocation = templateLocation;
+ }
+
+ public string Name { get; }
+ public string NameSpace { get; }
+ public string IdName { get; }
+ public EquatableArray TemplateNames { get; }
+ public ParentClass? Parent { get; }
+ public LocationInfo? TemplateLocation { get; }
+}
+
internal sealed record Result(TValue Value, EquatableArray Errors)
where TValue : IEquatable?
{
diff --git a/test/StronglyTypedIds.IntegrationTests.ExternalIds/Converters.cs b/test/StronglyTypedIds.IntegrationTests.ExternalIds/Converters.cs
new file mode 100644
index 000000000..48b6494d1
--- /dev/null
+++ b/test/StronglyTypedIds.IntegrationTests.ExternalIds/Converters.cs
@@ -0,0 +1,15 @@
+using StronglyTypedIds.IntegrationTests.Types;
+
+namespace StronglyTypedIds.IntegrationTests;
+
+[StronglyTypedIdConverters("guid-dapper", "guid-efcore")]
+internal partial struct Guid1Converters { }
+
+[StronglyTypedIdConverters("int-dapper", "int-efcore")]
+internal partial struct IntConverters { }
+
+[StronglyTypedIdConverters("long-dapper", "long-efcore")]
+internal partial struct LongConverters { }
+
+[StronglyTypedIdConverters("string-dapper", "string-efcore")]
+internal partial struct StringConverters { }
\ No newline at end of file
diff --git a/test/StronglyTypedIds.IntegrationTests.ExternalIds/StronglyTypedIds.IntegrationTests.ExternalIds.csproj b/test/StronglyTypedIds.IntegrationTests.ExternalIds/StronglyTypedIds.IntegrationTests.ExternalIds.csproj
index 833c1281f..2e422fd2e 100644
--- a/test/StronglyTypedIds.IntegrationTests.ExternalIds/StronglyTypedIds.IntegrationTests.ExternalIds.csproj
+++ b/test/StronglyTypedIds.IntegrationTests.ExternalIds/StronglyTypedIds.IntegrationTests.ExternalIds.csproj
@@ -6,6 +6,15 @@
false
true
+
+
+
+
+
+
+
+
+
diff --git a/test/StronglyTypedIds.IntegrationTests/DapperTypeHandlers.cs b/test/StronglyTypedIds.IntegrationTests/DapperTypeHandlers.cs
index 517239aea..478e1b150 100644
--- a/test/StronglyTypedIds.IntegrationTests/DapperTypeHandlers.cs
+++ b/test/StronglyTypedIds.IntegrationTests/DapperTypeHandlers.cs
@@ -19,6 +19,10 @@ public static void AddHandlers()
SqlMapper.AddTypeHandler(new ConvertersStringId2.DapperTypeHandler());
SqlMapper.AddTypeHandler(new NullableStringId.DapperTypeHandler());
SqlMapper.AddTypeHandler(new NewIdId1.DapperTypeHandler());
+ SqlMapper.AddTypeHandler(new Guid1Converters.DapperTypeHandler());
+ SqlMapper.AddTypeHandler(new IntConverters.DapperTypeHandler());
+ SqlMapper.AddTypeHandler(new LongConverters.DapperTypeHandler());
+ SqlMapper.AddTypeHandler(new StringConverters.DapperTypeHandler());
}
}
}
\ No newline at end of file
diff --git a/test/StronglyTypedIds.IntegrationTests/Enums.cs b/test/StronglyTypedIds.IntegrationTests/Enums.cs
index 66e88e574..d71808b14 100644
--- a/test/StronglyTypedIds.IntegrationTests/Enums.cs
+++ b/test/StronglyTypedIds.IntegrationTests/Enums.cs
@@ -72,3 +72,14 @@ internal readonly partial struct VeryNestedId
}
}
+[StronglyTypedIdConverters("guid-dapper", "guid-efcore")]
+internal partial struct Guid1Converters { }
+
+[StronglyTypedIdConverters("int-dapper", "int-efcore")]
+internal partial struct IntConverters { }
+
+[StronglyTypedIdConverters("long-dapper", "long-efcore")]
+internal partial struct LongConverters { }
+
+[StronglyTypedIdConverters("string-dapper", "string-efcore")]
+internal partial struct StringConverters { }
\ No newline at end of file
diff --git a/test/StronglyTypedIds.IntegrationTests/GuidIdTests.cs b/test/StronglyTypedIds.IntegrationTests/GuidIdTests.cs
index 6cdca8eca..f5fd41532 100644
--- a/test/StronglyTypedIds.IntegrationTests/GuidIdTests.cs
+++ b/test/StronglyTypedIds.IntegrationTests/GuidIdTests.cs
@@ -374,20 +374,36 @@ public void WhenEfCoreValueConverterUsesValueConverter()
}
[Fact]
- public async Task WhenDapperValueConverterUsesValueConverter()
+ public Task WhenDapperValueConverterUsesValueConverter_Id()
+ => WhenDapperValueConverterUsesValueConverter(g => new ConvertersGuidId(g));
+
+ [Fact]
+ public Task WhenDapperValueConverterUsesValueConverter_Converter()
+ => WhenDapperValueConverterUsesValueConverter(g => new GuidId1(g));
+
+ private async Task WhenDapperValueConverterUsesValueConverter(Func newFunc)
{
using var connection = new SqliteConnection("DataSource=:memory:");
await connection.OpenAsync();
- var results = await connection.QueryAsync("SELECT '5640dad4-862a-4738-9e3c-c76dc227eb66'");
+ var results = await connection.QueryAsync("SELECT '5640dad4-862a-4738-9e3c-c76dc227eb66'");
var value = Assert.Single(results);
- Assert.Equal(value, new ConvertersGuidId(Guid.Parse("5640dad4-862a-4738-9e3c-c76dc227eb66")));
+ Assert.Equal(value, newFunc(Guid.Parse("5640dad4-862a-4738-9e3c-c76dc227eb66")));
}
#if NET6_0_OR_GREATER
[Fact]
- public void WhenConventionBasedEfCoreValueConverterUsesValueConverter()
+ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter_Id()
+ => WhenConventionBasedEfCoreValueConverterUsesValueConverter(x => x.Entities,
+ new TestEntity { Id = ConvertersGuidId.New() });
+
+ [Fact]
+ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter_Converter()
+ => WhenConventionBasedEfCoreValueConverterUsesValueConverter(c => c.Entities3,
+ new TestEntity3 { Id = GuidId1.New() });
+
+ private void WhenConventionBasedEfCoreValueConverterUsesValueConverter(Func> dbsetFunc, T entity) where T : class
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
@@ -399,13 +415,12 @@ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter()
using (var context = new ConventionsDbContext(options))
{
context.Database.EnsureCreated();
- context.Entities.Add(
- new TestEntity { Id = ConvertersGuidId.New() });
+ dbsetFunc(context).Add(entity);
context.SaveChanges();
}
using (var context = new ConventionsDbContext(options))
{
- var all = context.Entities.ToList();
+ var all = dbsetFunc(context).ToList();
Assert.Single(all);
}
}
@@ -519,6 +534,7 @@ internal class ConventionsDbContext : DbContext
{
public DbSet Entities { get; set; }
public DbSet Entities2 { get; set; }
+ public DbSet Entities3 { get; set; }
public ConventionsDbContext(DbContextOptions options) : base(options)
{
@@ -532,6 +548,9 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura
configurationBuilder
.Properties()
.HaveConversion();
+ configurationBuilder
+ .Properties()
+ .HaveConversion();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -550,6 +569,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Property(x => x.Id)
.ValueGeneratedNever();
});
+ modelBuilder
+ .Entity(builder =>
+ {
+ builder
+ .Property(x => x.Id)
+ .ValueGeneratedNever();
+ });
}
}
#endif
@@ -605,6 +631,11 @@ internal class TestEntity2
public ConvertersGuidId2 Id { get; set; }
}
+ internal class TestEntity3
+ {
+ public GuidId1 Id { get; set; }
+ }
+
internal class EntityWithNullableId2
{
public ConvertersGuidId2? Id { get; set; }
diff --git a/test/StronglyTypedIds.IntegrationTests/IntIdTests.cs b/test/StronglyTypedIds.IntegrationTests/IntIdTests.cs
index dd73bae77..af687fce0 100644
--- a/test/StronglyTypedIds.IntegrationTests/IntIdTests.cs
+++ b/test/StronglyTypedIds.IntegrationTests/IntIdTests.cs
@@ -273,15 +273,22 @@ public void WhenEfCoreValueConverterUsesValueConverter()
}
[Fact]
- public async Task WhenDapperValueConverterUsesValueConverter()
+ public Task WhenDapperValueConverterUsesValueConverter_Id()
+ => WhenDapperValueConverterUsesValueConverter(g => new ConvertersIntId(g));
+
+ [Fact]
+ public Task WhenDapperValueConverterUsesValueConverter_Converter()
+ => WhenDapperValueConverterUsesValueConverter(g => new IntId(g));
+
+ private async Task WhenDapperValueConverterUsesValueConverter(Func newFunc)
{
using var connection = new SqliteConnection("DataSource=:memory:");
await connection.OpenAsync();
- var results = await connection.QueryAsync("SELECT 123");
+ var results = await connection.QueryAsync("SELECT 123");
var value = Assert.Single(results);
- Assert.Equal(new ConvertersIntId(123), value);
+ Assert.Equal(newFunc(123), value);
}
[Fact(Skip = "Requires localdb to be available")]
@@ -321,7 +328,16 @@ public void TypeConverter_CanConvertToAndFrom(object value)
#if NET6_0_OR_GREATER
[Fact]
- public void WhenConventionBasedEfCoreValueConverterUsesValueConverter()
+ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter_Id()
+ => WhenConventionBasedEfCoreValueConverterUsesValueConverter(x => x.Entities,
+ new TestEntity { Id = new ConvertersIntId(123) });
+
+ [Fact]
+ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter_Converter()
+ => WhenConventionBasedEfCoreValueConverterUsesValueConverter(c => c.Entities3,
+ new TestEntity3 { Id = new IntId(123) });
+
+ private void WhenConventionBasedEfCoreValueConverterUsesValueConverter(Func> dbsetFunc, T entity) where T : class
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
@@ -333,14 +349,13 @@ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter()
using (var context = new ConventionsDbContext(options))
{
context.Database.EnsureCreated();
- context.Entities.Add(
- new TestEntity {Id = new ConvertersIntId(123)});
+ dbsetFunc(context).Add(entity);
context.SaveChanges();
}
using (var context = new ConventionsDbContext(options))
{
- var all = context.Entities.ToList();
+ var all = dbsetFunc(context).ToList();
Assert.Single(all);
}
}
@@ -499,6 +514,7 @@ internal class ConventionsDbContext : DbContext
{
public DbSet Entities { get; set; }
public DbSet Entities2 { get; set; }
+ public DbSet Entities3 { get; set; }
public ConventionsDbContext(DbContextOptions options) : base(options)
{
@@ -512,6 +528,9 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura
configurationBuilder
.Properties()
.HaveConversion();
+ configurationBuilder
+ .Properties()
+ .HaveConversion();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -530,6 +549,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Property(x => x.Id)
.ValueGeneratedNever();
});
+ modelBuilder
+ .Entity(builder =>
+ {
+ builder
+ .Property(x => x.Id)
+ .ValueGeneratedNever();
+ });
}
}
#endif
@@ -579,6 +605,11 @@ internal class TestEntity2
public ConvertersIntId2 Id { get; set; }
}
+ internal class TestEntity3
+ {
+ public IntId Id { get; set; }
+ }
+
internal class EntityWithNullableId2
{
public ConvertersIntId2? Id { get; set; }
diff --git a/test/StronglyTypedIds.IntegrationTests/LongIdTests.cs b/test/StronglyTypedIds.IntegrationTests/LongIdTests.cs
index 45805b944..9f8370f44 100644
--- a/test/StronglyTypedIds.IntegrationTests/LongIdTests.cs
+++ b/test/StronglyTypedIds.IntegrationTests/LongIdTests.cs
@@ -285,15 +285,22 @@ public void WhenEfCoreValueConverterUsesValueConverter()
}
[Fact]
- public async Task WhenDapperValueConverterUsesValueConverter()
+ public Task WhenDapperValueConverterUsesValueConverter_Id()
+ => WhenDapperValueConverterUsesValueConverter(g => new ConvertersLongId(g));
+
+ [Fact]
+ public Task WhenDapperValueConverterUsesValueConverter_Converter()
+ => WhenDapperValueConverterUsesValueConverter(g => new LongId(g));
+
+ private async Task WhenDapperValueConverterUsesValueConverter(Func newFunc)
{
using var connection = new SqliteConnection("DataSource=:memory:");
await connection.OpenAsync();
- var results = await connection.QueryAsync("SELECT 123");
+ var results = await connection.QueryAsync("SELECT 123");
var value = Assert.Single(results);
- Assert.Equal(value, new ConvertersLongId(123));
+ Assert.Equal(value, newFunc(123));
}
[Fact(Skip = "Requires localdb to be available")]
@@ -319,7 +326,16 @@ public void WhenDapperValueConverterAndDecimalUsesValueConverter()
#if NET6_0_OR_GREATER
[Fact]
- public void WhenConventionBasedEfCoreValueConverterUsesValueConverter()
+ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter_Id()
+ => WhenConventionBasedEfCoreValueConverterUsesValueConverter(x => x.Entities,
+ new TestEntity { Id = new ConvertersLongId(123) });
+
+ [Fact]
+ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter_Converter()
+ => WhenConventionBasedEfCoreValueConverterUsesValueConverter(c => c.Entities3,
+ new TestEntity3 { Id = new LongId(123) });
+
+ private void WhenConventionBasedEfCoreValueConverterUsesValueConverter(Func> dbsetFunc, T entity) where T : class
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
@@ -331,13 +347,12 @@ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter()
using (var context = new ConventionsDbContext(options))
{
context.Database.EnsureCreated();
- context.Entities.Add(
- new TestEntity { Id = new ConvertersLongId(123) });
+ dbsetFunc(context).Add(entity);
context.SaveChanges();
}
using (var context = new ConventionsDbContext(options))
{
- var all = context.Entities.ToList();
+ var all = dbsetFunc(context).ToList();
Assert.Single(all);
}
}
@@ -481,6 +496,7 @@ internal class ConventionsDbContext : DbContext
{
public DbSet Entities { get; set; }
public DbSet Entities2 { get; set; }
+ public DbSet Entities3 { get; set; }
public ConventionsDbContext(DbContextOptions options) : base(options)
{
@@ -494,6 +510,9 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura
configurationBuilder
.Properties()
.HaveConversion();
+ configurationBuilder
+ .Properties()
+ .HaveConversion();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -512,6 +531,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Property(x => x.Id)
.ValueGeneratedNever();
});
+ modelBuilder
+ .Entity(builder =>
+ {
+ builder
+ .Property(x => x.Id)
+ .ValueGeneratedNever();
+ });
}
}
#endif
@@ -561,6 +587,11 @@ internal class TestEntity2
public ConvertersLongId2 Id { get; set; }
}
+ internal class TestEntity3
+ {
+ public LongId Id { get; set; }
+ }
+
internal class EntityWithNullableId2
{
public ConvertersLongId2? Id { get; set; }
diff --git a/test/StronglyTypedIds.IntegrationTests/StringIdTests.cs b/test/StronglyTypedIds.IntegrationTests/StringIdTests.cs
index 4a2e34945..d7642e761 100644
--- a/test/StronglyTypedIds.IntegrationTests/StringIdTests.cs
+++ b/test/StronglyTypedIds.IntegrationTests/StringIdTests.cs
@@ -295,21 +295,39 @@ public void WhenEfCoreValueConverterUsesValueConverter()
}
}
+
+
+ [Fact]
+ public Task WhenDapperValueConverterUsesValueConverter_Id()
+ => WhenDapperValueConverterUsesValueConverter(g => new ConvertersStringId(g));
+
[Fact]
- public async Task WhenDapperValueConverterUsesValueConverter()
+ public Task WhenDapperValueConverterUsesValueConverter_Converter()
+ => WhenDapperValueConverterUsesValueConverter(g => new StringId(g));
+
+ private async Task WhenDapperValueConverterUsesValueConverter(Func newFunc)
{
using var connection = new SqliteConnection("DataSource=:memory:");
await connection.OpenAsync();
- var results = await connection.QueryAsync("SELECT 'this is a value'");
+ var results = await connection.QueryAsync("SELECT 'this is a value'");
var value = Assert.Single(results);
- Assert.Equal(value, new ConvertersStringId("this is a value"));
+ Assert.Equal(value, newFunc("this is a value"));
}
#if NET6_0_OR_GREATER
[Fact]
- public void WhenConventionBasedEfCoreValueConverterUsesValueConverter()
+ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter_Id()
+ => WhenConventionBasedEfCoreValueConverterUsesValueConverter(x => x.Entities,
+ new TestEntity { Id = Guid.NewGuid(), Name = new ConvertersStringId("some name") });
+
+ [Fact]
+ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter_Converter()
+ => WhenConventionBasedEfCoreValueConverterUsesValueConverter(c => c.Entities3,
+ new TestEntity3 { Id = Guid.NewGuid(), Name = new StringId("some name") });
+
+ private void WhenConventionBasedEfCoreValueConverterUsesValueConverter(Func> dbsetFunc, T entity) where T : class
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
@@ -318,20 +336,17 @@ public void WhenConventionBasedEfCoreValueConverterUsesValueConverter()
.UseSqlite(connection)
.Options;
- var original = new TestEntity { Id = Guid.NewGuid(), Name = new ConvertersStringId("some name") };
using (var context = new ConventionsDbContext(options))
{
context.Database.EnsureCreated();
- context.Entities.Add(original);
+ dbsetFunc(context).Add(entity);
context.SaveChanges();
}
using (var context = new ConventionsDbContext(options))
{
- var all = context.Entities.ToList();
+ var all = dbsetFunc(context).ToList();
var retrieved = Assert.Single(all);
- Assert.Equal(original.Id, retrieved.Id);
- Assert.Equal(original.Name, retrieved.Name);
}
}
#endif
@@ -470,6 +485,7 @@ internal class ConventionsDbContext : DbContext
{
public DbSet Entities { get; set; }
public DbSet Entities2 { get; set; }
+ public DbSet Entities3 { get; set; }
public ConventionsDbContext(DbContextOptions options) : base(options)
{
@@ -483,6 +499,9 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura
configurationBuilder
.Properties()
.HaveConversion();
+ configurationBuilder
+ .Properties()
+ .HaveConversion();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -501,6 +520,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Property(x => x.Id)
.ValueGeneratedNever();
});
+ modelBuilder
+ .Entity(builder =>
+ {
+ builder
+ .Property(x => x.Id)
+ .ValueGeneratedNever();
+ });
}
}
#endif
@@ -552,6 +578,12 @@ internal class TestEntity2
public ConvertersStringId2 Name { get; set; }
}
+ internal class TestEntity3
+ {
+ public Guid Id { get; set; }
+ public StringId Name { get; set; }
+ }
+
internal class EntityWithNullableId2
{
public ConvertersStringId2? Id { get; set; }
diff --git a/test/StronglyTypedIds.Tests/EmbeddedResourceTests.cs b/test/StronglyTypedIds.Tests/EmbeddedResourceTests.cs
index fd4aef693..3a993efd0 100644
--- a/test/StronglyTypedIds.Tests/EmbeddedResourceTests.cs
+++ b/test/StronglyTypedIds.Tests/EmbeddedResourceTests.cs
@@ -11,6 +11,8 @@ public class EmbeddedResourceTests
{
"StronglyTypedIdAttribute",
"StronglyTypedIdDefaultsAttribute",
+ "StronglyTypedIdConvertersAttribute",
+ "StronglyTypedIdConvertersDefaultsAttribute",
"Template",
};
diff --git a/test/StronglyTypedIds.Tests/Snapshots/EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdConvertersAttribute.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdConvertersAttribute.verified.txt
new file mode 100644
index 000000000..a0aa2596d
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdConvertersAttribute.verified.txt
@@ -0,0 +1,41 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+
+#if STRONGLY_TYPED_ID_EMBED_ATTRIBUTES
+
+namespace StronglyTypedIds
+{
+ ///
+ /// Place on partial structs to generate converters for a strongly-typed ID, .
+ ///
+ [global::System.AttributeUsage(global::System.AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
+ [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")]
+ internal sealed class StronglyTypedIdConvertersAttribute : global::System.Attribute
+ where T: struct
+ {
+ ///
+ /// Generate converters for a strongly typed ID
+ ///
+ /// The names of the template to use to generate the converters.
+ /// Templates must be added to the project using the format NAME.typedid,
+ /// where NAME is the name of the template passed in .
+ /// If no templates are provided, the default value is used, as specified by
+ ///
+ /// The StronglyTyped ID implementation for which these converters are associated
+ ///
+ public StronglyTypedIdConvertersAttribute(params string[] templateNames)
+ {
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/StronglyTypedIds.Tests/Snapshots/EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdConvertersDefaultsAttribute.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdConvertersDefaultsAttribute.verified.txt
new file mode 100644
index 000000000..2f3ea4bcd
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdConvertersDefaultsAttribute.verified.txt
@@ -0,0 +1,52 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+
+#if STRONGLY_TYPED_ID_EMBED_ATTRIBUTES
+
+namespace StronglyTypedIds
+{
+ ///
+ /// Used to control the default templates to use with instances
+ ///
+ [global::System.AttributeUsage(global::System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
+ [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")]
+ internal sealed class StronglyTypedIdConvertersDefaultsAttribute : global::System.Attribute
+ {
+ ///
+ /// Set the default template to use for strongly typed ID converter types
+ ///
+ /// The name of the template to use to generate the converter type.
+ /// Templates must be added to the project using the format NAME.typedid,
+ /// where NAME is the name of the template passed in .
+ ///
+ public StronglyTypedIdConvertersDefaultsAttribute(string templateName)
+ {
+ }
+
+ ///
+ /// Set the default templates to use for strongly typed ID converter types
+ ///
+ /// The name of the template to use to generate the converter type.
+ /// Templates must be added to the project using the format NAME.typedid,
+ /// where NAME is the name of the template passed in .
+ ///
+ /// The names of additional custom templates to use to generate the converter type.
+ /// Templates must be added to the project using the format NAME.typedid,
+ /// where NAME is the name of the template passed in .
+ ///
+ public StronglyTypedIdConvertersDefaultsAttribute(string templateName, params string[] templateNames)
+ {
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInDifferentNamespace.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInDifferentNamespace.verified.txt
new file mode 100644
index 000000000..e2bc0e43d
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInDifferentNamespace.verified.txt
@@ -0,0 +1,271 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace1
+{
+ [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))]
+ [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyId :
+#if NET6_0_OR_GREATER
+ global::System.ISpanFormattable,
+#endif
+#if NET7_0_OR_GREATER
+ global::System.IParsable, global::System.ISpanParsable,
+#endif
+#if NET8_0_OR_GREATER
+ global::System.IUtf8SpanFormattable,
+#endif
+ global::System.IComparable, global::System.IEquatable, global::System.IFormattable
+ {
+ public global::System.Guid Value { get; }
+
+ public MyId(global::System.Guid value)
+ {
+ Value = value;
+ }
+
+ public static MyId New() => new MyId(global::System.Guid.NewGuid());
+ public static readonly MyId Empty = new MyId(global::System.Guid.Empty);
+
+ ///
+ public bool Equals(MyId other) => this.Value.Equals(other.Value);
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is MyId other && Equals(other);
+ }
+
+ public override int GetHashCode() => Value.GetHashCode();
+
+ public override string ToString() => Value.ToString();
+
+ public static bool operator ==(MyId a, MyId b) => a.Equals(b);
+ public static bool operator !=(MyId a, MyId b) => !(a == b);
+ public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0;
+ public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0;
+ public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0;
+ public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0;
+
+ ///
+ public int CompareTo(MyId other) => Value.CompareTo(other.Value);
+
+ public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter
+ {
+ public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => base.ConvertFrom(context, culture, value),
+ };
+ }
+
+ public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
+ }
+
+ public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType)
+ {
+ if (value is MyId idValue)
+ {
+ if (destinationType == typeof(global::System.Guid))
+ {
+ return idValue.Value;
+ }
+
+ if (destinationType == typeof(string))
+ {
+ return idValue.Value.ToString();
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+
+ public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ public override bool CanConvert(global::System.Type typeToConvert)
+ => typeToConvert == typeof(global::System.Guid) || typeToConvert == typeof(string) || base.CanConvert(typeToConvert);
+
+ public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new (reader.GetGuid());
+
+ public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Value);
+
+#if NET6_0_OR_GREATER
+ public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null")));
+
+ public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WritePropertyName(value.Value.ToString());
+#endif
+ }
+
+ public static MyId Parse(string input)
+ => new(global::System.Guid.Parse(input));
+
+#if NET7_0_OR_GREATER
+ ///
+ public static MyId Parse(string input, global::System.IFormatProvider? provider)
+ => new(global::System.Guid.Parse(input, provider));
+
+ ///
+ public static bool TryParse(
+ [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input,
+ global::System.IFormatProvider? provider,
+ out MyId result)
+ {
+ if (input is null)
+ {
+ result = default;
+ return false;
+ }
+
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+#endif
+
+ ///
+ public string ToString(
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ string? format,
+ global::System.IFormatProvider? formatProvider)
+ => Value.ToString(format, formatProvider);
+
+#if NETCOREAPP2_1_OR_GREATER
+ public static MyId Parse(global::System.ReadOnlySpan input)
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET6_0_OR_GREATER
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider)
+#if NET7_0_OR_GREATER
+ => new(global::System.Guid.Parse(input, provider));
+#else
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result)
+ {
+#if NET7_0_OR_GREATER
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+#else
+ if (global::System.Guid.TryParse(input, out var guid))
+#endif
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(destination, out charsWritten, format);
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format = default)
+ => Value.TryFormat(destination, out charsWritten, format);
+#endif
+#if NET8_0_OR_GREATER
+ ///
+ public bool TryFormat(
+ global::System.Span utf8Destination,
+ out int bytesWritten,
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(utf8Destination, out bytesWritten, format);
+#endif
+ }
+}
+
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace2
+{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyIdConverters
+ {
+ public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
+ {
+ public override void SetValue(global::System.Data.IDbDataParameter parameter, SomeNamespace1.MyId value)
+ {
+ parameter.Value = value.Value;
+ }
+
+ public override SomeNamespace1.MyId Parse(object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new SomeNamespace1.MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new SomeNamespace1.MyId(result),
+ _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to SomeNamespace1.MyId"),
+ };
+ }
+ }
+ }
+}
diff --git a/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInFileScopedNamespace.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInFileScopedNamespace.verified.txt
new file mode 100644
index 000000000..2cfd883a2
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInFileScopedNamespace.verified.txt
@@ -0,0 +1,271 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace
+{
+ [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))]
+ [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyId :
+#if NET6_0_OR_GREATER
+ global::System.ISpanFormattable,
+#endif
+#if NET7_0_OR_GREATER
+ global::System.IParsable, global::System.ISpanParsable,
+#endif
+#if NET8_0_OR_GREATER
+ global::System.IUtf8SpanFormattable,
+#endif
+ global::System.IComparable, global::System.IEquatable, global::System.IFormattable
+ {
+ public global::System.Guid Value { get; }
+
+ public MyId(global::System.Guid value)
+ {
+ Value = value;
+ }
+
+ public static MyId New() => new MyId(global::System.Guid.NewGuid());
+ public static readonly MyId Empty = new MyId(global::System.Guid.Empty);
+
+ ///
+ public bool Equals(MyId other) => this.Value.Equals(other.Value);
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is MyId other && Equals(other);
+ }
+
+ public override int GetHashCode() => Value.GetHashCode();
+
+ public override string ToString() => Value.ToString();
+
+ public static bool operator ==(MyId a, MyId b) => a.Equals(b);
+ public static bool operator !=(MyId a, MyId b) => !(a == b);
+ public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0;
+ public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0;
+ public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0;
+ public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0;
+
+ ///
+ public int CompareTo(MyId other) => Value.CompareTo(other.Value);
+
+ public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter
+ {
+ public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => base.ConvertFrom(context, culture, value),
+ };
+ }
+
+ public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
+ }
+
+ public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType)
+ {
+ if (value is MyId idValue)
+ {
+ if (destinationType == typeof(global::System.Guid))
+ {
+ return idValue.Value;
+ }
+
+ if (destinationType == typeof(string))
+ {
+ return idValue.Value.ToString();
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+
+ public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ public override bool CanConvert(global::System.Type typeToConvert)
+ => typeToConvert == typeof(global::System.Guid) || typeToConvert == typeof(string) || base.CanConvert(typeToConvert);
+
+ public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new (reader.GetGuid());
+
+ public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Value);
+
+#if NET6_0_OR_GREATER
+ public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null")));
+
+ public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WritePropertyName(value.Value.ToString());
+#endif
+ }
+
+ public static MyId Parse(string input)
+ => new(global::System.Guid.Parse(input));
+
+#if NET7_0_OR_GREATER
+ ///
+ public static MyId Parse(string input, global::System.IFormatProvider? provider)
+ => new(global::System.Guid.Parse(input, provider));
+
+ ///
+ public static bool TryParse(
+ [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input,
+ global::System.IFormatProvider? provider,
+ out MyId result)
+ {
+ if (input is null)
+ {
+ result = default;
+ return false;
+ }
+
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+#endif
+
+ ///
+ public string ToString(
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ string? format,
+ global::System.IFormatProvider? formatProvider)
+ => Value.ToString(format, formatProvider);
+
+#if NETCOREAPP2_1_OR_GREATER
+ public static MyId Parse(global::System.ReadOnlySpan input)
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET6_0_OR_GREATER
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider)
+#if NET7_0_OR_GREATER
+ => new(global::System.Guid.Parse(input, provider));
+#else
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result)
+ {
+#if NET7_0_OR_GREATER
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+#else
+ if (global::System.Guid.TryParse(input, out var guid))
+#endif
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(destination, out charsWritten, format);
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format = default)
+ => Value.TryFormat(destination, out charsWritten, format);
+#endif
+#if NET8_0_OR_GREATER
+ ///
+ public bool TryFormat(
+ global::System.Span utf8Destination,
+ out int bytesWritten,
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(utf8Destination, out bytesWritten, format);
+#endif
+ }
+}
+
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace
+{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyIdConverters
+ {
+ public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
+ {
+ public override void SetValue(global::System.Data.IDbDataParameter parameter, SomeNamespace.MyId value)
+ {
+ parameter.Value = value.Value;
+ }
+
+ public override SomeNamespace.MyId Parse(object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new SomeNamespace.MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new SomeNamespace.MyId(result),
+ _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to SomeNamespace.MyId"),
+ };
+ }
+ }
+ }
+}
diff --git a/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInGlobalNamespace.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInGlobalNamespace.verified.txt
new file mode 100644
index 000000000..fa0bd9ba1
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInGlobalNamespace.verified.txt
@@ -0,0 +1,265 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+ [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))]
+ [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyId :
+#if NET6_0_OR_GREATER
+ global::System.ISpanFormattable,
+#endif
+#if NET7_0_OR_GREATER
+ global::System.IParsable, global::System.ISpanParsable,
+#endif
+#if NET8_0_OR_GREATER
+ global::System.IUtf8SpanFormattable,
+#endif
+ global::System.IComparable, global::System.IEquatable, global::System.IFormattable
+ {
+ public global::System.Guid Value { get; }
+
+ public MyId(global::System.Guid value)
+ {
+ Value = value;
+ }
+
+ public static MyId New() => new MyId(global::System.Guid.NewGuid());
+ public static readonly MyId Empty = new MyId(global::System.Guid.Empty);
+
+ ///
+ public bool Equals(MyId other) => this.Value.Equals(other.Value);
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is MyId other && Equals(other);
+ }
+
+ public override int GetHashCode() => Value.GetHashCode();
+
+ public override string ToString() => Value.ToString();
+
+ public static bool operator ==(MyId a, MyId b) => a.Equals(b);
+ public static bool operator !=(MyId a, MyId b) => !(a == b);
+ public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0;
+ public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0;
+ public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0;
+ public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0;
+
+ ///
+ public int CompareTo(MyId other) => Value.CompareTo(other.Value);
+
+ public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter
+ {
+ public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => base.ConvertFrom(context, culture, value),
+ };
+ }
+
+ public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
+ }
+
+ public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType)
+ {
+ if (value is MyId idValue)
+ {
+ if (destinationType == typeof(global::System.Guid))
+ {
+ return idValue.Value;
+ }
+
+ if (destinationType == typeof(string))
+ {
+ return idValue.Value.ToString();
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+
+ public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ public override bool CanConvert(global::System.Type typeToConvert)
+ => typeToConvert == typeof(global::System.Guid) || typeToConvert == typeof(string) || base.CanConvert(typeToConvert);
+
+ public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new (reader.GetGuid());
+
+ public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Value);
+
+#if NET6_0_OR_GREATER
+ public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null")));
+
+ public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WritePropertyName(value.Value.ToString());
+#endif
+ }
+
+ public static MyId Parse(string input)
+ => new(global::System.Guid.Parse(input));
+
+#if NET7_0_OR_GREATER
+ ///
+ public static MyId Parse(string input, global::System.IFormatProvider? provider)
+ => new(global::System.Guid.Parse(input, provider));
+
+ ///
+ public static bool TryParse(
+ [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input,
+ global::System.IFormatProvider? provider,
+ out MyId result)
+ {
+ if (input is null)
+ {
+ result = default;
+ return false;
+ }
+
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+#endif
+
+ ///
+ public string ToString(
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ string? format,
+ global::System.IFormatProvider? formatProvider)
+ => Value.ToString(format, formatProvider);
+
+#if NETCOREAPP2_1_OR_GREATER
+ public static MyId Parse(global::System.ReadOnlySpan input)
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET6_0_OR_GREATER
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider)
+#if NET7_0_OR_GREATER
+ => new(global::System.Guid.Parse(input, provider));
+#else
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result)
+ {
+#if NET7_0_OR_GREATER
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+#else
+ if (global::System.Guid.TryParse(input, out var guid))
+#endif
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(destination, out charsWritten, format);
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format = default)
+ => Value.TryFormat(destination, out charsWritten, format);
+#endif
+#if NET8_0_OR_GREATER
+ ///
+ public bool TryFormat(
+ global::System.Span utf8Destination,
+ out int bytesWritten,
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(utf8Destination, out bytesWritten, format);
+#endif
+ }
+
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyIdConverters
+ {
+ public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
+ {
+ public override void SetValue(global::System.Data.IDbDataParameter parameter, MyId value)
+ {
+ parameter.Value = value.Value;
+ }
+
+ public override MyId Parse(object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to MyId"),
+ };
+ }
+ }
+ }
diff --git a/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInNamespace.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInNamespace.verified.txt
new file mode 100644
index 000000000..2cfd883a2
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateDefaultConverterIdInNamespace.verified.txt
@@ -0,0 +1,271 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace
+{
+ [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))]
+ [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyId :
+#if NET6_0_OR_GREATER
+ global::System.ISpanFormattable,
+#endif
+#if NET7_0_OR_GREATER
+ global::System.IParsable, global::System.ISpanParsable,
+#endif
+#if NET8_0_OR_GREATER
+ global::System.IUtf8SpanFormattable,
+#endif
+ global::System.IComparable, global::System.IEquatable, global::System.IFormattable
+ {
+ public global::System.Guid Value { get; }
+
+ public MyId(global::System.Guid value)
+ {
+ Value = value;
+ }
+
+ public static MyId New() => new MyId(global::System.Guid.NewGuid());
+ public static readonly MyId Empty = new MyId(global::System.Guid.Empty);
+
+ ///
+ public bool Equals(MyId other) => this.Value.Equals(other.Value);
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is MyId other && Equals(other);
+ }
+
+ public override int GetHashCode() => Value.GetHashCode();
+
+ public override string ToString() => Value.ToString();
+
+ public static bool operator ==(MyId a, MyId b) => a.Equals(b);
+ public static bool operator !=(MyId a, MyId b) => !(a == b);
+ public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0;
+ public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0;
+ public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0;
+ public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0;
+
+ ///
+ public int CompareTo(MyId other) => Value.CompareTo(other.Value);
+
+ public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter
+ {
+ public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => base.ConvertFrom(context, culture, value),
+ };
+ }
+
+ public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
+ }
+
+ public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType)
+ {
+ if (value is MyId idValue)
+ {
+ if (destinationType == typeof(global::System.Guid))
+ {
+ return idValue.Value;
+ }
+
+ if (destinationType == typeof(string))
+ {
+ return idValue.Value.ToString();
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+
+ public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ public override bool CanConvert(global::System.Type typeToConvert)
+ => typeToConvert == typeof(global::System.Guid) || typeToConvert == typeof(string) || base.CanConvert(typeToConvert);
+
+ public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new (reader.GetGuid());
+
+ public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Value);
+
+#if NET6_0_OR_GREATER
+ public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null")));
+
+ public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WritePropertyName(value.Value.ToString());
+#endif
+ }
+
+ public static MyId Parse(string input)
+ => new(global::System.Guid.Parse(input));
+
+#if NET7_0_OR_GREATER
+ ///
+ public static MyId Parse(string input, global::System.IFormatProvider? provider)
+ => new(global::System.Guid.Parse(input, provider));
+
+ ///
+ public static bool TryParse(
+ [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input,
+ global::System.IFormatProvider? provider,
+ out MyId result)
+ {
+ if (input is null)
+ {
+ result = default;
+ return false;
+ }
+
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+#endif
+
+ ///
+ public string ToString(
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ string? format,
+ global::System.IFormatProvider? formatProvider)
+ => Value.ToString(format, formatProvider);
+
+#if NETCOREAPP2_1_OR_GREATER
+ public static MyId Parse(global::System.ReadOnlySpan input)
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET6_0_OR_GREATER
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider)
+#if NET7_0_OR_GREATER
+ => new(global::System.Guid.Parse(input, provider));
+#else
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result)
+ {
+#if NET7_0_OR_GREATER
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+#else
+ if (global::System.Guid.TryParse(input, out var guid))
+#endif
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(destination, out charsWritten, format);
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format = default)
+ => Value.TryFormat(destination, out charsWritten, format);
+#endif
+#if NET8_0_OR_GREATER
+ ///
+ public bool TryFormat(
+ global::System.Span utf8Destination,
+ out int bytesWritten,
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(utf8Destination, out bytesWritten, format);
+#endif
+ }
+}
+
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace
+{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyIdConverters
+ {
+ public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
+ {
+ public override void SetValue(global::System.Data.IDbDataParameter parameter, SomeNamespace.MyId value)
+ {
+ parameter.Value = value.Value;
+ }
+
+ public override SomeNamespace.MyId Parse(object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new SomeNamespace.MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new SomeNamespace.MyId(result),
+ _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to SomeNamespace.MyId"),
+ };
+ }
+ }
+ }
+}
diff --git a/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateMultipleConvertersWithSameName.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateMultipleConvertersWithSameName.verified.txt
new file mode 100644
index 000000000..a960e3c57
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateMultipleConvertersWithSameName.verified.txt
@@ -0,0 +1,314 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+ public partial class ParentClass
+ {
+ [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))]
+ [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyId :
+#if NET6_0_OR_GREATER
+ global::System.ISpanFormattable,
+#endif
+#if NET7_0_OR_GREATER
+ global::System.IParsable, global::System.ISpanParsable,
+#endif
+#if NET8_0_OR_GREATER
+ global::System.IUtf8SpanFormattable,
+#endif
+ global::System.IComparable, global::System.IEquatable, global::System.IFormattable
+ {
+ public global::System.Guid Value { get; }
+
+ public MyId(global::System.Guid value)
+ {
+ Value = value;
+ }
+
+ public static MyId New() => new MyId(global::System.Guid.NewGuid());
+ public static readonly MyId Empty = new MyId(global::System.Guid.Empty);
+
+ ///
+ public bool Equals(MyId other) => this.Value.Equals(other.Value);
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is MyId other && Equals(other);
+ }
+
+ public override int GetHashCode() => Value.GetHashCode();
+
+ public override string ToString() => Value.ToString();
+
+ public static bool operator ==(MyId a, MyId b) => a.Equals(b);
+ public static bool operator !=(MyId a, MyId b) => !(a == b);
+ public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0;
+ public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0;
+ public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0;
+ public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0;
+
+ ///
+ public int CompareTo(MyId other) => Value.CompareTo(other.Value);
+
+ public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter
+ {
+ public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => base.ConvertFrom(context, culture, value),
+ };
+ }
+
+ public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
+ }
+
+ public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType)
+ {
+ if (value is MyId idValue)
+ {
+ if (destinationType == typeof(global::System.Guid))
+ {
+ return idValue.Value;
+ }
+
+ if (destinationType == typeof(string))
+ {
+ return idValue.Value.ToString();
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+
+ public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ public override bool CanConvert(global::System.Type typeToConvert)
+ => typeToConvert == typeof(global::System.Guid) || typeToConvert == typeof(string) || base.CanConvert(typeToConvert);
+
+ public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new (reader.GetGuid());
+
+ public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Value);
+
+#if NET6_0_OR_GREATER
+ public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null")));
+
+ public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WritePropertyName(value.Value.ToString());
+#endif
+ }
+
+ public static MyId Parse(string input)
+ => new(global::System.Guid.Parse(input));
+
+#if NET7_0_OR_GREATER
+ ///
+ public static MyId Parse(string input, global::System.IFormatProvider? provider)
+ => new(global::System.Guid.Parse(input, provider));
+
+ ///
+ public static bool TryParse(
+ [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input,
+ global::System.IFormatProvider? provider,
+ out MyId result)
+ {
+ if (input is null)
+ {
+ result = default;
+ return false;
+ }
+
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+#endif
+
+ ///
+ public string ToString(
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ string? format,
+ global::System.IFormatProvider? formatProvider)
+ => Value.ToString(format, formatProvider);
+
+#if NETCOREAPP2_1_OR_GREATER
+ public static MyId Parse(global::System.ReadOnlySpan input)
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET6_0_OR_GREATER
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider)
+#if NET7_0_OR_GREATER
+ => new(global::System.Guid.Parse(input, provider));
+#else
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result)
+ {
+#if NET7_0_OR_GREATER
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+#else
+ if (global::System.Guid.TryParse(input, out var guid))
+#endif
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(destination, out charsWritten, format);
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format = default)
+ => Value.TryFormat(destination, out charsWritten, format);
+#endif
+#if NET8_0_OR_GREATER
+ ///
+ public bool TryFormat(
+ global::System.Span utf8Destination,
+ out int bytesWritten,
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(utf8Destination, out bytesWritten, format);
+#endif
+ }
+ }
+
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace MyContracts.V1
+{
+ public partial class ConverterClass
+ {
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyIdConverters
+ {
+ public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
+ {
+ public override void SetValue(global::System.Data.IDbDataParameter parameter, MyId value)
+ {
+ parameter.Value = value.Value;
+ }
+
+ public override MyId Parse(object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to MyId"),
+ };
+ }
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace MyContracts.V2
+{
+ public partial class ConverterClass
+ {
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyIdConverters
+ {
+ public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
+ {
+ public override void SetValue(global::System.Data.IDbDataParameter parameter, MyId value)
+ {
+ parameter.Value = value.Value;
+ }
+
+ public override MyId Parse(object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to MyId"),
+ };
+ }
+ }
+ }
+ }
+}
diff --git a/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateNestedIdInFileScopeNamespace.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateNestedIdInFileScopeNamespace.verified.txt
new file mode 100644
index 000000000..c3bda2a74
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateNestedIdInFileScopeNamespace.verified.txt
@@ -0,0 +1,277 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace
+{
+ public partial class ParentClass
+ {
+ [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))]
+ [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyId :
+#if NET6_0_OR_GREATER
+ global::System.ISpanFormattable,
+#endif
+#if NET7_0_OR_GREATER
+ global::System.IParsable, global::System.ISpanParsable,
+#endif
+#if NET8_0_OR_GREATER
+ global::System.IUtf8SpanFormattable,
+#endif
+ global::System.IComparable, global::System.IEquatable, global::System.IFormattable
+ {
+ public global::System.Guid Value { get; }
+
+ public MyId(global::System.Guid value)
+ {
+ Value = value;
+ }
+
+ public static MyId New() => new MyId(global::System.Guid.NewGuid());
+ public static readonly MyId Empty = new MyId(global::System.Guid.Empty);
+
+ ///
+ public bool Equals(MyId other) => this.Value.Equals(other.Value);
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is MyId other && Equals(other);
+ }
+
+ public override int GetHashCode() => Value.GetHashCode();
+
+ public override string ToString() => Value.ToString();
+
+ public static bool operator ==(MyId a, MyId b) => a.Equals(b);
+ public static bool operator !=(MyId a, MyId b) => !(a == b);
+ public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0;
+ public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0;
+ public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0;
+ public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0;
+
+ ///
+ public int CompareTo(MyId other) => Value.CompareTo(other.Value);
+
+ public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter
+ {
+ public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => base.ConvertFrom(context, culture, value),
+ };
+ }
+
+ public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
+ }
+
+ public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType)
+ {
+ if (value is MyId idValue)
+ {
+ if (destinationType == typeof(global::System.Guid))
+ {
+ return idValue.Value;
+ }
+
+ if (destinationType == typeof(string))
+ {
+ return idValue.Value.ToString();
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+
+ public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ public override bool CanConvert(global::System.Type typeToConvert)
+ => typeToConvert == typeof(global::System.Guid) || typeToConvert == typeof(string) || base.CanConvert(typeToConvert);
+
+ public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new (reader.GetGuid());
+
+ public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Value);
+
+#if NET6_0_OR_GREATER
+ public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null")));
+
+ public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WritePropertyName(value.Value.ToString());
+#endif
+ }
+
+ public static MyId Parse(string input)
+ => new(global::System.Guid.Parse(input));
+
+#if NET7_0_OR_GREATER
+ ///
+ public static MyId Parse(string input, global::System.IFormatProvider? provider)
+ => new(global::System.Guid.Parse(input, provider));
+
+ ///
+ public static bool TryParse(
+ [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input,
+ global::System.IFormatProvider? provider,
+ out MyId result)
+ {
+ if (input is null)
+ {
+ result = default;
+ return false;
+ }
+
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+#endif
+
+ ///
+ public string ToString(
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ string? format,
+ global::System.IFormatProvider? formatProvider)
+ => Value.ToString(format, formatProvider);
+
+#if NETCOREAPP2_1_OR_GREATER
+ public static MyId Parse(global::System.ReadOnlySpan input)
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET6_0_OR_GREATER
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider)
+#if NET7_0_OR_GREATER
+ => new(global::System.Guid.Parse(input, provider));
+#else
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result)
+ {
+#if NET7_0_OR_GREATER
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+#else
+ if (global::System.Guid.TryParse(input, out var guid))
+#endif
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(destination, out charsWritten, format);
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format = default)
+ => Value.TryFormat(destination, out charsWritten, format);
+#endif
+#if NET8_0_OR_GREATER
+ ///
+ public bool TryFormat(
+ global::System.Span utf8Destination,
+ out int bytesWritten,
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(utf8Destination, out bytesWritten, format);
+#endif
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace
+{
+ public partial class ConverterClass
+ {
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyIdConverters
+ {
+ public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
+ {
+ public override void SetValue(global::System.Data.IDbDataParameter parameter, MyId value)
+ {
+ parameter.Value = value.Value;
+ }
+
+ public override MyId Parse(object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to MyId"),
+ };
+ }
+ }
+ }
+ }
+}
diff --git a/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateNonDefaultConverterIdInNamespace.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateNonDefaultConverterIdInNamespace.verified.txt
new file mode 100644
index 000000000..6972e8a1f
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.CanGenerateNonDefaultConverterIdInNamespace.verified.txt
@@ -0,0 +1,286 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace
+{
+ [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))]
+ [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyId :
+#if NET6_0_OR_GREATER
+ global::System.ISpanFormattable,
+#endif
+#if NET7_0_OR_GREATER
+ global::System.IParsable, global::System.ISpanParsable,
+#endif
+#if NET8_0_OR_GREATER
+ global::System.IUtf8SpanParsable, global::System.IUtf8SpanFormattable,
+#endif
+ global::System.IComparable, global::System.IEquatable, global::System.IFormattable
+ {
+ public int Value { get; }
+
+ public MyId(int value)
+ {
+ Value = value;
+ }
+
+ public static readonly MyId Empty = new MyId(0);
+
+ ///
+ public bool Equals(MyId other) => this.Value.Equals(other.Value);
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is MyId other && Equals(other);
+ }
+
+ public override int GetHashCode() => Value.GetHashCode();
+
+ public override string ToString() => Value.ToString(global::System.Globalization.CultureInfo.InvariantCulture);
+
+ public static bool operator ==(MyId a, MyId b) => a.Equals(b);
+ public static bool operator !=(MyId a, MyId b) => !(a == b);
+ public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0;
+ public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0;
+ public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0;
+ public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0;
+
+ ///
+ public int CompareTo(MyId other) => Value.CompareTo(other.Value);
+
+ public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter
+ {
+ public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType)
+ {
+ return sourceType == typeof(int) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value)
+ {
+ return value switch
+ {
+ int intValue => new MyId(intValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && int.TryParse(stringValue, out var result) => new MyId(result),
+ _ => base.ConvertFrom(context, culture, value),
+ };
+ }
+
+ public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType)
+ {
+ return sourceType == typeof(int) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
+ }
+
+ public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType)
+ {
+ if (value is MyId idValue)
+ {
+ if (destinationType == typeof(int))
+ {
+ return idValue.Value;
+ }
+
+ if (destinationType == typeof(string))
+ {
+ return idValue.Value.ToString(global::System.Globalization.CultureInfo.InvariantCulture);
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+
+ public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ public override bool CanConvert(global::System.Type typeToConvert)
+ => typeToConvert == typeof(int) || typeToConvert == typeof(string) || base.CanConvert(typeToConvert);
+
+ public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new (reader.GetInt32());
+
+ public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WriteNumberValue(value.Value);
+
+#if NET6_0_OR_GREATER
+ public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new(int.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null")));
+
+ public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WritePropertyName(value.Value.ToString(global::System.Globalization.CultureInfo.InvariantCulture));
+#endif
+ }
+
+ public static MyId Parse(string input)
+ => new(int.Parse(input));
+
+#if NET7_0_OR_GREATER
+ ///
+ public static MyId Parse(string input, global::System.IFormatProvider? provider)
+ => new(int.Parse(input, provider));
+
+ ///
+ public static bool TryParse(
+ [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input,
+ global::System.IFormatProvider? provider,
+ out MyId result)
+ {
+ if (input is null)
+ {
+ result = default;
+ return false;
+ }
+
+ if (int.TryParse(input, provider, out var value))
+ {
+ result = new(value);
+ return true;
+ }
+
+ result = default;
+ return false;
+ }
+#endif
+
+ ///
+ public string ToString(
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
+#endif
+ string? format,
+ global::System.IFormatProvider? formatProvider)
+ => Value.ToString(format, formatProvider);
+
+#if NETCOREAPP2_1_OR_GREATER
+ public static MyId Parse(global::System.ReadOnlySpan input)
+ => new(int.Parse(input));
+#endif
+
+#if NET6_0_OR_GREATER
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider)
+#if NET7_0_OR_GREATER
+ => new(int.Parse(input, provider));
+#else
+ => new(int.Parse(input));
+#endif
+
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result)
+ {
+#if NET7_0_OR_GREATER
+ if (int.TryParse(input, provider, out var value))
+#else
+ if (int.TryParse(input, out var value))
+#endif
+ {
+ result = new(value);
+ return true;
+ }
+
+ result = default;
+ return false;
+ }
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
+#endif
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(destination, out charsWritten, format);
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
+#endif
+ global::System.ReadOnlySpan format = default)
+ => Value.TryFormat(destination, out charsWritten, format);
+#endif
+#if NET8_0_OR_GREATER
+ ///
+ public bool TryFormat(
+ global::System.Span utf8Destination,
+ out int bytesWritten,
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)]
+ global::System.ReadOnlySpan format = default,
+ global::System.IFormatProvider? provider = null)
+ => Value.TryFormat(utf8Destination, out bytesWritten, format, provider);
+
+ ///
+ public static MyId Parse(global::System.ReadOnlySpan utf8Text, global::System.IFormatProvider? provider)
+ => new(int.Parse(utf8Text, provider));
+
+ ///
+ public static bool TryParse(global::System.ReadOnlySpan utf8Text, global::System.IFormatProvider? provider, out MyId result)
+ {
+ if (int.TryParse(utf8Text, provider, out var intResult))
+ {
+ result = new MyId(intResult);
+ return true;
+ }
+
+ result = default;
+ return false;
+ }
+#endif
+ }
+}
+
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+namespace SomeNamespace
+{
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyIdConverters
+ {
+ public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
+ {
+ public override void SetValue(global::System.Data.IDbDataParameter parameter, SomeNamespace.MyId value)
+ {
+ parameter.Value = value.Value;
+ }
+
+ public override SomeNamespace.MyId Parse(object value)
+ {
+ return value switch
+ {
+ int intValue => new SomeNamespace.MyId(intValue),
+ short shortValue => new SomeNamespace.MyId(shortValue),
+ long longValue and < int.MaxValue and > int.MinValue => new SomeNamespace.MyId((int)longValue),
+ decimal decimalValue and < int.MaxValue and > int.MinValue => new SomeNamespace.MyId((int)decimalValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && int.TryParse(stringValue, out var result) => new SomeNamespace.MyId(result),
+ _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to SomeNamespace.MyId"),
+ };
+ }
+ }
+ }
+}
diff --git a/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.DefaultConverterIdInGlobalNamespaceWithoutDefaultsDoesntGenerateConverters.verified.txt b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.DefaultConverterIdInGlobalNamespaceWithoutDefaultsDoesntGenerateConverters.verified.txt
new file mode 100644
index 000000000..77006d100
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/Snapshots/StronglyTypedIdConverterTests.DefaultConverterIdInGlobalNamespaceWithoutDefaultsDoesntGenerateConverters.verified.txt
@@ -0,0 +1,231 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by the StronglyTypedId source generator
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+#pragma warning disable 1591 // publicly visible type or member must be documented
+
+#nullable enable
+ [global::System.ComponentModel.TypeConverter(typeof(MyIdTypeConverter))]
+ [global::System.Text.Json.Serialization.JsonConverter(typeof(MyIdSystemTextJsonConverter))]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "1.0.0-beta6")]
+ partial struct MyId :
+#if NET6_0_OR_GREATER
+ global::System.ISpanFormattable,
+#endif
+#if NET7_0_OR_GREATER
+ global::System.IParsable, global::System.ISpanParsable,
+#endif
+#if NET8_0_OR_GREATER
+ global::System.IUtf8SpanFormattable,
+#endif
+ global::System.IComparable, global::System.IEquatable, global::System.IFormattable
+ {
+ public global::System.Guid Value { get; }
+
+ public MyId(global::System.Guid value)
+ {
+ Value = value;
+ }
+
+ public static MyId New() => new MyId(global::System.Guid.NewGuid());
+ public static readonly MyId Empty = new MyId(global::System.Guid.Empty);
+
+ ///
+ public bool Equals(MyId other) => this.Value.Equals(other.Value);
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is MyId other && Equals(other);
+ }
+
+ public override int GetHashCode() => Value.GetHashCode();
+
+ public override string ToString() => Value.ToString();
+
+ public static bool operator ==(MyId a, MyId b) => a.Equals(b);
+ public static bool operator !=(MyId a, MyId b) => !(a == b);
+ public static bool operator > (MyId a, MyId b) => a.CompareTo(b) > 0;
+ public static bool operator < (MyId a, MyId b) => a.CompareTo(b) < 0;
+ public static bool operator >= (MyId a, MyId b) => a.CompareTo(b) >= 0;
+ public static bool operator <= (MyId a, MyId b) => a.CompareTo(b) <= 0;
+
+ ///
+ public int CompareTo(MyId other) => Value.CompareTo(other.Value);
+
+ public partial class MyIdTypeConverter : global::System.ComponentModel.TypeConverter
+ {
+ public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
+ }
+
+ public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value)
+ {
+ return value switch
+ {
+ global::System.Guid guidValue => new MyId(guidValue),
+ string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new MyId(result),
+ _ => base.ConvertFrom(context, culture, value),
+ };
+ }
+
+ public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType)
+ {
+ return sourceType == typeof(global::System.Guid) || sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
+ }
+
+ public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType)
+ {
+ if (value is MyId idValue)
+ {
+ if (destinationType == typeof(global::System.Guid))
+ {
+ return idValue.Value;
+ }
+
+ if (destinationType == typeof(string))
+ {
+ return idValue.Value.ToString();
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+
+ public partial class MyIdSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter
+ {
+ public override bool CanConvert(global::System.Type typeToConvert)
+ => typeToConvert == typeof(global::System.Guid) || typeToConvert == typeof(string) || base.CanConvert(typeToConvert);
+
+ public override MyId Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new (reader.GetGuid());
+
+ public override void Write(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WriteStringValue(value.Value);
+
+#if NET6_0_OR_GREATER
+ public override MyId ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
+ => new(global::System.Guid.Parse(reader.GetString() ?? throw new global::System.FormatException("The string for the MyId property was null")));
+
+ public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, MyId value, global::System.Text.Json.JsonSerializerOptions options)
+ => writer.WritePropertyName(value.Value.ToString());
+#endif
+ }
+
+ public static MyId Parse(string input)
+ => new(global::System.Guid.Parse(input));
+
+#if NET7_0_OR_GREATER
+ ///
+ public static MyId Parse(string input, global::System.IFormatProvider? provider)
+ => new(global::System.Guid.Parse(input, provider));
+
+ ///
+ public static bool TryParse(
+ [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input,
+ global::System.IFormatProvider? provider,
+ out MyId result)
+ {
+ if (input is null)
+ {
+ result = default;
+ return false;
+ }
+
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+#endif
+
+ ///
+ public string ToString(
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ string? format,
+ global::System.IFormatProvider? formatProvider)
+ => Value.ToString(format, formatProvider);
+
+#if NETCOREAPP2_1_OR_GREATER
+ public static MyId Parse(global::System.ReadOnlySpan input)
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET6_0_OR_GREATER
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static MyId Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider)
+#if NET7_0_OR_GREATER
+ => new(global::System.Guid.Parse(input, provider));
+#else
+ => new(global::System.Guid.Parse(input));
+#endif
+
+#if NET7_0_OR_GREATER
+ ///
+#endif
+ public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out MyId result)
+ {
+#if NET7_0_OR_GREATER
+ if (global::System.Guid.TryParse(input, provider, out var guid))
+#else
+ if (global::System.Guid.TryParse(input, out var guid))
+#endif
+ {
+ result = new(guid);
+ return true;
+ }
+ else
+ {
+ result = default;
+ return false;
+ }
+ }
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(destination, out charsWritten, format);
+
+ ///
+ public bool TryFormat(
+ global::System.Span destination,
+ out int charsWritten,
+#if NET7_0_OR_GREATER
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+#endif
+ global::System.ReadOnlySpan format = default)
+ => Value.TryFormat(destination, out charsWritten, format);
+#endif
+#if NET8_0_OR_GREATER
+ ///
+ public bool TryFormat(
+ global::System.Span utf8Destination,
+ out int bytesWritten,
+ [global::System.Diagnostics.CodeAnalysis.StringSyntax(global::System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.GuidFormat)]
+ global::System.ReadOnlySpan format,
+ global::System.IFormatProvider? provider)
+ => Value.TryFormat(utf8Destination, out bytesWritten, format);
+#endif
+ }
diff --git a/test/StronglyTypedIds.Tests/StronglyTypedIdConverterTests.cs b/test/StronglyTypedIds.Tests/StronglyTypedIdConverterTests.cs
new file mode 100644
index 000000000..6b389763b
--- /dev/null
+++ b/test/StronglyTypedIds.Tests/StronglyTypedIdConverterTests.cs
@@ -0,0 +1,288 @@
+using System.Threading.Tasks;
+using StronglyTypedIds.Diagnostics;
+using VerifyXunit;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace StronglyTypedIds.Tests;
+
+[UsesVerify]
+public class StronglyTypedIdConverterTests
+{
+ readonly ITestOutputHelper _output;
+
+ public StronglyTypedIdConverterTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Fact]
+ public Task DefaultConverterIdInGlobalNamespaceWithoutDefaultsDoesntGenerateConverters()
+ {
+ const string input =
+ """
+ using StronglyTypedIds;
+
+ [StronglyTypedId]
+ public partial struct MyId {}
+
+ [StronglyTypedIdConverters]
+ public partial struct MyIdConverters {}
+ """;
+ var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false);
+
+ Assert.Contains(diagnostics, diagnostic => diagnostic.Id == MissingDefaultsDiagnostic.Id);
+
+ return Verifier.Verify(output)
+ .UseDirectory("Snapshots");
+ }
+
+ [Theory]
+ [InlineData("", true)]
+ [InlineData("(\"guid-dapper\")", true)]
+ [InlineData("(\"guid-dapper\")", false)]
+ public Task CanGenerateDefaultConverterIdInGlobalNamespace(string template, bool includeDefaults)
+ {
+ var attribute = includeDefaults
+ ? "[assembly:StronglyTypedIdConvertersDefaults(\"guid-dapper\")]"
+ : string.Empty;
+
+ var input =
+ $$"""
+ using StronglyTypedIds;
+ {{attribute}}
+
+ [StronglyTypedId]
+ public partial struct MyId {}
+
+ [StronglyTypedIdConverters{{template}}]
+ public partial struct MyIdConverters {}
+ """;
+ var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false);
+
+ Assert.Empty(diagnostics);
+
+ return Verifier.Verify(output)
+ .DisableRequireUniquePrefix()
+ .UseDirectory("Snapshots");
+ }
+
+ [Theory]
+ [InlineData("", true)]
+ [InlineData("(\"guid-dapper\")", true)]
+ [InlineData("(\"guid-dapper\")", false)]
+ public Task CanGenerateDefaultConverterIdInNamespace(string template, bool includeDefaults)
+ {
+ var attribute = includeDefaults
+ ? "[assembly:StronglyTypedIdConvertersDefaults(\"guid-dapper\")]"
+ : string.Empty;
+
+ var input =
+ $$"""
+ using StronglyTypedIds;
+ {{attribute}}
+
+ namespace SomeNamespace
+ {
+ [StronglyTypedId]
+ public partial struct MyId {}
+
+ [StronglyTypedIdConverters{{template}}]
+ public partial struct MyIdConverters {}
+ }
+ """;
+ var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false);
+
+ Assert.Empty(diagnostics);
+
+ return Verifier.Verify(output)
+ .DisableRequireUniquePrefix()
+ .UseDirectory("Snapshots");
+ }
+
+ [Theory]
+ [InlineData("", true)]
+ [InlineData("(\"int-dapper\")", true)]
+ [InlineData("(\"int-dapper\")", false)]
+ public Task CanGenerateNonDefaultConverterIdInNamespace(string template, bool includeDefaults)
+ {
+ var attribute = includeDefaults
+ ? "[assembly:StronglyTypedIdConvertersDefaults(\"int-dapper\")]"
+ : string.Empty;
+
+ var input =
+ $$"""
+ using StronglyTypedIds;
+ {{attribute}}
+
+ namespace SomeNamespace
+ {
+ [StronglyTypedId(Template.Int)]
+ public partial struct MyId {}
+
+ [StronglyTypedIdConverters{{template}}]
+ public partial struct MyIdConverters {}
+ }
+ """;
+ var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false);
+
+ Assert.Empty(diagnostics);
+
+ return Verifier.Verify(output)
+ .DisableRequireUniquePrefix()
+ .UseDirectory("Snapshots");
+ }
+
+ [Theory]
+ [InlineData("", true)]
+ [InlineData("(\"guid-dapper\")", true)]
+ [InlineData("(\"guid-dapper\")", false)]
+ public Task CanGenerateDefaultConverterIdInFileScopedNamespace(string template, bool includeDefaults)
+ {
+ var attribute = includeDefaults
+ ? "[assembly:StronglyTypedIdConvertersDefaults(\"guid-dapper\")]"
+ : string.Empty;
+
+ var input =
+ $$"""
+ using StronglyTypedIds;
+ {{attribute}}
+
+ namespace SomeNamespace;
+
+ [StronglyTypedId]
+ public partial struct MyId {}
+
+ [StronglyTypedIdConverters{{template}}]
+ public partial struct MyIdConverters {}
+ """;
+ var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false);
+
+ Assert.Empty(diagnostics);
+
+ return Verifier.Verify(output)
+ .DisableRequireUniquePrefix()
+ .UseDirectory("Snapshots");
+ }
+
+ [Theory]
+ [InlineData("", true)]
+ [InlineData("(\"guid-dapper\")", true)]
+ [InlineData("(\"guid-dapper\")", false)]
+ public Task CanGenerateDefaultConverterIdInDifferentNamespace(string template, bool includeDefaults)
+ {
+ var attribute = includeDefaults
+ ? "[assembly:StronglyTypedIdConvertersDefaults(\"guid-dapper\")]"
+ : string.Empty;
+
+ var input =
+ $$"""
+ using StronglyTypedIds;
+ {{attribute}}
+
+ namespace SomeNamespace1
+ {
+ [StronglyTypedId]
+ public partial struct MyId {}
+ }
+ namespace SomeNamespace2
+ {
+ using SomeNamespace1;
+
+ [StronglyTypedIdConverters{{template}}]
+ public partial struct MyIdConverters {}
+ }
+ """;
+ var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false);
+
+ Assert.Empty(diagnostics);
+
+ return Verifier.Verify(output)
+ .DisableRequireUniquePrefix()
+ .UseDirectory("Snapshots");
+ }
+
+ [Theory]
+ [InlineData("", true)]
+ [InlineData("(\"guid-dapper\")", true)]
+ [InlineData("(\"guid-dapper\")", false)]
+ public Task CanGenerateNestedIdInFileScopeNamespace(string template, bool includeDefaults)
+ {
+ var attribute = includeDefaults
+ ? "[assembly:StronglyTypedIdConvertersDefaults(\"guid-dapper\")]"
+ : string.Empty;
+
+ var input = $$"""
+ using StronglyTypedIds;
+ {{attribute}}
+
+ namespace SomeNamespace;
+
+ public class ParentClass
+ {
+ [StronglyTypedId]
+ public partial struct MyId {}
+ }
+
+ public class ConverterClass
+ {
+ [StronglyTypedIdConverters{{template}}]
+ public partial struct MyIdConverters {}
+ }
+ """;
+ var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false);
+
+ Assert.Empty(diagnostics);
+
+ return Verifier.Verify(output)
+ .DisableRequireUniquePrefix()
+ .UseDirectory("Snapshots");
+ }
+
+ [Theory]
+ [InlineData("", true)]
+ [InlineData("(\"guid-dapper\")", true)]
+ [InlineData("(\"guid-dapper\")", false)]
+ public Task CanGenerateMultipleConvertersWithSameName(string template, bool includeDefaults)
+ {
+ var attribute = includeDefaults
+ ? "[assembly:StronglyTypedIdConvertersDefaults(\"guid-dapper\")]"
+ : string.Empty;
+
+ var input = $$"""
+ using StronglyTypedIds;
+ {{attribute}}
+
+ public class ParentClass
+ {
+ [StronglyTypedId]
+ public partial struct MyId {}
+ }
+
+ namespace MyContracts.V1
+ {
+ public class ConverterClass
+ {
+ [StronglyTypedIdConverters{{template}}]
+ public partial struct MyIdConverters {}
+ }
+ }
+
+ namespace MyContracts.V2
+ {
+ public class ConverterClass
+ {
+ [StronglyTypedIdConverters{{template}}]
+ public partial struct MyIdConverters {}
+ }
+ }
+ """;
+ var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input, includeAttributes: false);
+
+ Assert.Empty(diagnostics);
+
+ return Verifier.Verify(output)
+ .DisableRequireUniquePrefix()
+ .UseDirectory("Snapshots");
+ }
+}
\ No newline at end of file
diff --git a/test/StronglyTypedIds.Tests/StronglyTypedIds.Tests.csproj b/test/StronglyTypedIds.Tests/StronglyTypedIds.Tests.csproj
index 4ef5fe171..1f9b872a6 100644
--- a/test/StronglyTypedIds.Tests/StronglyTypedIds.Tests.csproj
+++ b/test/StronglyTypedIds.Tests/StronglyTypedIds.Tests.csproj
@@ -31,6 +31,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+