├── .gitattributes
├── .github
└── workflows
│ └── BuildAndPack.yml
├── .gitignore
├── .nuke
├── build.schema.json
└── parameters.json
├── CHANGELOG.md
├── Directory.Build.props
├── LICENSE
├── NuGet.integration-tests.config
├── README.md
├── StronglyTypedId.sln
├── build.cmd
├── build.ps1
├── build.sh
├── build
├── .editorconfig
├── Build.cs
├── Configuration.cs
├── Directory.Build.props
├── Directory.Build.targets
├── _build.csproj
└── _build.csproj.DotSettings
├── docs
├── migration.md
├── strongly_typed_id.gif
└── strongly_typed_id.mp4
├── global.json
├── logo.png
├── logo.xcf
├── releasenotes.props
├── src
├── StronglyTypedIds.Attributes
│ ├── StronglyTypedIdAttribute.cs
│ ├── StronglyTypedIdDefaultsAttribute.cs
│ ├── StronglyTypedIds.Attributes.csproj
│ └── Template.cs
├── StronglyTypedIds.Templates
│ ├── StronglyTypedId.Templates.props
│ ├── StronglyTypedId.Templates.targets
│ ├── StronglyTypedIds.Templates.csproj
│ ├── guid-dapper.typedid
│ ├── guid-efcore.typedid
│ ├── guid-full.typedid
│ ├── guid-newtonsoftjson.typedid
│ ├── int-dapper.typedid
│ ├── int-efcore.typedid
│ ├── int-full.typedid
│ ├── int-newtonsoftjson.typedid
│ ├── long-dapper.typedid
│ ├── long-efcore.typedid
│ ├── long-full.typedid
│ ├── long-newtonsoftjson.typedid
│ ├── newid-full.typedid
│ ├── nullablestring-full.typedid
│ ├── string-dapper.typedid
│ ├── string-efcore.typedid
│ ├── string-full.typedid
│ └── string-newtonsoftjson.typedid
└── StronglyTypedIds
│ ├── Constants.cs
│ ├── DiagnosticInfo.cs
│ ├── Diagnostics
│ ├── DiagnosticHelper.cs
│ ├── InvalidTemplateNameDiagnostic.cs
│ ├── MultipleAssemblyAttributeDiagnostic.cs
│ ├── NotPartialDiagnostic.cs
│ ├── UnknownTemplateCodeFixProvider.cs
│ └── UnknownTemplateDiagnostic.cs
│ ├── EmbeddedSources.Guid.cs
│ ├── EmbeddedSources.Int.cs
│ ├── EmbeddedSources.Long.cs
│ ├── EmbeddedSources.String.cs
│ ├── EmbeddedSources.cs
│ ├── EquatableArray.cs
│ ├── HashCode.cs
│ ├── Parser.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── SourceGenerationHelper.cs
│ ├── StronglyTypedId.props
│ ├── StronglyTypedIdGenerator.cs
│ ├── StronglyTypedIds.csproj
│ ├── StronglyTypedIds.csproj.DotSettings
│ ├── StructToGenerate.cs
│ └── Templates
│ └── AutoGeneratedHeader.cs
├── test
├── Directory.Build.props
├── IntegrationLibraries.props
├── StronglyTypedIds.IntegrationTests.ExternalIds
│ ├── StronglyTypedIds.IntegrationTests.ExternalIds.csproj
│ └── xunit.runner.json
├── StronglyTypedIds.IntegrationTests.Types
│ ├── Enums.cs
│ ├── StronglyTypedIds.IntegrationTests.Types.csproj
│ └── simple.typedid
├── StronglyTypedIds.IntegrationTests
│ ├── DapperTypeHandlers.cs
│ ├── DefaultIdTests.cs
│ ├── Enums.cs
│ ├── GuidIdTests.cs
│ ├── IntIdTests.cs
│ ├── LongIdTests.cs
│ ├── MassTransitNewIdTests.cs
│ ├── ModuleInitializerAttribute.cs
│ ├── NestedIdTests.cs
│ ├── NullableStringIdTests.cs
│ ├── SimpleCustomIdTests.cs
│ ├── StringIdTests.cs
│ ├── StronglyTypedIds.IntegrationTests.csproj
│ ├── SystemTextJsonSerializerContext.cs
│ ├── simple.typedid
│ └── xunit.runner.json
├── StronglyTypedIds.Nuget.Attributes.IntegrationTests
│ ├── StronglyTypedIds.Nuget.Attributes.IntegrationTests.csproj
│ └── simple.typedid
├── StronglyTypedIds.Nuget.IntegrationTests
│ ├── StronglyTypedIds.Nuget.IntegrationTests.csproj
│ └── simple.typedid
└── StronglyTypedIds.Tests
│ ├── DiagnosticsTests.cs
│ ├── EmbeddedResourceTests.cs
│ ├── EqualityTests.cs
│ ├── Snapshots
│ ├── EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdAttribute.verified.txt
│ ├── EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=StronglyTypedIdDefaultsAttribute.verified.txt
│ ├── EmbeddedResourceTests.EmittedResourceIsSameAsCompiledResource_resource=Template.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateDefaultIdInGlobalNamespace.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateForCustomTemplate.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateGenericVeryNestedIdInFileScopeNamespace.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateIdInFileScopedNamespace.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateIdInNamespace.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateMultipleIdsWithSameName.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateMultipleTemplatesWithBuiltIn.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateMultipleTemplatesWithoutBuiltIn.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateNestedIdInFileScopeNamespace.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateNonDefaultIdInNamespace.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanGenerateVeryNestedIdInFileScopeNamespace.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanOverrideDefaultsWithCustomTemplateUsingGlobalAttribute.verified.txt
│ ├── StronglyTypedIdGeneratorTests.CanOverrideDefaultsWithTemplateUsingGlobalAttribute.verified.txt
│ ├── UnknownTemplateCodeFixProviderUnitTests.UsesBuiltInTemplates_Guid.verified.txt
│ ├── UnknownTemplateCodeFixProviderUnitTests.UsesBuiltInTemplates_Int.verified.txt
│ ├── UnknownTemplateCodeFixProviderUnitTests.UsesBuiltInTemplates_Long.verified.txt
│ └── UnknownTemplateCodeFixProviderUnitTests.UsesBuiltInTemplates_String.verified.txt
│ ├── SourceGenerationHelperSnapshotTests.cs
│ ├── StronglyTypedIdGeneratorTests.cs
│ ├── StronglyTypedIds.Tests.csproj
│ └── TestHelpers.cs
└── version.props
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 | *.sh eol=lf
6 |
7 | ###############################################################################
8 | # Set default behavior for command prompt diff.
9 | #
10 | # This is need for earlier builds of msysgit that does not have it on by
11 | # default for csharp files.
12 | # Note: This is only used by command line
13 | ###############################################################################
14 | #*.cs diff=csharp
15 |
16 | ###############################################################################
17 | # Set the merge driver for project and solution files
18 | #
19 | # Merging from the command prompt will add diff markers to the files if there
20 | # are conflicts (Merging from VS is not affected by the settings below, in VS
21 | # the diff markers are never inserted). Diff markers may cause the following
22 | # file extensions to fail to load in VS. An alternative would be to treat
23 | # these files as binary and thus will always conflict and require user
24 | # intervention with every merge. To do so, just uncomment the entries below
25 | ###############################################################################
26 | #*.sln merge=binary
27 | #*.csproj merge=binary
28 | #*.vbproj merge=binary
29 | #*.vcxproj merge=binary
30 | #*.vcproj merge=binary
31 | #*.dbproj merge=binary
32 | #*.fsproj merge=binary
33 | #*.lsproj merge=binary
34 | #*.wixproj merge=binary
35 | #*.modelproj merge=binary
36 | #*.sqlproj merge=binary
37 | #*.wwaproj merge=binary
38 |
39 | ###############################################################################
40 | # behavior for image files
41 | #
42 | # image files are treated as binary by default.
43 | ###############################################################################
44 | #*.jpg binary
45 | #*.png binary
46 | #*.gif binary
47 |
48 | ###############################################################################
49 | # diff behavior for common document formats
50 | #
51 | # Convert binary document formats to text before diffing them. This feature
52 | # is only available from the command line. Turn it on by uncommenting the
53 | # entries below.
54 | ###############################################################################
55 | #*.doc diff=astextplain
56 | #*.DOC diff=astextplain
57 | #*.docx diff=astextplain
58 | #*.DOCX diff=astextplain
59 | #*.dot diff=astextplain
60 | #*.DOT diff=astextplain
61 | #*.pdf diff=astextplain
62 | #*.PDF diff=astextplain
63 | #*.rtf diff=astextplain
64 | #*.RTF diff=astextplain
65 |
--------------------------------------------------------------------------------
/.github/workflows/BuildAndPack.yml:
--------------------------------------------------------------------------------
1 | # ------------------------------------------------------------------------------
2 | #
3 | #
4 | # This code was generated.
5 | #
6 | # - To turn off auto-generation set:
7 | #
8 | # [GitHubActions (AutoGenerate = false)]
9 | #
10 | # - To trigger manual generation invoke:
11 | #
12 | # nuke --generate-configuration GitHubActions_BuildAndPack --host GitHubActions
13 | #
14 | #
15 | # ------------------------------------------------------------------------------
16 |
17 | name: BuildAndPack
18 |
19 | on:
20 | push:
21 | branches:
22 | - master
23 | - main
24 | tags:
25 | - '*'
26 | pull_request:
27 | branches:
28 | - '*'
29 |
30 | jobs:
31 | build-and-test:
32 | strategy:
33 | matrix:
34 | include:
35 | - os: windows
36 | vm: windows-latest
37 | - os: linux
38 | vm: ubuntu-latest
39 | - os: macos
40 | vm: macos-13 # latest is arm64, and it breaks a bunch of stuff
41 | env:
42 | MSBuildEnableWorkloadResolver: false
43 | DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: "true"
44 | name: ${{ matrix.os}}
45 | runs-on: ${{ matrix.vm}}
46 | steps:
47 | - uses: actions/checkout@v4
48 | - uses: actions/setup-dotnet@v4
49 | with:
50 | dotnet-version: |
51 | 9.0.x
52 | 8.0.x
53 | 7.0.x
54 | 6.0.x
55 | 3.1.x
56 |
57 | - name: Cache .nuke/temp, ~/.nuget/packages
58 | uses: actions/cache@v3
59 | with:
60 | path: |
61 | .nuke/temp
62 | ~/.nuget/packages
63 | !~/.nuget/packages/stronglytypeid
64 | key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
65 |
66 | - name: Run './build.cmd Clean Test TestPackages PushToNuGet'
67 | run: ./build.cmd Clean Test TestPackages PushToNuGet
68 | env:
69 | NuGetToken: ${{ secrets.NUGET_TOKEN || 'NOT_SET'}}
70 |
71 | - uses: actions/upload-artifact@v4
72 | with:
73 | name: packages-${{ matrix.os}}
74 | path: artifacts/packages
75 | - uses: actions/upload-artifact@v4
76 | with:
77 | name: results-${{ matrix.os}}
78 | path: artifacts/results
79 |
80 | publish-test-results:
81 | name: "Publish Tests Results"
82 | needs: build-and-test
83 | runs-on: ubuntu-latest
84 | permissions:
85 | checks: write
86 | pull-requests: write # needed unless run with comment_mode: off
87 | # contents: read # only needed for private repository
88 | # issues: read # only needed for private repository
89 | if: always()
90 |
91 | steps:
92 | - name: Download Artifacts
93 | uses: actions/download-artifact@v4
94 | with:
95 | path: artifacts/results
96 |
97 | - name: Publish Test Results
98 | uses: EnricoMi/publish-unit-test-result-action@v2
99 | with:
100 | files: "artifacts/**/*.trx"
101 | json_thousands_separator: ","
102 | check_run_annotations_branch: "*"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | .idea
6 | *.suo
7 | *.user
8 | *.userosscache
9 | *.sln.docstates
10 |
11 | # User-specific files (MonoDevelop/Xamarin Studio)
12 | *.userprefs
13 |
14 | # Build results
15 | [Dd]ebug/
16 | [Dd]ebugPublic/
17 | [Rr]elease/
18 | [Rr]eleases/
19 | [Xx]64/
20 | [Xx]86/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 |
85 | # Visual Studio profiler
86 | *.psess
87 | *.vsp
88 | *.vspx
89 | *.sap
90 |
91 | # TFS 2012 Local Workspace
92 | $tf/
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 | *.DotSettings.user
101 |
102 | # JustCode is a .NET coding add-in
103 | .JustCode
104 |
105 | # TeamCity is a build add-in
106 | _TeamCity*
107 |
108 | # DotCover is a Code Coverage Tool
109 | *.dotCover
110 |
111 | # NCrunch
112 | _NCrunch_*
113 | .*crunch*.local.xml
114 | nCrunchTemp_*
115 |
116 | # MightyMoose
117 | *.mm.*
118 | AutoTest.Net/
119 |
120 | # Web workbench (sass)
121 | .sass-cache/
122 |
123 | # Installshield output folder
124 | [Ee]xpress/
125 |
126 | # DocProject is a documentation generator add-in
127 | DocProject/buildhelp/
128 | DocProject/Help/*.HxT
129 | DocProject/Help/*.HxC
130 | DocProject/Help/*.hhc
131 | DocProject/Help/*.hhk
132 | DocProject/Help/*.hhp
133 | DocProject/Help/Html2
134 | DocProject/Help/html
135 |
136 | # Click-Once directory
137 | publish/
138 |
139 | # Publish Web Output
140 | *.[Pp]ublish.xml
141 | *.azurePubxml
142 |
143 | # TODO: Un-comment the next line if you do not want to checkin
144 | # your web deploy settings because they may include unencrypted
145 | # passwords
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # NuGet Packages
150 | *.nupkg
151 | # The packages folder can be ignored because of Package Restore
152 | **/packages/*
153 | # except build/, which is used as an MSBuild target.
154 | !**/packages/build/
155 | # Uncomment if necessary however generally it will be regenerated when needed
156 | #!**/packages/repositories.config
157 | # NuGet v3's project.json files produces more ignoreable files
158 | *.nuget.props
159 | *.nuget.targets
160 |
161 | # Microsoft Azure Build Output
162 | csx/
163 | *.build.csdef
164 |
165 | # Microsoft Azure Emulator
166 | ecf/
167 | rcf/
168 |
169 | # Microsoft Azure ApplicationInsights config file
170 | ApplicationInsights.config
171 |
172 | # Windows Store app package directory
173 | AppPackages/
174 | BundleArtifacts/
175 |
176 | # Visual Studio cache files
177 | # files ending in .cache can be ignored
178 | *.[Cc]ache
179 | # but keep track of directories ending in .cache
180 | !*.[Cc]ache/
181 |
182 | # Others
183 | ClientBin/
184 | [Ss]tyle[Cc]op.*
185 | ~$*
186 | *~
187 | *.dbmdl
188 | *.dbproj.schemaview
189 | *.pfx
190 | *.publishsettings
191 | node_modules/
192 | orleans.codegen.cs
193 |
194 | # RIA/Silverlight projects
195 | Generated_Code/
196 |
197 | # Backup & report files from converting an old project file
198 | # to a newer Visual Studio version. Backup files are not needed,
199 | # because we have git ;-)
200 | _UpgradeReport_Files/
201 | Backup*/
202 | UpgradeLog*.XML
203 | UpgradeLog*.htm
204 |
205 | # SQL Server files
206 | *.mdf
207 | *.ldf
208 |
209 | # Business Intelligence projects
210 | *.rdl.data
211 | *.bim.layout
212 | *.bim_*.settings
213 |
214 | # Microsoft Fakes
215 | FakesAssemblies/
216 |
217 | # GhostDoc plugin setting file
218 | *.GhostDoc.xml
219 |
220 | # Node.js Tools for Visual Studio
221 | .ntvs_analysis.dat
222 |
223 | # Visual Studio 6 build log
224 | *.plg
225 |
226 | # Visual Studio 6 workspace options file
227 | *.opt
228 |
229 | # Visual Studio LightSwitch build output
230 | **/*.HTMLClient/GeneratedArtifacts
231 | **/*.DesktopClient/GeneratedArtifacts
232 | **/*.DesktopClient/ModelManifest.xml
233 | **/*.Server/GeneratedArtifacts
234 | **/*.Server/ModelManifest.xml
235 | _Pvt_Extensions
236 |
237 | # LightSwitch generated files
238 | GeneratedArtifacts/
239 | ModelManifest.xml
240 |
241 | # Paket dependency manager
242 | .paket/paket.exe
243 |
244 | # FAKE - F# Make
245 | .fake/
246 | tools/*
247 | !tools/packages.config
248 |
249 | # NuGet testing
250 | globalPackagesFolder
--------------------------------------------------------------------------------
/.nuke/build.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-04/schema#",
3 | "definitions": {
4 | "Host": {
5 | "type": "string",
6 | "enum": [
7 | "AppVeyor",
8 | "AzurePipelines",
9 | "Bamboo",
10 | "Bitbucket",
11 | "Bitrise",
12 | "GitHubActions",
13 | "GitLab",
14 | "Jenkins",
15 | "Rider",
16 | "SpaceAutomation",
17 | "TeamCity",
18 | "Terminal",
19 | "TravisCI",
20 | "VisualStudio",
21 | "VSCode"
22 | ]
23 | },
24 | "ExecutableTarget": {
25 | "type": "string",
26 | "enum": [
27 | "Clean",
28 | "Compile",
29 | "Pack",
30 | "PushToNuGet",
31 | "Restore",
32 | "Test",
33 | "TestPackages"
34 | ]
35 | },
36 | "Verbosity": {
37 | "type": "string",
38 | "description": "",
39 | "enum": [
40 | "Verbose",
41 | "Normal",
42 | "Minimal",
43 | "Quiet"
44 | ]
45 | },
46 | "NukeBuild": {
47 | "properties": {
48 | "Continue": {
49 | "type": "boolean",
50 | "description": "Indicates to continue a previously failed build attempt"
51 | },
52 | "Help": {
53 | "type": "boolean",
54 | "description": "Shows the help text for this build assembly"
55 | },
56 | "Host": {
57 | "description": "Host for execution. Default is 'automatic'",
58 | "$ref": "#/definitions/Host"
59 | },
60 | "NoLogo": {
61 | "type": "boolean",
62 | "description": "Disables displaying the NUKE logo"
63 | },
64 | "Partition": {
65 | "type": "string",
66 | "description": "Partition to use on CI"
67 | },
68 | "Plan": {
69 | "type": "boolean",
70 | "description": "Shows the execution plan (HTML)"
71 | },
72 | "Profile": {
73 | "type": "array",
74 | "description": "Defines the profiles to load",
75 | "items": {
76 | "type": "string"
77 | }
78 | },
79 | "Root": {
80 | "type": "string",
81 | "description": "Root directory during build execution"
82 | },
83 | "Skip": {
84 | "type": "array",
85 | "description": "List of targets to be skipped. Empty list skips all dependencies",
86 | "items": {
87 | "$ref": "#/definitions/ExecutableTarget"
88 | }
89 | },
90 | "Target": {
91 | "type": "array",
92 | "description": "List of targets to be invoked. Default is '{default_target}'",
93 | "items": {
94 | "$ref": "#/definitions/ExecutableTarget"
95 | }
96 | },
97 | "Verbosity": {
98 | "description": "Logging verbosity during build execution. Default is 'Normal'",
99 | "$ref": "#/definitions/Verbosity"
100 | }
101 | }
102 | }
103 | },
104 | "allOf": [
105 | {
106 | "properties": {
107 | "Configuration": {
108 | "type": "string",
109 | "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
110 | "enum": [
111 | "Debug",
112 | "Release"
113 | ]
114 | },
115 | "GithubToken": {
116 | "type": "string"
117 | },
118 | "NuGetToken": {
119 | "type": "string"
120 | },
121 | "PackagesDirectory": {
122 | "type": "string"
123 | },
124 | "Solution": {
125 | "type": "string",
126 | "description": "Path to a solution file that is automatically loaded"
127 | }
128 | }
129 | },
130 | {
131 | "$ref": "#/definitions/NukeBuild"
132 | }
133 | ]
134 | }
135 |
--------------------------------------------------------------------------------
/.nuke/parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./build.schema.json",
3 | "Solution": "StronglyTypedId.sln"
4 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [v1.0.0-beta08]
4 |
5 | Fixes
6 | * Fix bug in System.Text.Json converters that could cause errors when used with source generators
7 |
8 | ## [v1.0.0-beta07]
9 |
10 | Breaking Changes:
11 | * This release contains a fundamental change in how you define your IDs
12 | * For details see [the related issue](https://github.com/andrewlock/StronglyTypedId/issues/102), the [PR](https://github.com/andrewlock/StronglyTypedId/pull/117), or the [README](./README.md)
13 | * The "options" enums `StronglyTypedIdBackingType`, `StronglyTypedIdConverter`, and `StronglyTypedIdImplementations` have been removed.
14 | * Instead, you simply choose one of 4 different built-in templates, or use a custom template.
15 |
16 | Features
17 | * Adds `[GeneratedCode]` attribute to generated IDs (Fixes https://github.com/andrewlock/StronglyTypedId/issues/57)
18 | * Add support for parsing `SCOPE_IDENTITY()` and `@@IDENTITY` in MSSQL with `DapperTypeHandler` (Fixes https://github.com/andrewlock/StronglyTypedId/issues/118)
19 | * Fix exception being thrown when deserializing nullable strongly-typed id backed by string (Fixes https://github.com/andrewlock/StronglyTypedId/issues/83)
20 | * Allow creating multiple IDs with the same name in a project (Fixes https://github.com/andrewlock/StronglyTypedId/issues/74, [thanks @jo-goro](https://github.com/andrewlock/StronglyTypedId/pull/77)!)
21 |
22 | ## [v1.0.0-beta06]
23 |
24 | Features
25 | * Added support for Masstransit.NewId ([thanks @Khitiara](https://github.com/andrewlock/StronglyTypedId/pull/52)!) Fixes https://github.com/andrewlock/StronglyTypedId/issues/51
26 | * Added parameterless constructor to EF Core ValueConverts for compatibility with global conventions. Fixes https://github.com/andrewlock/StronglyTypedId/issues/50
27 | * Added `#pragma warning disable 1591` to generated code to avoid warning CS1591. Fixes https://github.com/andrewlock/StronglyTypedId/issues/47
28 |
29 |
30 | ## [v1.0.0-beta05]
31 |
32 | Breaking Changes:
33 | * Removed StronglyTypedId.Attributes NuGet package.
34 | * The attributes are no longer embed in your project by default, instead it will use the external dll. You can re-enable the embedding by setting `STRONGLY_TYPED_ID_EMBED_ATTRIBUTES`.
35 |
36 | New Features:
37 |
38 | * Improved approach to handling [InternalsVisibleTo] issues, by embedding the StronglyTypedId.Attributes.dll in the NuGet package directly.
39 |
40 | ## [v1.0.0-beta04]
41 |
42 | New Features:
43 |
44 | * Added support for IDs inside nested classes/records/structs (Fixes https://github.com/andrewlock/StronglyTypedId/issues/40)
45 |
46 | ## [v1.0.0-beta03]
47 |
48 | Breaking Changes:
49 |
50 | * Converted to use .NET 6's incremental source generators. This should provide performance improvements, but it requires using the .NET 6 SDK.
51 |
52 | Bug fixes:
53 |
54 | * Fixed problem deserializing nullable strongly-typed IDs with Newtonsoft.Json (https://github.com/andrewlock/StronglyTypedId/issues/36)
55 |
56 | New Features:
57 |
58 | * To support scenarios in which [InternalsVisibleTo] causes duplicate reference issues with the marker attributes, you can set the msbuild constant `STRONGLY_TYPED_ID_EXCLUDE_ATTRIBUTES` to exclude these from build output. You must then reference the StronglyTypedId.Attributes project as well, which contains the marker attributes.
59 | * By default, the marker attributes are decorated with the `[Conditional]` attribute, so they will not appear on your IDs. If you need these to persist, define the msbuild constant `STRONGLY_TYPED_ID_USAGES`.
60 |
61 | ## [v1.0.0-beta02]
62 |
63 | Bug fixes
64 |
65 | * Adds auto-generated attributes and enums as `internal` to help avoid referencing issues
66 |
67 | ## [v1.0.0-beta01]
68 |
69 | Version 0.x of this library used the helper library [CodeGeneration.Roslyn](https://github.com/AArnott/CodeGeneration.Roslyn) by [AArnott](https://github.com/AArnott), for build-time source generation. In version 1.x this approach has been completely replaced in favour of source generators, as these are explicitly supported in .NET 5+. As part of this change, there were a number of additional features added and breaking changes made.
70 |
71 | Breaking Changes
72 |
73 | * `StronglyTypedIds` namespace is required. In version 0.x of the library, the `[StronglyTypedId]` attribute was in the global namespace. In version 1.x, the attribute is in the `StronglyTypedIds` namespace, so you must add `namespace StronglyTypedIds;`.
74 | * The properties exposed by `StronglyTypedIds` have changed: there is no longer a `generateJsonConverter` property. Instead, this is infered based on the `StronglyTypedIdConverters` flags provided.
75 | * The `String` backing typed ID will throw if you call the constructor with a `null` value
76 |
77 | New Features
78 |
79 | * The attributes can now auto-generate additional converter types such as EF Core `ValueConverter` and Dapper `TypeHandler`, as described in [my blog posts](https://andrewlock.net/series/using-strongly-typed-entity-ids-to-avoid-primitive-obsession/). These are optional flags on the `converters` property.
80 | * Made interface implementations (`IEquatable` and `IComparable` currently) optional. This is to potentially support additional interfaces in future versions.
81 | * Added a `NullableString` backing type. Due to the behaviour of `struct`s in c#, the `String` backing type ID _may_ still be null, but you can't explicitly call the constructor with a null value. In contrast, you can do this with the `NullableString` backing type.
82 | * Added a `[StronglyTypedIdDefaults]` attribute to set default values for all `[StronglyTypedId]` attributes in your project. This is useful if you want to customise all the attributes, for example, if you want to generate additional converters by default. You can still override all the properties of a `[StronglyTypedId]` instance.
83 |
84 | Bug Fixes
85 |
86 | * Some converters had incorrect implementations, such as in ([#26](https://github.com/andrewlock/StronglyTypedId/issues/24)). These have been addressed in version 1.x.
87 | * Better null handling has been added for the `String` backing type, handling issues such as [#32](https://github.com/andrewlock/StronglyTypedId/issues/32).
88 | * The code is marked as auto generated, to avoid errors such as #CS1591 as described in [#27](https://github.com/andrewlock/StronglyTypedId/issues/27)
89 |
90 | ## [v0.2.1]
91 |
92 | Features:
93 |
94 | * Fix Package description
95 |
96 | ## [v0.2.0]
97 |
98 | Features:
99 |
100 | * Added support for .NET Core 3.1, and converted to using CodeGeneration.Roslyn.Tool instead of dotnet-codegen (thanks [Bartłomiej Oryszak
101 | ](https://github.com/vebbo2))
102 | * Added support for generating System.Text.Json `JsonConverters` (thanks [Bartłomiej Oryszak
103 | ](https://github.com/vebbo2))
104 | * Added support for long backing type (thanks [Bartłomiej Oryszak
105 | ](https://github.com/vebbo2))
106 |
107 | ## [v0.1.0]
108 |
109 | Initial release
110 |
111 | [v0.2.0]: https://github.com/andrewlock/StronglyTypedId/compare/v0.1.0...v0.2.0
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Andrew Lock
8 | Copyright © AndrewLock
9 | en-GB
10 | false
11 | MIT
12 | logo.png
13 | https://github.com/andrewlock/StronglyTypedId
14 | stronglytypedid attribute generator generation codegen codegenerator codegeneration netescapades
15 | https://github.com/andrewlock/StronglyTypedId
16 | git
17 | false
18 | true
19 | $(MSBuildThisFileDirectory)artifacts
20 |
21 |
22 |
23 | embedded
24 | true
25 | latest
26 | False
27 | true
28 | true
29 | $(MSBuildThisFileDirectory)\artifacts
30 |
31 |
32 |
33 | README.md
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 andrewlock
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NuGet.integration-tests.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/build.cmd:
--------------------------------------------------------------------------------
1 | :; set -eo pipefail
2 | :; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
3 | :; ${SCRIPT_DIR}/build.sh "$@"
4 | :; exit $?
5 |
6 | @ECHO OFF
7 | powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
8 |
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding()]
2 | Param(
3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
4 | [string[]]$BuildArguments
5 | )
6 |
7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
8 |
9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
11 |
12 | ###########################################################################
13 | # CONFIGURATION
14 | ###########################################################################
15 |
16 | $BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
17 | $TempDirectory = "$PSScriptRoot\\.nuke\temp"
18 |
19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json"
20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
21 | $DotNetChannel = "STS"
22 |
23 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
24 | $env:DOTNET_NOLOGO = 1
25 |
26 | ###########################################################################
27 | # EXECUTION
28 | ###########################################################################
29 |
30 | function ExecSafe([scriptblock] $cmd) {
31 | & $cmd
32 | if ($LASTEXITCODE) { exit $LASTEXITCODE }
33 | }
34 |
35 | # If dotnet CLI is installed globally and it matches requested version, use for execution
36 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
37 | $(dotnet --version) -and $LASTEXITCODE -eq 0) {
38 | $env:DOTNET_EXE = (Get-Command "dotnet").Path
39 | }
40 | else {
41 | # Download install script
42 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
43 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
44 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
45 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
46 |
47 | # If global.json exists, load expected version
48 | if (Test-Path $DotNetGlobalFile) {
49 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
50 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
51 | $DotNetVersion = $DotNetGlobal.sdk.version
52 | }
53 | }
54 |
55 | # Install by channel or version
56 | $DotNetDirectory = "$TempDirectory\dotnet-win"
57 | if (!(Test-Path variable:DotNetVersion)) {
58 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
59 | } else {
60 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
61 | }
62 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
63 | $env:PATH = "$DotNetDirectory;$env:PATH"
64 | }
65 |
66 | Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
67 |
68 | if (Test-Path env:NUKE_ENTERPRISE_TOKEN) {
69 | & $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null
70 | & $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null
71 | }
72 |
73 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
74 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }
75 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | bash --version 2>&1 | head -n 1
4 |
5 | set -eo pipefail
6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
7 |
8 | ###########################################################################
9 | # CONFIGURATION
10 | ###########################################################################
11 |
12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
13 | TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
14 |
15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
16 | DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
17 | DOTNET_CHANNEL="STS"
18 |
19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1
20 | export DOTNET_NOLOGO=1
21 |
22 | ###########################################################################
23 | # EXECUTION
24 | ###########################################################################
25 |
26 | function FirstJsonValue {
27 | perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
28 | }
29 |
30 | # If dotnet CLI is installed globally and it matches requested version, use for execution
31 | if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
32 | export DOTNET_EXE="$(command -v dotnet)"
33 | else
34 | # Download install script
35 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
36 | mkdir -p "$TEMP_DIRECTORY"
37 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
38 | chmod +x "$DOTNET_INSTALL_FILE"
39 |
40 | # If global.json exists, load expected version
41 | if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
42 | DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
43 | if [[ "$DOTNET_VERSION" == "" ]]; then
44 | unset DOTNET_VERSION
45 | fi
46 | fi
47 |
48 | # Install by channel or version
49 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
50 | if [[ -z ${DOTNET_VERSION+x} ]]; then
51 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
52 | else
53 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
54 | fi
55 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
56 | export PATH="$DOTNET_DIRECTORY:$PATH"
57 | fi
58 |
59 | echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)"
60 |
61 | if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "$NUKE_ENTERPRISE_TOKEN" != "" ]]; then
62 | "$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true
63 | "$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true
64 | fi
65 |
66 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
67 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"
68 |
--------------------------------------------------------------------------------
/build/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 | dotnet_style_qualification_for_field = false:warning
3 | dotnet_style_qualification_for_property = false:warning
4 | dotnet_style_qualification_for_method = false:warning
5 | dotnet_style_qualification_for_event = false:warning
6 | dotnet_style_require_accessibility_modifiers = never:warning
7 |
8 | csharp_style_expression_bodied_methods = true:silent
9 | csharp_style_expression_bodied_properties = true:warning
10 | csharp_style_expression_bodied_indexers = true:warning
11 | csharp_style_expression_bodied_accessors = true:warning
12 |
--------------------------------------------------------------------------------
/build/Build.cs:
--------------------------------------------------------------------------------
1 | using Nuke.Common;
2 | using Nuke.Common.CI;
3 | using Nuke.Common.CI.GitHubActions;
4 | using Nuke.Common.IO;
5 | using Nuke.Common.ProjectModel;
6 | using Nuke.Common.Tooling;
7 | using Nuke.Common.Tools.DotNet;
8 | using Nuke.Common.Utilities.Collections;
9 | using static Nuke.Common.EnvironmentInfo;
10 | using static Nuke.Common.Tools.DotNet.DotNetTasks;
11 |
12 | [ShutdownDotNetAfterServerBuild]
13 | class Build : NukeBuild
14 | {
15 | /// Support plugins are available for:
16 | /// - JetBrains ReSharper https://nuke.build/resharper
17 | /// - JetBrains Rider https://nuke.build/rider
18 | /// - Microsoft VisualStudio https://nuke.build/visualstudio
19 | /// - Microsoft VSCode https://nuke.build/vscode
20 |
21 | public static int Main () => Execute(x => x.Compile);
22 |
23 | [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
24 | readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
25 |
26 | [Solution(GenerateProjects = true)] readonly Solution Solution;
27 |
28 | AbsolutePath SourceDirectory => RootDirectory / "src";
29 | AbsolutePath TestsDirectory => RootDirectory / "test";
30 | AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts";
31 | AbsolutePath TestResultsDirectory => ArtifactsDirectory / "results";
32 | AbsolutePath OutputPackagesDirectory => ArtifactsDirectory / "packages";
33 |
34 | [Parameter] readonly string GithubToken;
35 | [Parameter] readonly string NuGetToken;
36 | [Parameter] readonly AbsolutePath PackagesDirectory = RootDirectory / "packages";
37 |
38 | const string NugetOrgUrl = "https://api.nuget.org/v3/index.json";
39 | bool IsTag => GitHubActions.Instance?.Ref?.StartsWith("refs/tags/") ?? false;
40 |
41 | Target Clean => _ => _
42 | .Before(Restore)
43 | .Executes(() =>
44 | {
45 | SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(x => x.DeleteDirectory());
46 | TestsDirectory.GlobDirectories("**/bin", "**/obj").ForEach(x => x.DeleteDirectory());
47 | PackagesDirectory.CreateOrCleanDirectory();
48 | ArtifactsDirectory.CreateOrCleanDirectory();
49 | });
50 |
51 | Target Restore => _ => _
52 | .Executes(() =>
53 | {
54 | DotNetRestore(s => s
55 | .When(_ => !string.IsNullOrEmpty(PackagesDirectory), x=>x.SetPackageDirectory(PackagesDirectory))
56 | .SetProjectFile(Solution));
57 | });
58 |
59 | Target Compile => _ => _
60 | .DependsOn(Restore)
61 | .Executes(() =>
62 | {
63 | DotNetBuild(s => s
64 | .SetProjectFile(Solution)
65 | .SetConfiguration(Configuration)
66 | .When(_ => IsServerBuild, x => x.SetProperty("ContinuousIntegrationBuild", "true"))
67 | .EnableNoRestore());
68 | });
69 |
70 | Target Test => _ => _
71 | .DependsOn(Compile)
72 | .Executes(() =>
73 | {
74 | DotNetTest(s => s
75 | .SetProjectFile(Solution)
76 | .SetConfiguration(Configuration)
77 | .When(_ => IsServerBuild, x => x.SetProperty("ContinuousIntegrationBuild", "true"))
78 | .When(_ => IsServerBuild, x => x
79 | .SetLoggers("trx")
80 | .SetResultsDirectory(TestResultsDirectory))
81 | .EnableNoBuild()
82 | .EnableNoRestore());
83 | });
84 |
85 | Target Pack => _ => _
86 | .DependsOn(Compile)
87 | .After(Test)
88 | .Produces(OutputPackagesDirectory)
89 | .Executes(() =>
90 | {
91 | DotNetPack(s => s
92 | .SetConfiguration(Configuration)
93 | .SetOutputDirectory(OutputPackagesDirectory)
94 | .When(_ => IsServerBuild, x => x.SetProperty("ContinuousIntegrationBuild", "true"))
95 | .EnableNoBuild()
96 | .EnableNoRestore()
97 | .SetProject(Solution));
98 | });
99 |
100 | Target TestPackages => _ => _
101 | .DependsOn(Pack)
102 | .After(Test)
103 | .Produces(OutputPackagesDirectory)
104 | .Executes(() =>
105 | {
106 | var projectFiles = new[]
107 | {
108 | TestsDirectory / "StronglyTypedIds.Nuget.IntegrationTests",
109 | TestsDirectory / "StronglyTypedIds.Nuget.Attributes.IntegrationTests",
110 | };
111 |
112 | if (!string.IsNullOrEmpty(PackagesDirectory))
113 | {
114 | (PackagesDirectory / "stronglytypedid").DeleteDirectory();
115 | (PackagesDirectory / "stronglytypedid.attributes").DeleteDirectory();
116 | }
117 |
118 | DotNetRestore(s => s
119 | .When(_ => !string.IsNullOrEmpty(PackagesDirectory), x => x.SetPackageDirectory(PackagesDirectory))
120 | .SetConfigFile(RootDirectory / "NuGet.integration-tests.config")
121 | .CombineWith(projectFiles, (s, p) => s.SetProjectFile(p)));
122 |
123 | DotNetBuild(s => s
124 | .When(_ => !string.IsNullOrEmpty(PackagesDirectory), x=>x.SetPackageDirectory(PackagesDirectory))
125 | .EnableNoRestore()
126 | .SetConfiguration(Configuration)
127 | .CombineWith(projectFiles, (s, p) => s.SetProjectFile(p)));
128 |
129 | DotNetTest(s => s
130 | .SetConfiguration(Configuration)
131 | .EnableNoBuild()
132 | .EnableNoRestore()
133 | .When(_ => IsServerBuild, x => x
134 | .SetLoggers("trx")
135 | .SetResultsDirectory(TestResultsDirectory))
136 | .CombineWith(projectFiles, (s, p) => s.SetProjectFile(p)));
137 |
138 | });
139 |
140 | Target PushToNuGet => _ => _
141 | .DependsOn(Compile)
142 | .OnlyWhenStatic(() => IsTag && IsServerBuild && IsWin)
143 | .Requires(() => NuGetToken)
144 | .After(Pack)
145 | .Executes(() =>
146 | {
147 | var packages = OutputPackagesDirectory.GlobFiles("*.nupkg");
148 | DotNetNuGetPush(s => s
149 | .SetApiKey(NuGetToken)
150 | .SetSource(NugetOrgUrl)
151 | .EnableSkipDuplicate()
152 | .CombineWith(packages, (x, package) => x
153 | .SetTargetPath(package)));
154 | });
155 | }
156 |
--------------------------------------------------------------------------------
/build/Configuration.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Linq;
4 | using Nuke.Common.Tooling;
5 |
6 | [TypeConverter(typeof(TypeConverter))]
7 | public class Configuration : Enumeration
8 | {
9 | public static Configuration Debug = new Configuration { Value = nameof(Debug) };
10 | public static Configuration Release = new Configuration { Value = nameof(Release) };
11 |
12 | public static implicit operator string(Configuration configuration)
13 | {
14 | return configuration.Value;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/build/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build/_build.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0
6 |
7 | CS0649;CS0169
8 | ..
9 | ..
10 | 1
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/build/_build.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | DO_NOT_SHOW
3 | DO_NOT_SHOW
4 | DO_NOT_SHOW
5 | DO_NOT_SHOW
6 | Implicit
7 | Implicit
8 | ExpressionBody
9 | 0
10 | NEXT_LINE
11 | True
12 | False
13 | 120
14 | IF_OWNER_IS_SINGLE_LINE
15 | WRAP_IF_LONG
16 | False
17 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
18 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
19 | <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
20 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
21 | True
22 | True
23 | True
24 | True
25 | True
26 | True
27 | True
28 | True
29 | True
30 | True
31 |
--------------------------------------------------------------------------------
/docs/migration.md:
--------------------------------------------------------------------------------
1 | # Migration Guide
2 |
3 | This page describes how to migrate between specific versions in which major updates were made.
4 |
5 | ## Migrating from 0.x.x -> 1.x.x
6 |
7 | Version 0.x of this library used the helper library [CodeGeneration.Roslyn](https://github.com/AArnott/CodeGeneration.Roslyn) by [AArnott](https://github.com/AArnott), for build-time source generation. In version 1.0.0 this approach has been completely replaced in favour of source generators, as these are explicitly supported in .NET 6+. As part of this change, there were a number of additional features added and breaking changes made.
8 |
9 | ### Breaking Changes
10 |
11 | * `StronglyTypedIds` namespace is required. In version 0.x of the library, the `[StronglyTypedId]` attribute was in the global namespace. In version 1.x, the attribute is in the `StronglyTypedIds` namespace, so you must add `namespace StronglyTypedIds;`.
12 | * The properties exposed by `StronglyTypedIds` have changed: there is no longer a `generateJsonConverter` property. Instead, this is infered based on the `StronglyTypedIdConverters` flags provided.
13 | * The `String` backing typed ID will throw if you call the constructor with a `null` value
14 |
15 | ### New Features
16 |
17 | * The attributes can now auto-generate additional converter types such as EF Core `ValueConverter` and Dapper `TypeHandler`, as described in [my blog posts](https://andrewlock.net/series/using-strongly-typed-entity-ids-to-avoid-primitive-obsession/). These are optional flags on the `converters` property.
18 | * Made interface implementations (`IEquatable` and `IComparable` currently) optional. This is to potentially support additional interfaces in future versions.
19 | * Added a `NullableString` backing type. Due to the behaviour of `struct`s in c#, the `String` backing type ID _may_ still be null, but you can't explicitly call the constructor with a null value. In contrast, you can do this with the `NullableString` backing type.
20 | * Added a `[StronglyTypedIdDefaults]` attribute to set default values for all `[StronglyTypedId]` attributes in your project. This is useful if you want to customise all the attributes, for example, if you want to generate additional converters by default. You can still override all the properties of a `[StronglyTypedId]` instance.
21 |
22 | ### Bug Fixes
23 |
24 | * Some converters had incorrect implementations, such as in ([#24](https://github.com/andrewlock/StronglyTypedId/issues/24)). These have been addressed in version 1.x.
25 | * Better null handling has been added for the `String` backing type, handling issues such as [#32](https://github.com/andrewlock/StronglyTypedId/issues/32).
26 | * The code is marked as auto generated, to avoid errors such as #CS1591 as described in [#27](https://github.com/andrewlock/StronglyTypedId/issues/27)
27 | * An error deserializing nullable StronglyTypedIds with Newtonsoft.Json [#36](https://github.com/andrewlock/StronglyTypedId/issues/36)
--------------------------------------------------------------------------------
/docs/strongly_typed_id.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewlock/StronglyTypedId/6bd17db4a4b700eaad9e209baf41478cc3f0bbe9/docs/strongly_typed_id.gif
--------------------------------------------------------------------------------
/docs/strongly_typed_id.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewlock/StronglyTypedId/6bd17db4a4b700eaad9e209baf41478cc3f0bbe9/docs/strongly_typed_id.mp4
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "allowPrerelease": true
4 | }
5 | }
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewlock/StronglyTypedId/6bd17db4a4b700eaad9e209baf41478cc3f0bbe9/logo.png
--------------------------------------------------------------------------------
/logo.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewlock/StronglyTypedId/6bd17db4a4b700eaad9e209baf41478cc3f0bbe9/logo.xcf
--------------------------------------------------------------------------------
/releasenotes.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ` and `IComparable` currently) optional. This is to potentially support additional interfaces in future versions.
81 | * Added a `NullableString` backing type. Due to the behaviour of `struct`s in c#, the `String` backing type ID _may_ still be null, but you can't explicitly call the constructor with a null value. In contrast, you can do this with the `NullableString` backing type.
82 | * Added a `[StronglyTypedIdDefaults]` attribute to set default values for all `[StronglyTypedId]` attributes in your project. This is useful if you want to customise all the attributes, for example, if you want to generate additional converters by default. You can still override all the properties of a `[StronglyTypedId]` instance.
83 |
84 | ## Bug Fixes
85 |
86 | * Some converters had incorrect implementations, such as in ([#26](https://github.com/andrewlock/StronglyTypedId/issues/24)). These have been addressed in version 1.x.
87 | * Better null handling has been added for the `String` backing type, handling issues such as [#32](https://github.com/andrewlock/StronglyTypedId/issues/32).
88 | * The code is marked as auto generated, to avoid errors such as #CS1591 as described in [#27](https://github.com/andrewlock/StronglyTypedId/issues/27)
89 |
90 | ]]>
91 |
92 | $(PackageReleaseNotes)
93 | See $(PackageProjectUrl)/blob/master/CHANGELOG.md#v$(VersionPrefix.Replace('.','')) for more details.
94 |
95 |
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Attributes/StronglyTypedIdAttribute.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by the StronglyTypedId source generator
4 | //
5 | // Changes to this file may cause incorrect behavior and will be lost if
6 | // the code is regenerated.
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 | #pragma warning disable 1591 // publicly visible type or member must be documented
11 |
12 | #nullable enable
13 |
14 | namespace StronglyTypedIds
15 | {
16 | ///
17 | /// Place on partial structs to make the type a strongly-typed ID
18 | ///
19 | [global::System.AttributeUsage(global::System.AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
20 | [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")]
21 | public sealed class StronglyTypedIdAttribute : global::System.Attribute
22 | {
23 | ///
24 | /// Make the struct a strongly typed ID.
25 | ///
26 | /// The built-in template to use to generate the ID.
27 | /// The names of additional custom templates to use to generate the ID.
28 | /// Templates must be added to the project using the format NAME.typedid,
29 | /// where NAME is the name of the template passed in .
30 | ///
31 | public StronglyTypedIdAttribute(global::StronglyTypedIds.Template template, params string[] templateNames)
32 | {
33 | }
34 |
35 | ///
36 | /// Make the struct a strongly typed ID.
37 | ///
38 | /// The names of the template to use to generate the ID.
39 | /// Templates must be added to the project using the format NAME.typedid,
40 | /// where NAME is the name of the template passed in .
41 | /// If no templates are provided, the default value is used, as specified by
42 | /// , or alternatively the
43 | /// template.
44 | ///
45 | public StronglyTypedIdAttribute(params string[] templateNames)
46 | {
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Attributes/StronglyTypedIdDefaultsAttribute.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by the StronglyTypedId source generator
4 | //
5 | // Changes to this file may cause incorrect behavior and will be lost if
6 | // the code is regenerated.
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 | #pragma warning disable 1591 // publicly visible type or member must be documented
11 |
12 | #nullable enable
13 |
14 | namespace StronglyTypedIds
15 | {
16 | ///
17 | /// Used to control the default strongly typed ID values. Apply to an assembly using
18 | /// [assembly:StronglyTypedIdDefaults(Template.Int)]
for example
19 | ///
20 | [global::System.AttributeUsage(global::System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
21 | [global::System.Diagnostics.Conditional("STRONGLY_TYPED_ID_USAGES")]
22 | public sealed class StronglyTypedIdDefaultsAttribute : global::System.Attribute
23 | {
24 | ///
25 | /// Set the default template to use for strongly typed IDs
26 | ///
27 | /// The built-in template to use to generate the ID.
28 | /// The names of additional custom templates to use to generate the ID.
29 | /// Templates must be added to the project using the format NAME.typedid,
30 | /// where NAME is the name of the template passed in .
31 | ///
32 | public StronglyTypedIdDefaultsAttribute(global::StronglyTypedIds.Template template, params string[] templateNames)
33 | {
34 | }
35 |
36 | ///
37 | /// Set the default template to use for strongly typed IDs
38 | ///
39 | /// The name of the template to use to generate the ID.
40 | /// Templates must be added to the project using the format NAME.typedid,
41 | /// where NAME is the name of the template passed in .
42 | ///
43 | public StronglyTypedIdDefaultsAttribute(string templateName)
44 | {
45 | }
46 |
47 | ///
48 | /// Set the default template to use for strongly typed IDs
49 | ///
50 | /// The name of the template to use to generate the ID.
51 | /// Templates must be added to the project using the format NAME.typedid,
52 | /// where NAME is the name of the template passed in .
53 | ///
54 | public StronglyTypedIdDefaultsAttribute(string templateName, params string[] templateNames)
55 | {
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Attributes/StronglyTypedIds.Attributes.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | enable
6 | StronglyTypedIds
7 | false
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Attributes/Template.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by the StronglyTypedId source generator
4 | //
5 | // Changes to this file may cause incorrect behavior and will be lost if
6 | // the code is regenerated.
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 | #pragma warning disable 1591 // publicly visible type or member must be documented
11 |
12 | #nullable enable
13 |
14 | using System;
15 |
16 | namespace StronglyTypedIds
17 | {
18 | ///
19 | /// The built-in template to use to generate the strongly-typed ID
20 | ///
21 | public enum Template
22 | {
23 | Guid,
24 | Int,
25 | String,
26 | Long,
27 | }
28 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/StronglyTypedId.Templates.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/StronglyTypedId.Templates.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | $(ShowStronglyTypedIdTemplates)
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/StronglyTypedIds.Templates.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | enable
7 | StronglyTypedId.Templates
8 | A collection of templates for generating strongly-typed IDs by decorating with a [StronglyTypedId] attribute
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/guid-dapper.typedid:
--------------------------------------------------------------------------------
1 | partial struct PLACEHOLDERID
2 | {
3 | public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
4 | {
5 | public override void SetValue(global::System.Data.IDbDataParameter parameter, PLACEHOLDERID value)
6 | {
7 | parameter.Value = value.Value;
8 | }
9 |
10 | public override PLACEHOLDERID Parse(object value)
11 | {
12 | return value switch
13 | {
14 | global::System.Guid guidValue => new PLACEHOLDERID(guidValue),
15 | string stringValue when !string.IsNullOrEmpty(stringValue) && global::System.Guid.TryParse(stringValue, out var result) => new PLACEHOLDERID(result),
16 | _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to PLACEHOLDERID"),
17 | };
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/guid-efcore.typedid:
--------------------------------------------------------------------------------
1 | partial struct PLACEHOLDERID
2 | {
3 | public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter
4 | {
5 | public EfCoreValueConverter() : this(null) { }
6 | public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null)
7 | : base(
8 | id => id.Value,
9 | value => new PLACEHOLDERID(value),
10 | mappingHints
11 | ) { }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/guid-newtonsoftjson.typedid:
--------------------------------------------------------------------------------
1 | [global::Newtonsoft.Json.JsonConverter(typeof(PLACEHOLDERIDNewtonsoftJsonConverter))]
2 | partial struct PLACEHOLDERID
3 | {
4 | public partial class PLACEHOLDERIDNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter
5 | {
6 | public override bool CanConvert(global::System.Type objectType)
7 | {
8 | return objectType == typeof(PLACEHOLDERID);
9 | }
10 |
11 | public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer)
12 | {
13 | serializer.Serialize(writer, value is PLACEHOLDERID id ? id.Value : null);
14 | }
15 |
16 | public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer)
17 | {
18 | var guid = serializer.Deserialize(reader);
19 | return guid.HasValue ? new PLACEHOLDERID(guid.Value) : null;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/int-dapper.typedid:
--------------------------------------------------------------------------------
1 | partial struct PLACEHOLDERID
2 | {
3 | public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
4 | {
5 | public override void SetValue(global::System.Data.IDbDataParameter parameter, PLACEHOLDERID value)
6 | {
7 | parameter.Value = value.Value;
8 | }
9 |
10 | public override PLACEHOLDERID Parse(object value)
11 | {
12 | return value switch
13 | {
14 | int intValue => new PLACEHOLDERID(intValue),
15 | short shortValue => new PLACEHOLDERID(shortValue),
16 | long longValue and < int.MaxValue and > int.MinValue => new PLACEHOLDERID((int)longValue),
17 | decimal decimalValue and < int.MaxValue and > int.MinValue => new PLACEHOLDERID((int)decimalValue),
18 | string stringValue when !string.IsNullOrEmpty(stringValue) && int.TryParse(stringValue, out var result) => new PLACEHOLDERID(result),
19 | _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to PLACEHOLDERID"),
20 | };
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/int-efcore.typedid:
--------------------------------------------------------------------------------
1 | partial struct PLACEHOLDERID
2 | {
3 | public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter
4 | {
5 | public EfCoreValueConverter() : this(null) { }
6 | public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null)
7 | : base(
8 | id => id.Value,
9 | value => new PLACEHOLDERID(value),
10 | mappingHints
11 | ) { }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/int-newtonsoftjson.typedid:
--------------------------------------------------------------------------------
1 | [global::Newtonsoft.Json.JsonConverter(typeof(PLACEHOLDERIDNewtonsoftJsonConverter))]
2 | partial struct PLACEHOLDERID
3 | {
4 | public partial class PLACEHOLDERIDNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter
5 | {
6 | public override bool CanConvert(global::System.Type objectType)
7 | {
8 | return objectType == typeof(PLACEHOLDERID);
9 | }
10 |
11 | public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer)
12 | {
13 | serializer.Serialize(writer, value is PLACEHOLDERID id ? id.Value : null);
14 | }
15 |
16 | public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer)
17 | {
18 | var result = serializer.Deserialize(reader);
19 | return result.HasValue ? new PLACEHOLDERID(result.Value) : null;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/long-dapper.typedid:
--------------------------------------------------------------------------------
1 | partial struct PLACEHOLDERID
2 | {
3 | public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
4 | {
5 | public override void SetValue(global::System.Data.IDbDataParameter parameter, PLACEHOLDERID value)
6 | {
7 | parameter.Value = value.Value;
8 | }
9 |
10 | public override PLACEHOLDERID Parse(object value)
11 | {
12 | return value switch
13 | {
14 | long longValue => new PLACEHOLDERID(longValue),
15 | int intValue => new PLACEHOLDERID(intValue),
16 | short shortValue => new PLACEHOLDERID(shortValue),
17 | decimal decimalValue and < long.MaxValue and > long.MinValue => new PLACEHOLDERID((long)decimalValue),
18 | string stringValue when !string.IsNullOrEmpty(stringValue) && long.TryParse(stringValue, out var result) => new PLACEHOLDERID(result),
19 | _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to PLACEHOLDERID"),
20 | };
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/long-efcore.typedid:
--------------------------------------------------------------------------------
1 | partial struct PLACEHOLDERID
2 | {
3 | public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter
4 | {
5 | public EfCoreValueConverter() : this(null) { }
6 | public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null)
7 | : base(
8 | id => id.Value,
9 | value => new PLACEHOLDERID(value),
10 | mappingHints
11 | ) { }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/long-newtonsoftjson.typedid:
--------------------------------------------------------------------------------
1 | [global::Newtonsoft.Json.JsonConverter(typeof(PLACEHOLDERIDNewtonsoftJsonConverter))]
2 | partial struct PLACEHOLDERID
3 | {
4 | public partial class PLACEHOLDERIDNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter
5 | {
6 | public override bool CanConvert(global::System.Type objectType)
7 | {
8 | return objectType == typeof(PLACEHOLDERID);
9 | }
10 |
11 | public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer)
12 | {
13 | serializer.Serialize(writer, value is PLACEHOLDERID id ? id.Value : null);
14 | }
15 |
16 | public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer)
17 | {
18 | var result = serializer.Deserialize(reader);
19 | return result.HasValue ? new PLACEHOLDERID(result.Value) : null;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/string-dapper.typedid:
--------------------------------------------------------------------------------
1 | partial struct PLACEHOLDERID
2 | {
3 | public partial class DapperTypeHandler : global::Dapper.SqlMapper.TypeHandler
4 | {
5 | public override void SetValue(global::System.Data.IDbDataParameter parameter, PLACEHOLDERID value)
6 | {
7 | parameter.Value = value.Value;
8 | }
9 |
10 | public override PLACEHOLDERID Parse(object value)
11 | {
12 | return value switch
13 | {
14 | string stringValue => new PLACEHOLDERID(stringValue),
15 | _ => throw new global::System.InvalidCastException($"Unable to cast object of type {value.GetType()} to PLACEHOLDERID"),
16 | };
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/string-efcore.typedid:
--------------------------------------------------------------------------------
1 | partial struct PLACEHOLDERID
2 | {
3 | public partial class EfCoreValueConverter : global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ValueConverter
4 | {
5 | public EfCoreValueConverter() : this(null) { }
6 | public EfCoreValueConverter(global::Microsoft.EntityFrameworkCore.Storage.ValueConversion.ConverterMappingHints? mappingHints = null)
7 | : base(
8 | id => id.Value,
9 | value => new PLACEHOLDERID(value),
10 | mappingHints
11 | ) { }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds.Templates/string-newtonsoftjson.typedid:
--------------------------------------------------------------------------------
1 | [global::Newtonsoft.Json.JsonConverter(typeof(PLACEHOLDERIDNewtonsoftJsonConverter))]
2 | partial struct PLACEHOLDERID
3 | {
4 | public partial class PLACEHOLDERIDNewtonsoftJsonConverter : global::Newtonsoft.Json.JsonConverter
5 | {
6 | public override bool CanConvert(global::System.Type objectType)
7 | {
8 | return objectType == typeof(PLACEHOLDERID);
9 | }
10 |
11 | public override void WriteJson(global::Newtonsoft.Json.JsonWriter writer, object? value, global::Newtonsoft.Json.JsonSerializer serializer)
12 | {
13 | serializer.Serialize(writer, value is PLACEHOLDERID id ? id.Value : null);
14 | }
15 |
16 | public override object? ReadJson(global::Newtonsoft.Json.JsonReader reader, global::System.Type objectType, object? existingValue, global::Newtonsoft.Json.JsonSerializer serializer)
17 | {
18 | if (objectType == typeof(PLACEHOLDERID?))
19 | {
20 | var value = serializer.Deserialize(reader);
21 |
22 | return value is null ? null : new PLACEHOLDERID(value);
23 | }
24 |
25 | return new PLACEHOLDERID(serializer.Deserialize(reader)!);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace StronglyTypedIds
2 | {
3 | internal static class Constants
4 | {
5 | public const string StronglyTypedIdsVersion = "1.0.0-beta08";
6 | public const string Usage = nameof(Usage);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/StronglyTypedIds/DiagnosticInfo.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 |
3 | namespace StronglyTypedIds;
4 |
5 | internal sealed record DiagnosticInfo
6 | {
7 | public DiagnosticInfo(DiagnosticDescriptor descriptor, Location location, string? messageArg = null, EquatableArray<(string, string)> properties = default)
8 | {
9 | Descriptor = descriptor;
10 | Location = location;
11 | MessageArg = messageArg;
12 | Properties = properties;
13 | }
14 |
15 | public DiagnosticDescriptor Descriptor { get; }
16 | public Location Location { get; }
17 | public string? MessageArg { get; }
18 | public EquatableArray<(string, string)> Properties { get; }
19 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/Diagnostics/DiagnosticHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Immutable;
2 | using Microsoft.CodeAnalysis;
3 |
4 | namespace StronglyTypedIds.Diagnostics;
5 |
6 | internal static class DiagnosticHelper
7 | {
8 | public static void ReportDiagnostic(this SourceProductionContext context, DiagnosticInfo info)
9 | {
10 | var diagnostic = CreateDiagnostic(info);
11 |
12 | context.ReportDiagnostic(diagnostic);
13 | }
14 |
15 | public static Diagnostic CreateDiagnostic(DiagnosticInfo info)
16 | {
17 | var messageArgs = info.MessageArg is { } arg
18 | ? new object[] {arg}
19 | : null;
20 |
21 | ImmutableDictionary? properties = null;
22 | if (info.Properties is {Count: > 0} props)
23 | {
24 | var dict = ImmutableDictionary.CreateBuilder();
25 | foreach (var prop in props.GetArray()!)
26 | {
27 | dict.Add(prop.Item1, prop.Item2);
28 | }
29 |
30 | properties = dict.ToImmutable()!;
31 | }
32 |
33 |
34 | var diagnostic = Diagnostic.Create(
35 | descriptor: info.Descriptor,
36 | location: info.Location,
37 | messageArgs: messageArgs,
38 | properties: properties);
39 | return diagnostic;
40 | }
41 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/Diagnostics/InvalidTemplateNameDiagnostic.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 |
3 | namespace StronglyTypedIds.Diagnostics;
4 |
5 | internal static class InvalidTemplateNameDiagnostic
6 | {
7 | internal const string Id = "STRONGID001";
8 | internal const string Message = "The template name must not be null or whitespace.";
9 | internal const string Title = "Invalid template name";
10 |
11 | public static DiagnosticInfo CreateInfo(LocationInfo location)
12 | => new(new DiagnosticDescriptor(
13 | Id, Title, Message, category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true),
14 | location.ToLocation());
15 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/Diagnostics/MultipleAssemblyAttributeDiagnostic.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 |
3 | namespace StronglyTypedIds.Diagnostics;
4 |
5 | internal static class MultipleAssemblyAttributeDiagnostic
6 | {
7 | internal const string Id = "STRONGID004";
8 | internal const string Message = "You may only have one instance of the StronglyTypedIdDefaults assembly attribute";
9 | internal const string Title = "Multiple assembly attributes";
10 |
11 | public static DiagnosticInfo CreateInfo(SyntaxNode currentNode) =>
12 | new(new DiagnosticDescriptor(
13 | Id, Title, Message, category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true),
14 | currentNode.GetLocation());
15 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/Diagnostics/NotPartialDiagnostic.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 |
3 | namespace StronglyTypedIds.Diagnostics
4 | {
5 | internal static class NotPartialDiagnostic
6 | {
7 | internal const string Id = "STRONGID003";
8 | internal const string Message = "The target of the StronglyTypedId attribute must be declared as partial.";
9 | internal const string Title = "Must be partial";
10 |
11 | public static Diagnostic Create(SyntaxNode currentNode) =>
12 | Diagnostic.Create(
13 | new DiagnosticDescriptor(
14 | Id, Title, Message, category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true),
15 | currentNode.GetLocation());
16 |
17 | public static DiagnosticInfo CreateInfo(SyntaxNode currentNode)
18 | => new(new DiagnosticDescriptor(
19 | Id, Title, Message, category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true),
20 | currentNode.GetLocation());
21 | }
22 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/Diagnostics/UnknownTemplateCodeFixProvider.cs:
--------------------------------------------------------------------------------
1 | // Based on https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/1dd5ced072d7d870f6dd698a6c02ad509a122452/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Settings/UnknownTemplateCodeFixProvider.cs
2 |
3 | #nullable disable
4 |
5 | using System;
6 | using System.Collections.Immutable;
7 | using System.Composition;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Microsoft.CodeAnalysis;
11 | using Microsoft.CodeAnalysis.CodeActions;
12 | using Microsoft.CodeAnalysis.CodeFixes;
13 |
14 | namespace StronglyTypedIds.Diagnostics;
15 |
16 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UnknownTemplateCodeFixProvider)), Shared]
17 | internal class UnknownTemplateCodeFixProvider : CodeFixProvider
18 | {
19 | internal const string DefaultHeader =
20 | """"
21 | // ACTION REQUIRED: This file was automatically added to your project, but it
22 | // will not take effect until additional steps are taken to enable it. See
23 | // https://github.com/dotnet/roslyn/issues/4655 for more details.
24 | //
25 | // To enable the template, in Visual Studio 2017, 2019, and 2022:
26 | // 1. Select the file in Solution Explorer.
27 | // 2. In the 'Properties' window, set the value for 'Build Action'
28 | // to one of the following (whichever is available):
29 | // - For .NET Core and .NET Standard projects: 'C# analyzer additional file'
30 | // - For other projects: 'AdditionalFiles'
31 | //
32 | // Any instances of PLACEHOLDERID will be replaced with the target ID name
33 | // when generating code from this template.
34 |
35 |
36 | """";
37 |
38 | ///
39 | public override ImmutableArray FixableDiagnosticIds { get; } =
40 | ImmutableArray.Create(Diagnostics.UnknownTemplateDiagnostic.Id);
41 |
42 | ///
43 | public override Task RegisterCodeFixesAsync(CodeFixContext context)
44 | {
45 | var project = context.Document.Project;
46 | var workspace = project.Solution.Workspace;
47 |
48 | // if we can't add extra documents, there's nothing we can do
49 | if (!workspace.CanApplyChange(ApplyChangesKind.AddAdditionalDocument))
50 | {
51 | return Task.CompletedTask;
52 | }
53 |
54 | foreach (var diagnostic in context.Diagnostics)
55 | {
56 | if(!diagnostic.Properties.TryGetValue(UnknownTemplateDiagnostic.TemplateName, out var templateName))
57 | {
58 | // This shouldn't happen, but play it safe
59 | continue;
60 | }
61 |
62 | // check if the template file already exists
63 | var alreadyAdded = false;
64 | foreach (var document in project.AdditionalDocuments)
65 | {
66 | if (document.Name.Equals(templateName, StringComparison.OrdinalIgnoreCase))
67 | {
68 | alreadyAdded = true;
69 | break;
70 | }
71 | }
72 |
73 | if (alreadyAdded)
74 | {
75 | continue;
76 | }
77 |
78 | context.RegisterCodeFix(
79 | CodeAction.Create(
80 | $"Add '{templateName}.typedid' template to the project",
81 | cancellationToken => GetTransformedSolutionAsync(context.Document, templateName, cancellationToken),
82 | nameof(UnknownTemplateCodeFixProvider)),
83 | diagnostic);
84 | }
85 |
86 | return Task.CompletedTask;
87 | }
88 |
89 | ///
90 | public override FixAllProvider GetFixAllProvider()
91 | {
92 | // This code fix does not support fix all actions.
93 | return null;
94 | }
95 |
96 | private static Task GetTransformedSolutionAsync(Document document, string templateName, CancellationToken cancellationToken)
97 | {
98 | // Currently unused
99 | _ = cancellationToken;
100 |
101 | var project = document.Project;
102 | var solution = project.Solution;
103 |
104 | var newDocumentId = DocumentId.CreateNewId(project.Id);
105 |
106 | var templateContent = GetTemplateContent(templateName);
107 |
108 | var newSolution = solution.AddAdditionalDocument(newDocumentId, $"{templateName}.typedid", templateContent);
109 |
110 | return Task.FromResult(newSolution);
111 | }
112 |
113 | internal static string GetTemplateContent(string templateName)
114 | {
115 | var templateContent = templateName switch
116 | {
117 | { } x when x.Contains("int", StringComparison.OrdinalIgnoreCase) => EmbeddedSources.LoadEmbeddedTypedId("int-full.typedid"),
118 | { } x when x.Contains("long", StringComparison.OrdinalIgnoreCase) => EmbeddedSources.LoadEmbeddedTypedId("long-full.typedid"),
119 | { } x when x.Contains("string", StringComparison.OrdinalIgnoreCase) => EmbeddedSources.LoadEmbeddedTypedId("string-full.typedid"),
120 | _ => EmbeddedSources.LoadEmbeddedTypedId("guid-full.typedid"),
121 | };
122 |
123 | return DefaultHeader + templateContent;
124 | }
125 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/Diagnostics/UnknownTemplateDiagnostic.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 |
3 | namespace StronglyTypedIds.Diagnostics;
4 |
5 | internal static class UnknownTemplateDiagnostic
6 | {
7 | internal const string Id = "STRONGID002";
8 | internal const string Title = "Unknown .typedid template";
9 | internal const string TemplateName = nameof(TemplateName);
10 |
11 | public static DiagnosticInfo CreateInfo(LocationInfo location, string name)
12 | => new(CreateDescriptor(),
13 | location.ToLocation(),
14 | name,
15 | new EquatableArray<(string, string)>(new[] {(TemplateName, name)}));
16 |
17 | public static DiagnosticDescriptor CreateDescriptor()
18 | => new(
19 | Id, Title, "Could not find '{0}.typedid' template. Ensure the template exists in the project and has a build action of 'Additional Files'.",
20 | category: Constants.Usage, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true);
21 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/EmbeddedSources.String.cs:
--------------------------------------------------------------------------------
1 | namespace StronglyTypedIds;
2 |
3 | internal static partial class EmbeddedSources
4 | {
5 | private const string StringTemplate = """
6 | partial struct PLACEHOLDERID :
7 | #if NET6_0_OR_GREATER
8 | global::System.ISpanFormattable,
9 | #endif
10 | #if NET7_0_OR_GREATER
11 | global::System.IParsable, global::System.ISpanParsable,
12 | #endif
13 | global::System.IComparable, global::System.IEquatable, global::System.IFormattable
14 | {
15 | public string Value { get; }
16 |
17 | public PLACEHOLDERID(string value)
18 | {
19 | #if NET7_0_OR_GREATER
20 | global::System.ArgumentNullException.ThrowIfNull(value);
21 | Value = value;
22 | #else
23 | Value = value ?? throw new global::System.ArgumentNullException(nameof(value));
24 | #endif
25 | }
26 |
27 | public static readonly PLACEHOLDERID Empty = new PLACEHOLDERID(string.Empty);
28 |
29 | ///
30 | public bool Equals(PLACEHOLDERID other)
31 | => (Value, other.Value) switch
32 | {
33 | (null, null) => true,
34 | (null, _) => false,
35 | (_, null) => false,
36 | (_, _) => Value.Equals(other.Value, global::System.StringComparison.Ordinal),
37 | };
38 |
39 | public override bool Equals(object? obj)
40 | {
41 | if (ReferenceEquals(null, obj)) return false;
42 | return obj is PLACEHOLDERID other && Equals(other);
43 | }
44 |
45 | public override int GetHashCode() => Value.GetHashCode();
46 |
47 | public override string ToString() => Value;
48 |
49 | public static bool operator ==(PLACEHOLDERID a, PLACEHOLDERID b) => a.Equals(b);
50 | public static bool operator !=(PLACEHOLDERID a, PLACEHOLDERID b) => !(a == b);
51 | public static bool operator > (PLACEHOLDERID a, PLACEHOLDERID b) => a.CompareTo(b) > 0;
52 | public static bool operator < (PLACEHOLDERID a, PLACEHOLDERID b) => a.CompareTo(b) < 0;
53 | public static bool operator >= (PLACEHOLDERID a, PLACEHOLDERID b) => a.CompareTo(b) >= 0;
54 | public static bool operator <= (PLACEHOLDERID a, PLACEHOLDERID b) => a.CompareTo(b) <= 0;
55 |
56 | ///
57 | public int CompareTo(PLACEHOLDERID other)
58 | => (Value, other.Value) switch
59 | {
60 | (null, null) => 0,
61 | (null, _) => -1,
62 | (_, null) => 1,
63 | (_, _) => string.CompareOrdinal(Value, other.Value),
64 | };
65 |
66 | public partial class PLACEHOLDERIDTypeConverter : global::System.ComponentModel.TypeConverter
67 | {
68 | public override bool CanConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type sourceType)
69 | {
70 | return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
71 | }
72 |
73 | public override object? ConvertFrom(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object value)
74 | {
75 | if (value is string stringValue)
76 | {
77 | return new PLACEHOLDERID(stringValue);
78 | }
79 |
80 | return base.ConvertFrom(context, culture, value);
81 | }
82 |
83 | public override bool CanConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Type? sourceType)
84 | {
85 | return sourceType == typeof(string) || base.CanConvertTo(context, sourceType);
86 | }
87 |
88 | public override object? ConvertTo(global::System.ComponentModel.ITypeDescriptorContext? context, global::System.Globalization.CultureInfo? culture, object? value, global::System.Type destinationType)
89 | {
90 | if (value is PLACEHOLDERID idValue)
91 | {
92 | if (destinationType == typeof(string))
93 | {
94 | return idValue.Value;
95 | }
96 | }
97 |
98 | return base.ConvertTo(context, culture, value, destinationType);
99 | }
100 | }
101 |
102 | public partial class PLACEHOLDERIDSystemTextJsonConverter : global::System.Text.Json.Serialization.JsonConverter
103 | {
104 | public override PLACEHOLDERID Read(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
105 | => new (reader.GetString()!);
106 |
107 | public override void Write(global::System.Text.Json.Utf8JsonWriter writer, PLACEHOLDERID value, global::System.Text.Json.JsonSerializerOptions options)
108 | => writer.WriteStringValue(value.Value);
109 |
110 | #if NET6_0_OR_GREATER
111 | public override PLACEHOLDERID ReadAsPropertyName(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Type typeToConvert, global::System.Text.Json.JsonSerializerOptions options)
112 | => new(reader.GetString() ?? throw new global::System.FormatException("The string for the PLACEHOLDERID property was null"));
113 |
114 | public override void WriteAsPropertyName(global::System.Text.Json.Utf8JsonWriter writer, PLACEHOLDERID value, global::System.Text.Json.JsonSerializerOptions options)
115 | => writer.WritePropertyName(value.Value);
116 | #endif
117 | }
118 |
119 | public static PLACEHOLDERID Parse(string input)
120 | => new(input);
121 |
122 | #if NET7_0_OR_GREATER
123 | ///
124 | public static PLACEHOLDERID Parse(string input, global::System.IFormatProvider? provider)
125 | => new(input);
126 |
127 | ///
128 | public static bool TryParse(
129 | [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? input,
130 | global::System.IFormatProvider? provider,
131 | out PLACEHOLDERID result)
132 | {
133 | if (input is null)
134 | {
135 | result = default;
136 | return false;
137 | }
138 |
139 | result = new(input);
140 | return true;
141 | }
142 | #endif
143 |
144 | ///
145 | public string ToString(string? format, global::System.IFormatProvider? formatProvider)
146 | => Value;
147 |
148 | #if NETCOREAPP2_1_OR_GREATER
149 | public static PLACEHOLDERID Parse(global::System.ReadOnlySpan input)
150 | => new(input.ToString());
151 | #endif
152 |
153 | #if NET6_0_OR_GREATER
154 | #if NET7_0_OR_GREATER
155 | ///
156 | #endif
157 | public static PLACEHOLDERID Parse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider)
158 | => new(input.ToString());
159 |
160 | #if NET7_0_OR_GREATER
161 | ///
162 | #endif
163 | public static bool TryParse(global::System.ReadOnlySpan input, global::System.IFormatProvider? provider, out PLACEHOLDERID result)
164 | {
165 | result = new(input.ToString());
166 | return true;
167 | }
168 |
169 | ///
170 | public bool TryFormat(
171 | global::System.Span destination,
172 | out int charsWritten,
173 | global::System.ReadOnlySpan format,
174 | global::System.IFormatProvider? provider)
175 | => TryFormat(destination, out charsWritten, format);
176 |
177 | ///
178 | public bool TryFormat(
179 | global::System.Span destination,
180 | out int charsWritten,
181 | global::System.ReadOnlySpan format = default)
182 | {
183 | if (destination.Length > Value.Length)
184 | {
185 | global::System.MemoryExtensions.AsSpan(Value).CopyTo(destination);
186 | charsWritten = Value.Length;
187 | return true;
188 | }
189 |
190 | charsWritten = default;
191 | return false;
192 | }
193 | #endif
194 | }
195 | """;
196 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/EmbeddedSources.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Reflection;
4 | using System.Text;
5 |
6 | namespace StronglyTypedIds;
7 |
8 | internal static partial class EmbeddedSources
9 | {
10 | private static readonly Assembly ThisAssembly = typeof(EmbeddedSources).Assembly;
11 | internal static readonly string StronglyTypedIdAttributeSource = LoadAttributeTemplateForEmitting("StronglyTypedIdAttribute");
12 | internal static readonly string StronglyTypedIdDefaultsAttributeSource = LoadAttributeTemplateForEmitting("StronglyTypedIdDefaultsAttribute");
13 | internal static readonly string TemplateSource = LoadAttributeTemplateForEmitting("Template");
14 |
15 | internal static readonly string AutoGeneratedHeader = LoadEmbeddedResource("StronglyTypedIds.Templates.AutoGeneratedHeader.cs");
16 |
17 | internal static string GetTemplate(Template template)
18 | => template switch
19 | {
20 | Template.Guid => GuidTemplate,
21 | Template.Int => IntTemplate,
22 | Template.Long => LongTemplate,
23 | Template.String => StringTemplate,
24 | _ => string.Empty,
25 | };
26 |
27 | internal static string LoadEmbeddedTypedId(string templateName)
28 | => LoadEmbeddedResource($"StronglyTypedIds.Templates.{templateName}");
29 |
30 | internal static string LoadEmbeddedResource(string resourceName)
31 | {
32 | var resourceStream = ThisAssembly.GetManifestResourceStream(resourceName);
33 | if (resourceStream is null)
34 | {
35 | var existingResources = ThisAssembly.GetManifestResourceNames();
36 | throw new ArgumentException($"Could not find embedded resource {resourceName}. Available names: {string.Join(", ", existingResources)}");
37 | }
38 |
39 | using var reader = new StreamReader(resourceStream, Encoding.UTF8);
40 |
41 | return reader.ReadToEnd();
42 | }
43 |
44 | internal static string LoadAttributeTemplateForEmitting(string resourceName)
45 | {
46 | var resource = LoadEmbeddedResource($"StronglyTypedIds.Templates.Sources.{resourceName}.cs");
47 | // AutoGeneratedHeader is included directly in the file
48 | // so that it's there if you're directly referencing the attributes package too
49 | return resource
50 | .Replace("#nullable enable", """
51 | #nullable enable
52 |
53 | #if STRONGLY_TYPED_ID_EMBED_ATTRIBUTES
54 | """)
55 | .Replace("public sealed", "internal sealed")
56 | .Replace("public enum", "internal enum")
57 | + """
58 |
59 | #endif
60 | """;
61 | }
62 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/EquatableArray.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.Immutable;
5 |
6 | namespace StronglyTypedIds;
7 |
8 | ///
9 | /// An immutable, equatable array. This is equivalent to but with value equality support.
10 | ///
11 | /// The type of values in the array.
12 | internal readonly struct EquatableArray : IEquatable>, IEnumerable
13 | where T : IEquatable
14 | {
15 | public static readonly EquatableArray Empty = new(Array.Empty());
16 |
17 | ///
18 | /// The underlying array.
19 | ///
20 | private readonly T[]? _array;
21 |
22 | ///
23 | /// Creates a new instance.
24 | ///
25 | /// The input to wrap.
26 | public EquatableArray(T[] array)
27 | {
28 | _array = array;
29 | }
30 |
31 | ///
32 | public bool Equals(EquatableArray array)
33 | {
34 | return AsSpan().SequenceEqual(array.AsSpan());
35 | }
36 |
37 | ///
38 | public override bool Equals(object? obj)
39 | {
40 | return obj is EquatableArray array && this.Equals(array);
41 | }
42 |
43 | ///
44 | public override int GetHashCode()
45 | {
46 | if (_array is not T[] array)
47 | {
48 | return 0;
49 | }
50 |
51 | HashCode hashCode = default;
52 |
53 | foreach (T item in array)
54 | {
55 | hashCode.Add(item);
56 | }
57 |
58 | return hashCode.ToHashCode();
59 | }
60 |
61 | ///
62 | /// Returns a wrapping the current items.
63 | ///
64 | /// A wrapping the current items.
65 | public ReadOnlySpan AsSpan()
66 | {
67 | return _array.AsSpan();
68 | }
69 |
70 | ///
71 | /// Gets the underlying array if there is one
72 | ///
73 | public T[]? GetArray() => _array;
74 |
75 | ///
76 | IEnumerator IEnumerable.GetEnumerator()
77 | {
78 | return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator();
79 | }
80 |
81 | ///
82 | IEnumerator IEnumerable.GetEnumerator()
83 | {
84 | return ((IEnumerable)(_array ?? Array.Empty())).GetEnumerator();
85 | }
86 |
87 | public int Count => _array?.Length ?? 0;
88 |
89 | ///
90 | /// Checks whether two values are the same.
91 | ///
92 | /// The first value.
93 | /// The second value.
94 | /// Whether and are equal.
95 | public static bool operator ==(EquatableArray left, EquatableArray right)
96 | {
97 | return left.Equals(right);
98 | }
99 |
100 | ///
101 | /// Checks whether two values are not the same.
102 | ///
103 | /// The first value.
104 | /// The second value.
105 | /// Whether and are not equal.
106 | public static bool operator !=(EquatableArray left, EquatableArray right)
107 | {
108 | return !left.Equals(right);
109 | }
110 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute("StronglyTypedIds.Tests")]
--------------------------------------------------------------------------------
/src/StronglyTypedIds/SourceGenerationHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace StronglyTypedIds
5 | {
6 | internal static class SourceGenerationHelper
7 | {
8 | public static string CreateId(
9 | string idNamespace,
10 | string idName,
11 | ParentClass? parentClass,
12 | string template,
13 | bool addDefaultAttributes,
14 | bool addGeneratedCodeAttribute,
15 | StringBuilder? sb)
16 | {
17 | if (string.IsNullOrEmpty(idName))
18 | {
19 | throw new ArgumentException("Value cannot be null or empty.", nameof(idName));
20 | }
21 |
22 | var hasNamespace = !string.IsNullOrEmpty(idNamespace);
23 |
24 | var parentsCount = 0;
25 |
26 | if (sb is null)
27 | {
28 | sb = new StringBuilder();
29 | }
30 | else
31 | {
32 | sb.Clear();
33 | }
34 |
35 | sb.Append(EmbeddedSources.AutoGeneratedHeader);
36 |
37 | if (hasNamespace)
38 | {
39 | sb
40 | .Append("namespace ")
41 | .Append(idNamespace)
42 | .AppendLine(@"
43 | {");
44 | }
45 |
46 | var hasGenericParent = false;
47 |
48 | while (parentClass is { } parent)
49 | {
50 | sb.Append(" ");
51 |
52 | if (!string.IsNullOrEmpty(parent.Modifiers))
53 | {
54 | sb.Append(parent.Modifiers).Append(' ');
55 | }
56 |
57 | if (parent.Modifiers.IndexOf("partial", StringComparison.Ordinal) == -1)
58 | {
59 | sb.Append("partial ");
60 | }
61 |
62 | sb
63 | .Append(parent.Keyword)
64 | .Append(' ')
65 | .Append(parent.Name)
66 | .Append(' ')
67 | .Append(parent.Constraints)
68 | .AppendLine(@"
69 | {");
70 | parentsCount++;
71 | hasGenericParent |= parent.IsGeneric;
72 | parentClass = parent.Child;
73 | }
74 |
75 | if (addDefaultAttributes && !hasGenericParent)
76 | {
77 | sb.AppendLine(" [global::System.ComponentModel.TypeConverter(typeof(PLACEHOLDERIDTypeConverter))]");
78 | sb.AppendLine(" [global::System.Text.Json.Serialization.JsonConverter(typeof(PLACEHOLDERIDSystemTextJsonConverter))]");
79 | }
80 |
81 | if (addGeneratedCodeAttribute)
82 | {
83 | sb.AppendLine($""" [global::System.CodeDom.Compiler.GeneratedCodeAttribute("StronglyTypedId", "{Constants.StronglyTypedIdsVersion}")]""");
84 | }
85 |
86 | sb.AppendLine(template);
87 |
88 | sb.Replace("PLACEHOLDERID", idName);
89 |
90 | for (int i = 0; i < parentsCount; i++)
91 | {
92 | sb.AppendLine(@" }");
93 | }
94 |
95 | if (hasNamespace)
96 | {
97 | sb.Append('}').AppendLine();
98 | }
99 |
100 | return sb.ToString();
101 | }
102 |
103 | internal static string CreateSourceName(StringBuilder sb, string nameSpace, ParentClass? parent, string name, string template)
104 | {
105 | sb.Clear();
106 | sb.Append(nameSpace).Append('.');
107 | while (parent is { } p)
108 | {
109 | var s = p.Name
110 | .Replace(" ", "")
111 | .Replace(",", "")
112 | .Replace("<", "__")
113 | .Replace(">", "");
114 | sb.Append(s).Append('.');
115 | parent = p.Child;
116 | }
117 |
118 | sb.Append(name);
119 | if (!string.IsNullOrEmpty(template))
120 | {
121 | sb.Append('.').Append(template);
122 | }
123 |
124 | return sb.Append(".g.cs").ToString();
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/StronglyTypedIds/StronglyTypedId.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/StronglyTypedIds/StronglyTypedIds.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | false
6 | enable
7 | StronglyTypedId
8 | A source generator for creating strongly-typed IDs by decorating with a [StronglyTypedId] attribute
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/StronglyTypedIds/StronglyTypedIds.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | False
3 | True
4 | True
--------------------------------------------------------------------------------
/src/StronglyTypedIds/StructToGenerate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.CodeAnalysis;
3 | using Microsoft.CodeAnalysis.Text;
4 |
5 | namespace StronglyTypedIds;
6 |
7 | internal readonly record struct StructToGenerate
8 | {
9 | public StructToGenerate(string name, string nameSpace, Template? template, string[]? templateNames, ParentClass? parent, LocationInfo templateLocation)
10 | {
11 | Name = name;
12 | NameSpace = nameSpace;
13 | TemplateNames = templateNames is null ? EquatableArray.Empty : new EquatableArray(templateNames);
14 | Template = template;
15 | Parent = parent;
16 | TemplateLocation = templateLocation;
17 | }
18 |
19 | public string Name { get; }
20 | public string NameSpace { get; }
21 | public EquatableArray TemplateNames { get; }
22 | public Template? Template { get; }
23 | public ParentClass? Parent { get; }
24 | public LocationInfo? TemplateLocation { get; }
25 | }
26 |
27 | internal sealed record Result(TValue Value, EquatableArray Errors)
28 | where TValue : IEquatable?
29 | {
30 | public static Result<(TValue, bool)> Fail()
31 | => new((default!, false), EquatableArray.Empty);
32 | }
33 |
34 |
35 | internal readonly record struct Defaults
36 | {
37 | public Defaults(Template? template, string[]? templateNames, LocationInfo location, bool hasMultiple)
38 | {
39 | TemplateNames = templateNames is null ? EquatableArray.Empty : new EquatableArray(templateNames);
40 | HasMultiple = hasMultiple;
41 | Template = template;
42 | TemplateLocation = location;
43 | }
44 |
45 | public EquatableArray TemplateNames { get; }
46 | public Template? Template { get; }
47 | public LocationInfo? TemplateLocation { get; }
48 | public bool HasMultiple { get; }
49 | }
50 |
51 | internal record ParentClass(string Modifiers, string Keyword, string Name, string Constraints, ParentClass? Child, bool IsGeneric);
52 |
53 | internal record LocationInfo(string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan)
54 | {
55 | public Location ToLocation()
56 | => Location.Create(FilePath, TextSpan, LineSpan);
57 |
58 | public static LocationInfo CreateFrom(SyntaxNode node)
59 | {
60 | var location = node.GetLocation();
61 | // assuming that source tree is always non-null here... hopefully that's the case
62 | return new LocationInfo(location.SourceTree!.FilePath, location.SourceSpan, location.GetLineSpan().Span);
63 | }
64 | }
--------------------------------------------------------------------------------
/src/StronglyTypedIds/Templates/AutoGeneratedHeader.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by the StronglyTypedId source generator
4 | //
5 | // Changes to this file may cause incorrect behavior and will be lost if
6 | // the code is regenerated.
7 | //
8 | //------------------------------------------------------------------------------
9 |
10 | #pragma warning disable 1591 // publicly visible type or member must be documented
11 |
12 | #nullable enable
13 |
--------------------------------------------------------------------------------
/test/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | net6.0;net7.0;net8.0;net9.0
6 | net48;netcoreapp3.1;$(TargetFrameworks)
7 | false
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 | all
16 | runtime; build; native; contentfiles; analyzers; buildtransitive
17 |
18 |
19 | runtime; build; native; contentfiles; analyzers; buildtransitive
20 | all
21 |
22 |
23 |
--------------------------------------------------------------------------------
/test/IntegrationLibraries.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests.ExternalIds/StronglyTypedIds.IntegrationTests.ExternalIds.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests.ExternalIds/xunit.runner.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3 | "shadowCopy": false
4 | }
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests.Types/Enums.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 | using System;
3 |
4 | namespace StronglyTypedIds.IntegrationTests.Types;
5 |
6 |
7 | [StronglyTypedId]
8 | public partial struct DefaultId1 { }
9 |
10 | [StronglyTypedId]
11 | public partial struct DefaultId2 { }
12 |
13 | [StronglyTypedId(Template.Guid)]
14 | public partial struct GuidId1 { }
15 |
16 | [StronglyTypedId("guid-full")]
17 | public partial struct ConvertersGuidId { }
18 |
19 | [StronglyTypedId(Template.Guid, "guid-efcore", "guid-dapper", "guid-newtonsoftjson")]
20 | public partial struct ConvertersGuidId2 { }
21 |
22 | [StronglyTypedId(Template.Guid)]
23 | public partial struct GuidId2 { }
24 |
25 | [StronglyTypedId(Template.Int)]
26 | public partial struct IntId { }
27 |
28 | [StronglyTypedId("int-full")]
29 | public partial struct ConvertersIntId { }
30 |
31 | [StronglyTypedId(Template.Int, "int-efcore", "int-dapper", "int-newtonsoftjson")]
32 | public partial struct ConvertersIntId2 { }
33 |
34 | [StronglyTypedId(Template.Long)]
35 | public partial struct LongId { }
36 |
37 | [StronglyTypedId("long-full")]
38 | public partial struct ConvertersLongId { }
39 |
40 | [StronglyTypedId(Template.Long, "long-efcore", "long-dapper", "long-newtonsoftjson")]
41 | public partial struct ConvertersLongId2 { }
42 |
43 | [StronglyTypedId("newid-full")]
44 | public partial struct NewIdId1 { }
45 |
46 | [StronglyTypedId("newid-full")]
47 | public partial struct NewIdId2 { }
48 |
49 | [StronglyTypedId(Template.String)]
50 | public partial struct StringId { }
51 |
52 | [StronglyTypedId("string-full")]
53 | public partial struct ConvertersStringId { }
54 |
55 | [StronglyTypedId(Template.String, "string-efcore", "string-dapper", "string-newtonsoftjson")]
56 | public partial struct ConvertersStringId2 { }
57 |
58 | [StronglyTypedId("nullablestring-full")]
59 | public partial struct NullableStringId { }
60 |
61 | [StronglyTypedId("simple")]
62 | public partial struct SimpleCustomId { }
63 |
64 | public partial class SomeType where T : new()
65 | {
66 | public partial record struct NestedType
67 | {
68 | public partial struct MoreNesting
69 | {
70 | [StronglyTypedId]
71 | public readonly partial struct VeryNestedId
72 | {
73 | }
74 | }
75 | }
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests.Types/StronglyTypedIds.IntegrationTests.Types.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests.Types/simple.typedid:
--------------------------------------------------------------------------------
1 | partial struct PLACEHOLDERID
2 | {
3 | public int Value { get; }
4 |
5 | public PLACEHOLDERID(int value)
6 | {
7 | Value = value;
8 | }
9 | }
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests/DapperTypeHandlers.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | using Dapper;
3 | using StronglyTypedIds.IntegrationTests.Types;
4 |
5 | namespace StronglyTypedIds.IntegrationTests
6 | {
7 | public static class DapperTypeHandlers
8 | {
9 | [ModuleInitializer]
10 | public static void AddHandlers()
11 | {
12 | SqlMapper.AddTypeHandler(new ConvertersGuidId.DapperTypeHandler());
13 | SqlMapper.AddTypeHandler(new ConvertersGuidId2.DapperTypeHandler());
14 | SqlMapper.AddTypeHandler(new ConvertersIntId.DapperTypeHandler());
15 | SqlMapper.AddTypeHandler(new ConvertersIntId2.DapperTypeHandler());
16 | SqlMapper.AddTypeHandler(new ConvertersLongId.DapperTypeHandler());
17 | SqlMapper.AddTypeHandler(new ConvertersLongId2.DapperTypeHandler());
18 | SqlMapper.AddTypeHandler(new ConvertersStringId.DapperTypeHandler());
19 | SqlMapper.AddTypeHandler(new ConvertersStringId2.DapperTypeHandler());
20 | SqlMapper.AddTypeHandler(new NullableStringId.DapperTypeHandler());
21 | SqlMapper.AddTypeHandler(new NewIdId1.DapperTypeHandler());
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests/DefaultIdTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Linq;
4 | using Microsoft.Data.Sqlite;
5 | using Microsoft.EntityFrameworkCore;
6 | using StronglyTypedIds.IntegrationTests.Types;
7 | using Xunit;
8 | using NewtonsoftJsonSerializer = Newtonsoft.Json.JsonConvert;
9 | using SystemTextJsonSerializer = System.Text.Json.JsonSerializer;
10 |
11 | namespace StronglyTypedIds.IntegrationTests
12 | {
13 | public class DefaultIdTests
14 | {
15 | [Fact]
16 | public void SameValuesAreEqual()
17 | {
18 | var id = Guid.NewGuid();
19 | var foo1 = new DefaultId1(id);
20 | var foo2 = new DefaultId1(id);
21 |
22 | Assert.Equal(foo1, foo2);
23 | }
24 |
25 | [Fact]
26 | public void EmptyValueIsEmpty()
27 | {
28 | Assert.Equal(DefaultId1.Empty.Value, Guid.Empty);
29 | }
30 |
31 | [Fact]
32 | public void DifferentValuesAreUnequal()
33 | {
34 | var foo1 = DefaultId1.New();
35 | var foo2 = DefaultId1.New();
36 |
37 | Assert.NotEqual(foo1, foo2);
38 | }
39 |
40 | [Fact]
41 | public void OverloadsWorkCorrectly()
42 | {
43 | var id = Guid.NewGuid();
44 | var same1 = new DefaultId1(id);
45 | var same2 = new DefaultId1(id);
46 | var different = DefaultId1.New();
47 |
48 | Assert.True(same1 == same2);
49 | Assert.False(same1 == different);
50 | Assert.False(same1 != same2);
51 | Assert.True(same1 != different);
52 | }
53 |
54 | [Fact]
55 | public void DifferentTypesAreUnequal()
56 | {
57 | var bar = DefaultId2.New();
58 | var foo = DefaultId1.New();
59 |
60 | //Assert.NotEqual(bar, foo); // does not compile
61 | Assert.NotEqual((object)bar, (object)foo);
62 | }
63 |
64 | [Fact]
65 | public void CantCreateEmptyGeneratedId1()
66 | {
67 | var foo = new DefaultId1();
68 | var bar = new DefaultId2();
69 |
70 | //Assert.NotEqual(bar, foo); // does not compile
71 | Assert.NotEqual((object)bar, (object)foo);
72 | }
73 |
74 | [Theory]
75 | [InlineData("78104553-f1cd-41ec-bcb6-d3a8ff8d994d")]
76 | public void TypeConverter_CanConvertToAndFrom(string value)
77 | {
78 | var converter = TypeDescriptor.GetConverter(typeof(DefaultId1));
79 | var id = converter.ConvertFrom(value);
80 | Assert.IsType(id);
81 | Assert.Equal(new DefaultId1(Guid.Parse(value)), id);
82 |
83 | var reconverted = converter.ConvertTo(id, value.GetType());
84 | Assert.Equal(value, reconverted);
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests/Enums.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 | using System;
3 |
4 | namespace StronglyTypedIds.IntegrationTests.Types;
5 |
6 |
7 | [StronglyTypedId]
8 | internal partial struct DefaultId1 { }
9 |
10 | [StronglyTypedId]
11 | internal partial struct DefaultId2 { }
12 |
13 | [StronglyTypedId(Template.Guid)]
14 | internal partial struct GuidId1 { }
15 |
16 | [StronglyTypedId("guid-full")]
17 | internal partial struct ConvertersGuidId { }
18 |
19 | [StronglyTypedId(Template.Guid, "guid-efcore", "guid-dapper", "guid-newtonsoftjson")]
20 | internal partial struct ConvertersGuidId2 { }
21 |
22 | [StronglyTypedId(Template.Guid)]
23 | internal partial struct GuidId2 { }
24 |
25 | [StronglyTypedId(Template.Int)]
26 | internal partial struct IntId { }
27 |
28 | [StronglyTypedId("int-full")]
29 | internal partial struct ConvertersIntId { }
30 |
31 | [StronglyTypedId(Template.Int, "int-efcore", "int-dapper", "int-newtonsoftjson")]
32 | internal partial struct ConvertersIntId2 { }
33 |
34 | [StronglyTypedId(Template.Long)]
35 | internal partial struct LongId { }
36 |
37 | [StronglyTypedId("long-full")]
38 | internal partial struct ConvertersLongId { }
39 |
40 | [StronglyTypedId(Template.Long, "long-efcore", "long-dapper", "long-newtonsoftjson")]
41 | internal partial struct ConvertersLongId2 { }
42 |
43 | [StronglyTypedId("newid-full")]
44 | internal partial struct NewIdId1 { }
45 |
46 | [StronglyTypedId("newid-full")]
47 | internal partial struct NewIdId2 { }
48 |
49 | [StronglyTypedId(Template.String)]
50 | internal partial struct StringId { }
51 |
52 | [StronglyTypedId("string-full")]
53 | internal partial struct ConvertersStringId { }
54 |
55 | [StronglyTypedId(Template.String, "string-efcore", "string-dapper", "string-newtonsoftjson")]
56 | internal partial struct ConvertersStringId2 { }
57 |
58 | [StronglyTypedId("nullablestring-full")]
59 | internal partial struct NullableStringId { }
60 |
61 | [StronglyTypedId("simple")]
62 | internal partial struct SimpleCustomId { }
63 |
64 | internal partial class SomeType where T : new()
65 | {
66 | internal partial record struct NestedType
67 | {
68 | internal partial struct MoreNesting
69 | {
70 | [StronglyTypedId]
71 | internal readonly partial struct VeryNestedId
72 | {
73 | }
74 | }
75 | }
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests/ModuleInitializerAttribute.cs:
--------------------------------------------------------------------------------
1 | #if !NET5_0_OR_GREATER
2 | // ReSharper disable once CheckNamespace
3 | namespace System.Runtime.CompilerServices
4 | {
5 | // This is a C#9 feature, but requires this attribute to be defined
6 | // Only .NET 5 defines it though, it only exists in .NET 5
7 | [AttributeUsage(AttributeTargets.Method, Inherited = false)]
8 | internal sealed class ModuleInitializerAttribute : Attribute
9 | {
10 | }
11 | }
12 | #endif
--------------------------------------------------------------------------------
/test/StronglyTypedIds.IntegrationTests/NestedIdTests.cs:
--------------------------------------------------------------------------------
1 | using StronglyTypedIds.IntegrationTests.Types;
2 | using Xunit;
3 |
4 | namespace StronglyTypedIds.IntegrationTests;
5 |
6 | public class NestedIdTests
7 | {
8 | [Fact]
9 | public void CanCreateNestedId()
10 | {
11 | var id = SomeType